Spring Boot学习笔记四---自动装配

什么是自动装配:

根据我们添加的jar包依赖,自动的将一些bean注册到ioc容器中。当我们需要的时候,直接就可以使用这些bean,而不需要事先手动添加。也就是我们常说的开箱即用。

问题:

  • Spring Boot是怎么实现自动装配的
  • 哪些资源会被自动装配

Spring Boot启动入口,被@SpringBootApplication标注的类,就是Spring Boot的启动类,通过运行该类,可以启动Spring Boot.

@SpringBootApplication
public class SpringBootMytestApplication {

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

}

@SpringBootApplication

@SpringBootApplication源码
@SpringBootApplication源码
@SpringBootApplication源码核心逻辑分析
  • @SpringBootConfiguration:标注的类将作为配置类
  • @EnableAutoConfiguration:启动自动装配
  • @ComponentScan(excludeFilters = {...}):扫描配置路径的包,默认路径为标注该注解的类所在路径
  • Class<?>[] exclude() default {}:根据class排除类,使其不加入spring容器
  • Class<?>[] exclude() default {}:根据classname排除类,使其不加入spring容器
  • String[] scanBasePackages() default {}:扫描配置的包路径
  • Class<?>[] scanBasePackageClasses() default {}:扫描特定的包路径

从源码可以知道@SpringBootApplication其实是一个组合注解,接下来我们看下里面的三个核心注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan

@SpringBootConfiguration

@SpringBootConfiguration源码
@SpringBootConfiguration源码

从源码可以看出来@SpringBootConfiguration其实是@Configuration注解的包装,用来标志一个类是配置类。

@EnableAutoConfiguration

@EnableAutoConfiguration源码
@EnableAutoConfiguration源码
@EnableAutoConfiguration源码分析
  • @AutoConfigurationPackage:自动配置包
  • @Import(...):Spring的底层注解@Import,将AutoConfigurationImportSelector.class导入到容器中
  • Class<?>[] exclude() default {}: 返回不会被导入到 Spring 容器中的类
  • String[] excludeName() default {}:返回不会被导入到 Spring 容器中的类名

@EnableAutoConfiguration是一个组合注解,其中的核心注解是@AutoConfigurationPackage,@Import

@AutoConfigurationPackage

@AutoConfigurationPackage源码
@AutoConfigurationPackage源码

@AutoConfigurationPackage也是一个组合注解,核心注解@Import(AutoConfigurationPackages.Registrar.class)的作用是将一个组件加入到容器中,这里就是将Registrar导入到容器中。
我们来看一下Registrar类中的registerBeanDefinitions方法,它的作用是将注解标注的元信息传入,并获取到相应的包名。

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

再看下register方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }

可以看到register方法会注册一个BeanDefinition,也就是class org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages到容器中,这个BeanDefinition中保存了@AutoConfigurationPackage注解标注类所在到包路径。

@Import(AutoConfigurationImportSelector.class)

@Import(AutoConfigurationImportSelector.class)注解会将AutoConfigurationImportSelector.class导入到容器中,AutoConfigurationImportSelector 可以帮助Spring Boot 将所有符合条件的 @Configuration配置都加载到当前Spring Boot 创建并使用的IOC容器中。
AutoConfigurationImportSelector的类图,可以看到它实现了DeferredImportSelector,Ordered,以及各种Aware接口,这代表着AutoConfigurationImportSelector具有优先级,可以在Spring加载过程中获取到Environment,BeanFactory等资源, 并且会执行DeferredImportSelectorGrouping 类的 getImports方法。


来看下org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports

public Iterable<Group.Entry> getImports() {
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }
        }

遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合存放了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector,然后调用this.group.process(...),最后再返回 this.group.selectImports()。

我们来看下org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process

       @Override
        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()));
            /**
             * 调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
             */
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
            /**
             * 将封装了自动配置类的autoConfigurationEntry对象装进 autoConfigurationEntries集合
             */
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                /**
                 * 将符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
                 */
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

上面的代码中,我们来看下org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry,这个方法主要用来在自动装配中加载需要注入到容器中的类。

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,

                                                               AnnotationMetadata annotationMetadata) {
        /**
         * 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true.
         */
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        /**
         * 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据
         * 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
         * 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据.
         */
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        /**
         * 【1】得到spring.factories文件配置的所有自动配置类
         */
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    
        configurations = removeDuplicates(configurations);
        
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        
        checkExcludedClasses(configurations, exclusions);
        
        configurations.removeAll(exclusions);
        /**
         * 【2】因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
         * 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合
         * @ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication
         */
        configurations = filter(configurations, autoConfigurationMetadata);
        
        fireAutoConfigurationImportEvents(configurations, exclusions);
        /**
         * 【3】将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
         */
        return new AutoConfigurationEntry(configurations, exclusions);
    }

进入到【1】getCandidateConfigurations(annotationMetadata, attributes);方法中,一直来到org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames

 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

获取工厂类型名称org.springframework.boot.autoconfigure.EnableAutoConfiguration ,然后调用org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

Spring Boot在这里加载环境中所有的META-INF/spring.factories中的信息,循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过键org.springframework.boot.autoconfigure.EnableAutoConfiguration 获取值,在将值切割为一个个字符串转化为Array,添加到result集合中.




Spring Boot将result返回,并在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry中做进一步处理

  1. removeDuplicates(configurations): 利用LinkedHashSet移除重复的配置类,避免重复配置
  2. getExclusions(annotationMetadata, attributes):获得要排除的类(配置在注解属性exclude中的类)
  3. checkExcludedClasses(configurations, exclusions): 检查要被排除的配置类
  4. configurations.removeAll(exclusions):将需要排除的类移除
    j接着我们进入【2】中看看
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
        long startTime = System.nanoTime();
        String[] candidates = StringUtils.toStringArray(configurations);
        boolean[] skip = new boolean[candidates.length];
        boolean skipped = false;
                /**
                  * getAutoConfigurationImportFilters方法:拿到OnBeanCondition,
          * OnClassCondition和OnWebApplicationCondition然后遍历这三个条件类去过滤从     
                  * spring.factories加载的大量配置类
                  */
        for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
            invokeAwareMethods(filter);
            boolean[] match = filter.match(candidates, autoConfigurationMetadata);
            for (int i = 0; i < match.length; i++) {
                if (!match[i]) {
                    skip[i] = true;
                    candidates[i] = null;
                    skipped = true;
                }
            }
        }
        if (!skipped) {
            return configurations;
        }
        List<String> result = new ArrayList<>(candidates.length);
        for (int i = 0; i < candidates.length; i++) {
            if (!skip[i]) {
                result.add(candidates[i]);
            }
        }
        if (logger.isTraceEnabled()) {
            int numberFiltered = configurations.size() - result.size();
            logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                    + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
        }
        return new ArrayList<>(result);
    }

从源码我们可以知道filter方法主要做的事情就是调用AutoConfigurationImportFilter接口的match方法来判断每一个自动配置类上的条件注解(若有的话) @ConditionalOnBean或 @ConditionalOnWebApplication是否满足条件,若满足则返回true,若不满足则返回false.可以看到执行完filter方法之后result集合的长度只有23了。

补充:@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件的bean才会注入到容器。
@ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。 @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
@ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。基于SpEL表达式 的条件判断。
@ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。 @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。 @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。 @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。 @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。 @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
@ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
@ConditionalOnResource:当类路径下有指定的资源时触发实例化。
@ConditionalOnJndi:在JNDI存在的条件下触发实例化。
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
现在来看org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry【3】,可以知道它其实就是将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回

AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
            this.configurations = new ArrayList<>(configurations);
            this.exclusions = new HashSet<>(exclusions);
        }

然后我们再来到org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports,在这里从autoConfigurationEntries对象中取出应该注入和排除的配置类,再做一次过滤,过滤完成之后对实现了@Order注解的类进行排序,最后返回需要注入到容器的配置类。

@Override
        public Iterable<Entry> selectImports() {
            if (this.autoConfigurationEntries.isEmpty()) {
                return Collections.emptyList();
            }
            /**
             * 这里得到所有要排除的自动配置类的set集合.
             */
            Set<String> allExclusions = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
            /**
             * 这里得到经过滤后所有符合条件的自动配置类的set集合
             */
            Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
                    .collect(Collectors.toCollection(LinkedHashSet::new));
            /**
             * 移除掉要排除的自动配置类
             */
            processedConfigurations.removeAll(allExclusions);
            /**
             * 对标注有@Order注解的自动配置类进行排序,并返回.
             */
            return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
                    .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
                    .collect(Collectors.toList());
        }
springboot自动装配.png

总结:

  1. 从spring.factories配置文件中通过key:org.springframework.boot.autoconfigure.EnableAutoConfiguration加载自动配置类
  2. 加载的自动配置类中排除掉EnableAutoConfiguration注解的exclude属性指定的自动配置类
  3. 再通过AutoConfigurationImportFilter接口根据条件过滤标注有条件注解的配置类
  4. 最后将需要注入的配置类交给spring,通过spring完成注入
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容