zoukankan      html  css  js  c++  java
  • SpringBoot 源码解析 (五)----- Spring Boot的核心能力

    在上一篇博客中分析了springBoot启动流程,大体的轮廓只是冰山一角。今天就来看一下springBoot的亮点功能:自动化装配功能。

    先从@SpringBootApplication开始。在启动流程章节中,我们讲述了SpringBoot2大致的启动步骤,并进行了源码详解。但是在刷新容器这块并未展开,refreshContext(context);简单的一行代码,背后却做了太多事情。所以为了不喧宾夺主,本篇也尽量选取和注解@SpringBootApplication有关的方法讲解。

    springBoot启动类加载

    首先加载springBoot启动类注入到spring容器中beanDefinitionMap中,看下prepareContext方法中的load方法:load(context, sources.toArray(new Object[0]));
    跟进该方法最终会执行BeanDefinitionLoader的load方法:

    private int load(Object source) {
        Assert.notNull(source, "Source must not be null");
        //如果是class类型,启用注解类型
        if (source instanceof Class<?>) {
            return load((Class<?>) source);
        }
        //如果是resource类型,启用xml解析
        if (source instanceof Resource) {
            return load((Resource) source);
        }
        //如果是package类型,启用扫描包,例如:@ComponentScan
        if (source instanceof Package) {
            return load((Package) source);
        }
        //如果是字符串类型,直接加载
        if (source instanceof CharSequence) {
            return load((CharSequence) source);
        }
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

    继续跟进load(Class<?> source)方法:

    上述方法判断启动类中是否包含@component注解,可我们的启动类并没有该注解。继续跟进会发现,AnnotationUtils判断是否包含该注解是通过递归实现,注解上的注解若包含指定类型也是可以的。

    启动类中包含@SpringBootApplication注解,进一步查找到@SpringBootConfiguration注解,然后查找到@Component注解,最后会查找到@Component注解:

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

    在查找到@Component注解后,表面该对象为spring bean,然后会将其信息包装成 beanDefinitaion ,添加到容器的 beanDefinitionMap中。如下:

    如此一来,我们的启动类就被包装成AnnotatedGenericBeanDefinition了,后续启动类的处理都基于该对象了。

    @EnableAutoConfiguration

    @SpringBootApplication注解中包含了自动配置的入口注解:

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
            @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {}

    我们跟进去看看@EnableAutoConfiguration

    @AutoConfigurationPackage
    @Import(EnableAutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {}

    @AutoConfigurationPackage

    • 自动配置包注解
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {}

    @Import(AutoConfigurationPackages.Registrar.class):默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中。如下

    @Order(Ordered.HIGHEST_PRECEDENCE)
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
             //默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.<Object>singleton(new PackageImport(metadata));
        }
    }

    @Import(EnableAutoConfigurationImportSelector.class)

    EnableAutoConfigurationImportSelector: 导入哪些组件的选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。

     1 //EnableAutoConfigurationImportSelector的父类:AutoConfigurationImportSelector
     2 @Override
     3 public String[] selectImports(AnnotationMetadata annotationMetadata) {
     4     if (!isEnabled(annotationMetadata)) {
     5         return NO_IMPORTS;
     6     }
     7     try {
     8         AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
     9             .loadMetadata(this.beanClassLoader);
    10         AnnotationAttributes attributes = getAttributes(annotationMetadata);
    11         List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    12         configurations = removeDuplicates(configurations);
    13         configurations = sort(configurations, autoConfigurationMetadata);
    14         Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    15         checkExcludedClasses(configurations, exclusions);
    16         configurations.removeAll(exclusions);
    17         configurations = filter(configurations, autoConfigurationMetadata);
    18         fireAutoConfigurationImportEvents(configurations, exclusions);
    19         return configurations.toArray(new String[configurations.size()]);
    20     }
    21     catch (IOException ex) {
    22         throw new IllegalStateException(ex);
    23     }
    24 }

    我们主要看第11行List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);会给容器中注入众多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件。获取这些组件后,还要过滤一下这些组件,我们跟进去看看

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        //...
        return configurations;
    }
    
    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }
    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            //从类路径的META-INF/spring.factories中加载所有默认的自动配置类
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                //获取EnableAutoConfiguration指定的所有值,也就是EnableAutoConfiguration.class的值
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

    SpringBoot启动的时候从类路径下的 META-INF/spring.factories中获取EnableAutoConfiguration指定的值,并将这些值作为自动配置类导入到容器中,自动配置类就会生效,最后完成自动配置工作。EnableAutoConfiguration默认在spring-boot-autoconfigure这个包中,如下图

    最终有96个自动配置类被加载并注册进Spring容器中

    我们也可以将需要自动配置的Bean写入这个文件

    自定义starter

    首先定义一个配置类模块:

    @Configuration
    @ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)
    public class MyAutoConfiguration {
    
        static {
            System.out.println("myAutoConfiguration init...");
        }
    
        @Bean
        public SimpleBean simpleBean(){
            return new SimpleBean();
        }
    
    }

    然后定义一个starter模块,里面无需任何代码,pom也无需任何依赖,只需在META-INF下面建一个 spring.factories文件,添加如下配置:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    spring.study.startup.bean.MyAutoConfiguration

    如图所示:

    最后只需在启动类项目的pom中引入我们的 starter 模块即可。

    原理

    最终在AutoConfigurationImportSelector解析spring.factories文件:

    springBoot为我们提供的配置类有180多个,但是我们不可能会全部引入。按条件注解 @Conditional或者@ConditionalOnProperty等相关注解进行判断,决定是否需要装配。

    我们自定义的配置类也是以相同的逻辑进行装配,我们指定了以下注解:

    @ConditionalOnProperty(name = "enabled.autoConfituration", matchIfMissing = true)

    默认为 true,所以自定义的starter成功执行。

  • 相关阅读:
    与你的领导意见不一致时你会怎么做?
    调用caffe脚本将图片转换为了lmdb格式
    第04组 Alpha冲刺(4/4)
    第04组 Alpha冲刺(3/4)
    第04组 Alpha冲刺(2/4)
    第04组 Alpha冲刺(1/4)
    2019 SDN上机第3次作业
    2019 SDN阅读作业
    2019 SDN上机第2次作业
    2019 SDN上机第1次作业
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/11837043.html
Copyright © 2011-2022 走看看