springboot源码之自动装备原理

上节我们了解了springboot的应用的启动原理,其所有的核心都在run方法里,来看一段简单的代码:

@SpringBootApplication
public class AppApplication {

public static void main(String[] args) {
    SpringApplication.run(AppApplication.class, args);
}

还是原来的味道,我们上节了解了run,接下来看看注解springBootApplication的原理过程

SpringBootApplication

我们可以发现该注解位于org.springframework.boot.autoconfigure包下,基本上我们的springboot应用都会用到该注解,都说该注解是自动装配作用,到底是如何运作的呢?其实我们不难发现该注解是一个组合注解,代码如下:

//springBootApplication.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM,   classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

/**
 * Exclude specific auto-configuration classes such that they will never be applied.
 * @return the classes to exclude
 */
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};

/**
 * Exclude specific auto-configuration class names such that they will never be
 * applied.
 * @return the class names to exclude
 * @since 1.3.0
 */
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};

/**
 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
 * for a type-safe alternative to String-based package names.
 * @return base packages to scan
 * @since 1.3.0
 */
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};

/**
 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
 * scan for annotated components. The package of each class specified will be scanned.
 * <p>
 * Consider creating a special no-op marker class or interface in each package that
 * serves no purpose other than being referenced by this attribute.
 * @return base packages to scan
 * @since 1.3.0
 */
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}

我们接下来看一下这些注解的作用:

Inherited

该注解是JDK自带的注解,该注解一般作用于类上时,表明子类是可以继承它的,对方法或者属性时无效的.关于该注解的详解这里给大家推荐一篇写的不错的文章关于java 注解中元注解Inherited的使用详解,博主写的很详细这里就不多说了,我们接着看

SpringBootConfiguration

该注解位于org.springframework.boot.@SpringBootConfiguration包下,其主要的作用是用来标记是一个springboot的配置类,我们来看代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

可以看到的是,我们该注解的上面还是继承spring的Configuration的注解,就是表明是一个配置类.

ComponentScan

该注解位于org.springframework.context.annotation包下,其主要的作用是用来扫描指定路径下的组件(@Componment、@Configuration、@Service等标注的注解)

EnableAutoConfiguration

该注解位于org.springframework.boot.autoconfigure包下,其主要的作用是用来开启自动装配的功能,也是springboot项目中最核心的注解,我们来看代码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**
 * Exclude specific auto-configuration classes such that they will never be applied.
 * @return the classes to exclude
 */
Class<?>[] exclude() default {};

/**
 * Exclude specific auto-configuration class names such that they will never be
 * applied.
 * @return the class names to exclude
 * @since 1.3.0
 */
String[] excludeName() default {};

}

从代码中看到,AutoConfigurationPackage注解引起了我们的关注,该注解位于org.springframework.boot.autoconfigure包下,其主要的作用是自动配置包,可以获取主程序所在的包路径,同时将组件注册到spring的容器中,代码如下:

/**
 * Indicates that the package containing the annotated class should be registered with
 * {@link AutoConfigurationPackages}.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see AutoConfigurationPackages
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

从代码中可以看到,我们需要的注重点放在注解Import上,该注解位于org.springframework.context.annotation包下,其主要的作用是引入别的资源文件,关于该注解的使用可以看这篇文章@Import注解——导入资源,其中最重要的AutoConfigurationImportSelector才是我们需要重点分析的对象,接下来我们来看.

AutoConfigurationImportSelector

该类位于org.springframework.boot.autoconfigure包下,分别实现了DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口,来处理EnableAutoConfiguration注解的资源引入,来看代码:

getCandidateConfigurations

该getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) 方法主要是用来获取符合条件的配置类数组:

//AutoConfigurationImportSelector.java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

    //1.从META-INF/spring.factories文件下加载指定类型的类的数组
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}
  • 在1. 处,通过调用#getSpringFactoriesLoaderFactoryClass()在meta-inf/spring.factories下加载我们的配置类EnableAutoConfiguration,来看代码:
/**
 * Return the class used by {@link SpringFactoriesLoader} to load configuration
 * candidates.
 * @return the factory class
 */
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
    }

在来接着看,在上述代码中通过SpringFactoriesLoader#loadFactoryNames(...)方法从meta-inf/spring.factories中加载指定类型EnableAutoConfiguration的类.来看一张图:

微信截图_20190906221139.png

我们来看通过#getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)方法是如何调用的过程,来看张调用过程的图:

微信截图_20190906230558.png

在上图的执行过程中我们总结了大概有3步的过程,分别来看下:

  • 在第1步处,通过调用#refresh来准备spring容器的初始化过程,关于该部分的详解我们在上篇[spring容器之从bean的实例中获取对2(https://www.jianshu.com/p/b587b501bec6)说的很清楚了,想了解的可以去看看 .
  • 在第2步处,通过#getCandidateConfigurations(...)从meta-inf/spring.factories文件下加载所有的EnableAutoConfiguration配置类型数组
  • 第3步,才是我们接下来需要了解的东西,首先我们来看一段代码:
    private final DeferredImportSelector.Group group;
    public Iterable<Group.Entry> getImports() {
        for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
            this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                    deferredImport.getImportSelector());
        }
        return this.group.selectImports();
    }
}

该段代码来源于org.springframework.context.annotation.ConfigurationClassParser下内部类DeferredImportSelectorGroupingHandler#Iterable(...)方法,该方法的主要作用配置由注解import导入的每一个关联的配置类.

  • 在上述代码中我们可以看到的是通过调用DeferredImportSelector.group#process(AnnotationMetadata metadata, DeferredImportSelector selector)方法来处理标有注解import的注解.后面来说.
  • 在方法的最后通过调用DeferredImportSelector.group.selectImports()来选择需要导入的配置类.后续来说.
微信截图_20190907095205.png

从上图中我们可以看到在此处该方法导入了22个配置类.在我们这段代码中一直有一个东东我们需要搞明白,那就是Group实例是如何来的问题,在我们的AutoConfiguationImportSelector类中有一个方法getImportGroup来获取对应的Group实例的接下来我们来看代码:

getImportGroup
public Class<? extends Group> getImportGroup() {
    return AutoConfigurationGroup.class;
}

该方法位于AutoConfiguationImportSelector,其次是该类实现了DeferredImportSelector接口,接着看:

AutoConfigurationGroup

首先是AutoConfigurationGroup位于AutoConfiguationImportSelector类的内部类,同样的该类实现了DeferredImportSelector.Group .BeanClassLoaderAware以及BeanFactoryAware和ResourceLoaderAware接口.

    /**AnnotationMetadata类型的映射*/
    private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
    /**保存AutoConfigurationEntry的集合*/
    private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

    private ClassLoader beanClassLoader;

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;

    private AutoConfigurationMetadata autoConfigurationMetadata;

上面这些代码是AutoConfigurationGroup类的属性,在该类中有一个AutoConfigurationGroup#process(...)方法,在该方法中是进行的赋值操作,包括我们的entries属性,其中key为配置类的全限定名称,来看张图:

微信截图_20190907191127.png

看完了上图中entries属性的赋值过程,发现也没有我们想象的那么难,接着我们来看autoConfigurationEntries属性的赋值过程,同样是在#process(...)方法中进行赋值的操作,来看:

微信截图_20190907174151.png

我们也看到了,其中AutoConfigurationEntry为AutoConfigurationImportSelector的内部类,来看代码:

protected static class AutoConfigurationEntry {
    /** 配置类的全类名的数组*/
    private final List<String> configurations;
    /***
     * 排除的配置类的全类名的数组
     */
    private final Set<String> exclusions;

    private AutoConfigurationEntry() {
        this.configurations = Collections.emptyList();
        this.exclusions = Collections.emptySet();
    }

接着看,其中AutoConfigurationMetadata属性主要的作用是用来保存自动配置的的元数据.通过方法#getAutoConfigurationMetadata()来初始化的,来看代码:

private AutoConfigurationMetadata getAutoConfigurationMetadata() {
        //如果不存在的话,进行加载
        if (this.autoConfigurationMetadata == null) {
            this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        }
        //存在的话直接返回
        return this.autoConfigurationMetadata;
    }
微信截图_20190907204621.png

简单的来说一下,要想让InfluxDbAutoConfiguration配置类生效,对应的InfluxDbAutoConfiguration类上@ConditionalOnClass({ InfluxDB.class }) 注解上就应该有对应的类型,因此,所以,autoConfigurationMetadata 属性,用途就是制定配置类(Configuration)的生效条件(Condition)

process方法

该方法位于#AutoConfigurationImportSelector#AutoConfigurationGroup内部类的# process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector)方法:

    public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
        //
        Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                () -> String.format("Only %s implementations are supported, got %s",
                        AutoConfigurationImportSelector.class.getSimpleName(),
                        deferredImportSelector.getClass().getName()));
        //1.获取AutoConfigurationEntry实例
        AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
        //2.保存
        this.autoConfigurationEntries.add(autoConfigurationEntry);
        //3.遍历添加到entries中
        for (String importClassName : autoConfigurationEntry.getConfigurations()) {
            this.entries.putIfAbsent(importClassName, annotationMetadata);
        }
    }

该方法就做了上述代码的三件事我们来看一下,其中我们的参数AnnotationMetadata一般是用来标注注解SpringBootApplication的元数据,因为我们的注解SpringBootApplication组合了注解EnableAutoConfiguration.参数deferredImportSelector一般是用来@EnableAutoConfiguration定义的import注解标注的类,也就是AutoConfigurationImportSelector对象.

  • 在1处,通过调用#AutoConfigurationImportSelector#getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) 方法来获取AutoConfigurationEntry对象,我们来看该方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    //1.判断是否开启,没有的话,直接返回一个空数组
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    //2.获取原注解的属性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //3.获取符合条件的配置类数组
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //4.去除重复的配置类
    configurations = removeDuplicates(configurations);
    //5.获取需要排除的配置类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    //5.1.校验排除的配置类
    checkExcludedClasses(configurations, exclusions);
    //5.2.从配置类数组中移除掉排除的配置类
    configurations.removeAll(exclusions);
    //6.过滤不符合的配置类从configurations中
    configurations = filter(configurations, autoConfigurationMetadata);
    //7.触发自动配置类引入完成的事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    //8.创建AutoConfigurationEntry对象
    return new AutoConfigurationEntry(configurations, exclusions);
}

简单的来看一下上述方法都做了些什么过程:

  • 在1.处,通过调用#isEnabled(annotationMetadata)来判断是否开启,未开启的话,直接返回一个空数组,代码如下:
//AutoConfigurationImportSelector.java
protected boolean isEnabled(AnnotationMetadata metadata) {
    //判断'spring.boot.EnableAutoConfiguration'是否配置,是否开启自动配置
    //默认情况下(未配置)
    if (getClass() == AutoConfigurationImportSelector.class) {
        return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    return true;
}
  • 在2处,通过调用方法# getAttributes(annotationMetadata)来获取原注解的属性,代码如下:
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
    //
    String name = getAnnotationClass().getName();
    //获取属性
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
    Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
            + " annotated with " + ClassUtils.getShortName(name) + "?");
    return attributes;
}

上述代码中首先是通过方法#getAnnotationClass().getName()获取到的是@EnableAutoConfiguration,所以这里返回的注解属性只能是exclude和excludeName这两个.

  • 在3处通过方法#getCandidateConfigurations(annotationMetadata, attributes)获取符合条件的配置类的数组.
  • 在4处,通过方法#removeDuplicates(configurations)将获取到的配置类进行过滤处理,这里选择需要的,代码如下:
  protected final <T> List<T> removeDuplicates(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
}
  • 在5处,通过调用#getExclusions(annotationMetadata, attributes)方法来获取需要排除的配置类,代码如下:
//AutoConfigurationImportSelector.java
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    //注解属性上的exclude
    excluded.addAll(asList(attributes, "exclude"));
    //添加注解上的excludeName属性
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    //添加配置文件上的属性
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

从上面的代码来看获取排除类的方式总共有三种,分别来看:

  • 通过注解上的exclude属性来排除的.
//AutoConfigurationImportSelector.java
protected final List<String> asList(AnnotationAttributes attributes, String name) {
    String[] value = attributes.getStringArray(name);
    return Arrays.asList((value != null) ? value : new String[0]);
}
  • 第二种是通过注解上的excludeName属性来排除的
  • 方式三是通过配置文件上的属性来排除的,这里一般是spring.autoconfigure.exclude配置
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private List<String> getExcludeAutoConfigurationsProperty() {

    if (getEnvironment() instanceof ConfigurableEnvironment) {
        Binder binder = Binder.get(getEnvironment());
        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
                .orElse(Collections.emptyList());
    }
    String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
  • 在5.1.处,通过方法#checkExcludedClasses(configurations, exclusions)对我们要排除的配置类进行合法性的检验,代码如下:
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
    //用来保存需要检验的配置类
    List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    //从集合exclusions中遍历处理
    //如果该配置类在exclusions中(不在configurations中),进行添加操作
    for (String exclusion : exclusions) {
          //classPath中存在该配置类
        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
            invalidExcludes.add(exclusion);
        }
    }
    //invalidExcludes不为null,抛IllegalStateException异常
    if (!invalidExcludes.isEmpty()) {
        handleInvalidExcludes(invalidExcludes);
    }
}

/**
 * Handle any invalid excludes that have been specified.
 * @param invalidExcludes the list of invalid excludes (will always have at least one
 * element)
 */
protected void handleInvalidExcludes(List<String> invalidExcludes) {
    StringBuilder message = new StringBuilder();
    for (String exclude : invalidExcludes) {
        message.append("\t- ").append(exclude).append(String.format("%n"));
    }
    throw new IllegalStateException(String.format(
            "The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s",
            message));
}

从上面的代码中可以看处,排除的过程是这样的,因为我们的配置类是存在于classPath下的,而不存在与configurations中,所以这样就可以直接排除了,接着看:

  • 在5.2.处,从我们的配置类数组中configurations移除掉要排除的配置类
  • 在6处,通过方法#filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata)从configurations过滤掉不符合的配置类,后续来说该方法
  • 在7处,通过方法# fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions)来触发自动配置类完成的一些列事件,代码如下:
//AutoConfigurationImportSelector.java
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
    //1.在meta-INF目录下的spring.factories中加载指定类型为AutoConfigurationImportListener的所有类的数组
    List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
    //不为null
    if (!listeners.isEmpty()) {
        //2.通过我们的配置类数组和exclusions来构建AutoConfigurationImportEvent对象
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        //3.遍历 AutoConfigurationImportListener监听器们逐个通知
        for (AutoConfigurationImportListener listener : listeners) {
            //3.1设置AutoConfigurationImportListener属性
            invokeAwareMethods(listener);
            //3.2.进行通知操作
            listener.onAutoConfigurationImportEvent(event);
        }
    }
}

上述代码中,不能理解,大致做了三件事,我们分别来看一下:

  • 首先是通过#getAutoConfigurationImportListeners()方法在meta-INF目录下的spring.factories文件中去加载指定类型(AutoConfigurationImportListener)的所有类的集合,我们通过dbug会发现如下图:
微信截图_20190922092016.png

在我们的集合中有一个类,接着看:

  • 在上述条件成立的情况下,也就是当我们的listeners不为null时:
    • 在2处创建一个AutoConfigurationImportEvent事件对象
  • 在3处遍历 AutoConfigurationImportListener监听器们逐个通知
  • 在3.1.处,通过调用#invokeAwareMethods(Object instance)方法来设置AutoConfigurationImportListener的相关属性,代码如下:
private void invokeAwareMethods(Object instance) {
    //是Aware类型的
    if (instance instanceof Aware) {
        if (instance instanceof BeanClassLoaderAware) {
            ((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
        }
        if (instance instanceof BeanFactoryAware) {
            ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
        }
        if (instance instanceof EnvironmentAware) {
            ((EnvironmentAware) instance).setEnvironment(this.environment);
        }
        if (instance instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
        }
    }
}

代码简单,这里就不说啥了,我们接着看:

  • 在3.2.处,对我们的事件进行通知处理操作,代码如下:
//ConditionEvaluationReportAutoConfigurationImportListener.java
public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
    if (this.beanFactory != null) {
        ConditionEvaluationReport report = ConditionEvaluationReport.get(this.beanFactory);
        report.recordEvaluationCandidates(event.getCandidateConfigurations());
        report.recordExclusions(event.getExclusions());
    }
}

代码简单,就不啰嗦了,继续回到我们的getAutoConfigurationEntry(...)方法

  • 在8处,创建AutoConfigurationEntry对象

关于process方法我们基本上完了,我们来看一下注解AutoConfigurationPackage的作用

AutoConfigurationPackage

该注解位于org.springframework.boot.autoconfigure包下,官方是这样解释的:

Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner)

大致的意思是这样的,如果使用该注解@AutoConfigurationPackage所在的包,会被注册到spring IOC容器中的一个bean,我们通过获取该bean就能获取该bean所在的包,如:获取JPA等.

接下来,我们AutoConfigurationPackage类下的一些重要的方法

Registrar

Registrar是AutoConfigurationPackage类的一个内部类,可以看到的是它实现了ImportBeanDefinitionRegistrar和DeterminableImports接口,代码如下:

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

在上述代码中,我们需要关注几个点如:

  • 在方法#registerBeanDefinitions(...)中,需要我们new PackageImport对象作为参数的传递,那么PackageImport是什么,我们来看代码:
//AutoConfigurationPackages.java
/**
 * Wrapper for a package import.
 */
private static final class PackageImport {
    //包名
    private final String packageName;

    PackageImport(AnnotationMetadata metadata) {
        this.packageName = ClassUtils.getPackageName(metadata.getClassName());
    }

    public String getPackageName() {
        return this.packageName;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.packageName.equals(((PackageImport) obj).packageName);
    }

    @Override
    public int hashCode() {
        return this.packageName.hashCode();
    }

    @Override
    public String toString() {
        return "Package Import " + this.packageName;
    }

}

我们发现PackageImport类为AutoConfigurationPackages的内部类,其主要的作用是用来获取包名.接着看

register方法

该方法主要的作用是注册包名的bean到spring容器中,以备后续通过该bean直接获取对应的包名,代码如下:

private static final String BEAN = AutoConfigurationPackages.class.getName();
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    //如果存在该bean
    if (registry.containsBeanDefinition(BEAN)) {
        //获取该bean的定义
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        //获取构造参数以及构造函数等
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        //修改其包名属性
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    //不存在该bean,创建一个GenericBeanDefinition兵注册
    else {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

在上述注册的BEAN的类型为BasePackages类型的,其中BasePackages为AutoConfigurationPackages的内部类,来看代码:

/**
 * Holder for the base package (name may be null to indicate no scanning).
 */
static final class BasePackages {

    private final List<String> packages;

    private boolean loggedBasePackageInfo;

    BasePackages(String... names) {
        List<String> packages = new ArrayList<>();
        for (String name : names) {
            if (StringUtils.hasText(name)) {
                packages.add(name);
            }
        }
        this.packages = packages;
    }

    public List<String> get() {
        if (!this.loggedBasePackageInfo) {
            if (this.packages.isEmpty()) {
                if (logger.isWarnEnabled()) {
                    logger.warn("@EnableAutoConfiguration was declared on a class "
                            + "in the default package. Automatic @Repository and "
                            + "@Entity scanning is not enabled.");
                }
            }
            else {
                if (logger.isDebugEnabled()) {
                    String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages);
                    logger.debug("@EnableAutoConfiguration was declared on a class " + "in the package '"
                            + packageNames + "'. Automatic @Repository and @Entity scanning is " + "enabled.");
                }
            }
            this.loggedBasePackageInfo = true;
        }
        return this.packages;
    }

}

这段代码实质就是对packages的封装过程,无需多言,接着我们来看看注册的过程

  • 在注册的过程中,如果存在BEAN仅仅是对包名属性进行修改,其中是通过方法addBasePackages(...)来操作,我们来看该方法:
private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
    //获取存在的包名属性的值
    String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0, String[].class).getValue();
    Set<String> merged = new LinkedHashSet<>();
    //进行覆盖操作
    merged.addAll(Arrays.asList(existing));
    merged.addAll(Arrays.asList(packageNames));
    return StringUtils.toStringArray(merged);
}

在我们的注册方法中,如果不存在该 BEAN ,则创建一个 Bean ,并进行注册.

  • 首先是通过#has(BeanFactory beanFactory)来判断是否在spring容器中存在该bean,代码如下:
/**
 * Determine if the auto-configuration base packages for the given bean factory are
 * available.
 * @param beanFactory the source bean factory
 * @return true if there are auto-config packages available
 */
public static boolean has(BeanFactory beanFactory) {
    return beanFactory.containsBean(BEAN) && !get(beanFactory).isEmpty();
}
  • 接着是通过get方法来从容器中获取我们的bean,代码如下:
/**
 * Return the auto-configuration base packages for the given bean factory.
 * @param beanFactory the source bean factory
 * @return a list of auto-configuration packages
 * @throws IllegalStateException if auto-configuration is not enabled
 */
public static List<String> get(BeanFactory beanFactory) {
    try {
        return beanFactory.getBean(BEAN, BasePackages.class).get();
    }
    catch (NoSuchBeanDefinitionException ex) {
        throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
    }
}

到这里关于springboot的自动装配的过程分析的差不多了....

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容