美文网首页
springboot源码之自动装备原理

springboot源码之自动装备原理

作者: 会上树的程序猿 | 来源:发表于2019-09-26 08:21 被阅读0次

    上节我们了解了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的自动装配的过程分析的差不多了....

    相关文章

      网友评论

          本文标题:springboot源码之自动装备原理

          本文链接:https://www.haomeiwen.com/subject/cjvxyctx.html