美文网首页
Spring Boot自动配置原理详解

Spring Boot自动配置原理详解

作者: superlee01 | 来源:发表于2019-03-11 00:26 被阅读0次

    Spring Boot是Pivotal团队提供的全新框架,使用“习惯优于配置”的理念,将开发过程中的习惯配置以自动注入的形式注入配置,只需很少的配置就可以快速开始项目。

    本文使用的Spring Boot版本为2.1.3.RELEASE作为示例。

    自定义一个自动配置类

    首先我们使用idea新建一个Spring Boot应用,发现入口类上面有一个@SpringBootApplication的注解,这个就是Spring Boot自动配置的核心。

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

    接下来我们新建一个配置类CustomConfig

    @Configuration
    public class CustomConfig {
    
        @Bean
        public CustomConfig customConfig() {
            System.out.println("CustomConfig has been loaded");
            return new CustomConfig();
        }
    }
    

    resources目录下新建META-INF文件夹,然后新建spring.factories文件,内容如下:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.superlee.CustomConfig
    

    指定了CustomConfig的类路径。这样我们自定义的一个自动配置类就完成类,然后就是启动应用验证了,启用应用后控制台打印如下:

    Connected to the target VM, address: '127.0.0.1:50615', transport: 'socket'
    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.1.3.RELEASE)
    
    2019-03-10 15:19:53.716  INFO 7969 --- [           main] com.superlee.TestApplication             : Starting TestApplication on superleedeMacBook-Pro.local with PID 7969 (/Users/superlee/Documents/idea_work/superlee/target/classes started by superlee in /Users/superlee/Documents/idea_work/superlee)
    2019-03-10 15:19:53.719  INFO 7969 --- [           main] com.superlee.TestApplication             : No active profile set, falling back to default profiles: default
    CustomConfig has been loaded
    2019-03-10 15:19:54.172  INFO 7969 --- [           main] com.superlee.TestApplication             : Started TestApplication in 0.73 seconds (JVM running for 1.219)
    Disconnected from the target VM, address: '127.0.0.1:50615', transport: 'socket'
    
    Process finished with exit code 0
    

    我们可以看到打印出来了CustomConfig has been loaded这句话,说明我们的自动配置生效了。

    自动配置的思考

    Spring Boot怎么会知道要把CustomConfig这个类自动加载到配置中呢?回想一下我们做了哪些操作:
    1.在程序入口TestApplication上加了@SpringBootApplication注解
    2.在CustomConfig上加上@Configuration注解
    3.新建了resources/META-INF/spring.factories文件,并将CustomConfig的包路径配置到了EnableAutoConfiguration的key下面

    我们可以猜测,Spring Boot应该是根据我们配置的CustomConfig包路径去实例化该对象,然后放到Spring context中,接下来就要看下源码看看是如何实现的。

    源代码分析

    SpringBootApplication
    @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 {
    //省略
    }
    

    我们可以发现SpringBootApplication这个注解集成了很多个注解,重点关注SpringBootConfiguration EnableAutoConfiguration ComponentScan这三个注解。

    @Configuration
    public @interface SpringBootConfiguration {
    }
    

    SpringBootConfiguration注解其实和Configuration等效的,标示为Spring Boot应用提供配置。

    ComponentScan组件扫描注解,用于扫描指定包的bean,没有指定包名则扫描该注解所在的路径下的文件。本例即TestApplication所在的根目录。
    这里需要注意excludeFilters属性,即组件扫描不包含的过滤器,看下AutoConfigurationExcludeFilter这个过滤器:

    public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {
    
        private ClassLoader beanClassLoader;
    
        private volatile List<String> autoConfigurations;
    
        @Override
        public void setBeanClassLoader(ClassLoader beanClassLoader) {
            this.beanClassLoader = beanClassLoader;
        }
    
        @Override
        public boolean match(MetadataReader metadataReader,
                MetadataReaderFactory metadataReaderFactory) throws IOException {
            return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
        }
    
        private boolean isConfiguration(MetadataReader metadataReader) {
            return metadataReader.getAnnotationMetadata()
                    .isAnnotated(Configuration.class.getName());
        }
    
        private boolean isAutoConfiguration(MetadataReader metadataReader) {
            return getAutoConfigurations()
                    .contains(metadataReader.getClassMetadata().getClassName());
        }
    
        protected List<String> getAutoConfigurations() {
            if (this.autoConfigurations == null) {
                this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(
                        EnableAutoConfiguration.class, this.beanClassLoader);
            }
            return this.autoConfigurations;
        }
    
    }
    

    我们可以看到这个过滤器有个match()方法,如果类加了@Configuration并且声明为EnableAutoConfiguration的自动配置类,则匹配。
    但这里组件扫描注解不包含这个filter,也就是说组件扫描的时候,并不会示例化自动配置的类。那么这些自动配置类是如何被找到然后加载到IOC容器中呢?
    下面我们看下EnableAutoConfiguration注解

    @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 {};
    }
    

    我们可以看到该注解导入了一个AutoConfigurationImportSelector选择器,该选择器有一个AutoConfigurationGroup的内部类,我们关注一下该内部类的process()方法

    public class AutoConfigurationImportSelector
            implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
            BeanFactoryAware, EnvironmentAware, Ordered {
        ···
        protected AutoConfigurationEntry getAutoConfigurationEntry(
                AutoConfigurationMetadata autoConfigurationMetadata,
                AnnotationMetadata annotationMetadata) {
            //判断是否启用了EnableAutoConfiguration注解
            if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            }
            //获取excludeName和exclude属性
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            //拿到所有的自动配置类
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            //移除重复的自动配置类
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            //校验注解中不包含的自动配置类的合法性,如果classpath中不包含忽略的类或者自动配置的类中不包含忽略的类,抛异常
            checkExcludedClasses(configurations, exclusions);
            //移除要忽略的类
            configurations.removeAll(exclusions);
            //类加载器遍历class,如果某个自动配置类的Conditional不满足,则移除改配置
            configurations = filter(configurations, autoConfigurationMetadata);
            //发送自动配置类导入的事件
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    
        ...
        private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
                BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
            ...
    
            @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()));
                //加载autoConfiguration的路径名。
                AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                        .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                                annotationMetadata);
                this.autoConfigurationEntries.add(autoConfigurationEntry);
                for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                    this.entries.putIfAbsent(importClassName, annotationMetadata);
                }
            }
        }
    }
    

    我们看一下getAutoConfigurationEntry(_, _)方法里调用的getCandidateConfigurations(annotationMetadata, attributes)方法:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                          AnnotationAttributes attributes) {
            //getSpringFactoriesLoaderFactoryClass()方法返回EnableAutoConfiguration.class类
            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;
        }
    

    SpringFactoriesLoader是自动配置扫描过程中很重要的一个类,我们看下它的代码:

    public final class SpringFactoriesLoader {
    
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            //拿到传入的EnableAutoConfiguration.class
            String factoryClassName = factoryClass.getName();
            //获取key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的所有集合
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
        private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
    
            try {
                //classLoader去搜索所有包含META-INF/spring.factories文件的jar包
                Enumeration<URL> urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap<>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    //读取jar中的spring.factories文件,然后转换为key-value形式
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        //spring.factories文件中的属性名
                        String factoryClassName = ((String) entry.getKey()).trim();
                        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }
    }
    

    这样就可以把classpath里所有的自动配置类找出来,放到list集合中,后面可以根据包路径使用反射的方法去实例化。

    Conditional条件注解

    条件注解为自动配置提供类实现基础,我们可以看到很多自动配置类都和条件注解结合使用,这样就可以灵活的根据classpath中的具体类去加载具体的配置。

    总结

    1.SpringBoot程序启动的时候,回去看是否启用了EnableAutoConfiguration
    2.启用的情况下,classLoader在classpath中搜索所有的包含META-INF/spring.factories文件的jar包,然后解析spring.factories文件,拿到所有key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的字符集合
    3.根据条件注解和classpath中包含的类去筛选满足条件的配置,然后使用反射实例化
    4.把实例化的配置加载到Spring IOC容器中,然后刷新上下文,即可自动加载配置了。

    相关文章

      网友评论

          本文标题:Spring Boot自动配置原理详解

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