美文网首页java面试Spring Boot
SpringBoot源码系列(一):深入理解自动配置原理

SpringBoot源码系列(一):深入理解自动配置原理

作者: Renaissance_ | 来源:发表于2021-08-02 12:55 被阅读0次

    SpringBoot作为目前市面上的主流框架,大小公司都会基于SpringBoot进行开发,因此在面试中出现频率也是很高的,从本文开始,和大家一起深入理解SpringBoot工作原理。

    1. SpringBoot 特点

    首先先看下笔者总结的几点SpringBoot优点。

    • SpringBoot 并未提供Spring框架外的功能,它是一个快速集成Spring项目的工具
    • SpringBoot提供内置的tomcat, jetty等web容器,因此不需要将项目打包成war包,以jar包方式即可运行
    • SpringBoot 减少了繁琐的xml配置,通过自动配置大幅度提高了开发效率

    本文将理解自动配置为目标,带大家深入浅出自动配置原理是怎么一回事。

    2. 预备知识

    在看源码之前,我们先了解下几个注解。

    2.1 @Configuration注解

    @Configuration注解应该是最常见的注解了,被@Configuration注解修饰的类会被作为一个配置类,@Bean注解修改的方法会返回一个对象,这个对象将会以Java Config的形式被Spring IOC容器管理。

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Configuration {
    
    
        @AliasFor(annotation = Component.class)
        String value() default "";
    
        boolean proxyBeanMethods() default true;
    
    }
    

    2.2 @Import注解

    @Import注解 的作用就是导入一些配置类,或者直接创建导入类的实例,被Spring 容器进行管理。

    @import 注解支持三种类型的导入:

    • 导入普通类,这个类的实例将会被生成
    • 指定实现了ImportSelector接口的类,将以自定义的方式导入类
    • 指定实现了ImportBeanDefinitionRegistrar的接口的类,用于个性化加载,但是如果想要修改bean的属性和作用范围,就需要通过这种方式
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Import {
        Class<?>[] value();
    }
    
    

    @Import注解的详细介绍请参考大佬的文章 Spring全解系列 - @Import注解

    2.3 @Conditional注解

    @Conditional注解是一类注解,这类注解定义被修饰的类什么时候被加载,如果定义的条件满足,将会进行加载。比如SpringBoot提供的@ConditionalOnBean 这个注解会定义当容器中存在某个bean时才进行加载。

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Conditional {
        Class<? extends Condition>[] value();
    }
    
    

    如果我们需要实现自定义的条件,可以实现Condition接口,并且覆盖match方法

    @FunctionalInterface
    public interface Condition {
        boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
    }
    

    以下时SpringBoot中提供的注解

    @ConditionalOnBean:仅仅在当前上下文中存在某个对象时,才会实例化一个Bean。
    @ConditionalOnClass:某个class位于类路径上,才会实例化一个Bean。
    @ConditionalOnExpression:当表达式为true的时候,才会实例化一个Bean。
    @ConditionalOnMissingBean:仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean。
    @ConditionalOnMissingClass:某个class类路径上不存在的时候,才会实例化一个Bean。
    @ConditionalOnNotWebApplication:不是web应用,才会实例化一个Bean。
    @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
    @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
    @ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
    @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
    @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
    @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
    @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
    @ConditionalOnExpression:基于SpEL表达式的条件判断。
    @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
    @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
    @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
    @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化
    

    2.4 @AliasFor注解

    @AliasFor注解 主要的作用是别名,在同一个注解中使用,@AliasFor可以定义两个字段含有相同的含义。比如@RequestMapping()中我们可以直接写@RequestMapping("/api") 也可以写@RequestMapping(path = "/api")

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    @Documented
    public @interface AliasFor {
        @AliasFor("attribute")
        String value() default "";
    
        @AliasFor("value")
        String attribute() default "";
    
        Class<? extends Annotation> annotation() default Annotation.class;
    }
    
    

    2.5 @Inherited注解

    @Inherited注解 根据名称就可以得知,表示子类可以继承父类的注解

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }
    
    

    3. 自动配置原理

    有了上面的基础知识的理解后,我们对后续的自动配置原理就会比较轻松了。

    3.1 @SpringBootApplication注解

    每个SpringBoot项目需要在主启动类上加上@SpringBootApplication注解,这个注解时自动配置的核心,它是一个复合注解,点进去看到,下面还有比较重要的三个注解,@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。其中最重要的是@EnableAutoConfiguration注解

    @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 {
        @AliasFor(
            annotation = EnableAutoConfiguration.class
        )
        Class<?>[] exclude() default {};
    
        @AliasFor(
            annotation = EnableAutoConfiguration.class
        )
        String[] excludeName() default {};
    
        @AliasFor(
            annotation = ComponentScan.class,
            attribute = "basePackages"
        )
        String[] scanBasePackages() default {};
    
        @AliasFor(
            annotation = ComponentScan.class,
            attribute = "basePackageClasses"
        )
        Class<?>[] scanBasePackageClasses() default {};
    
        @AliasFor(
            annotation = ComponentScan.class,
            attribute = "nameGenerator"
        )
        Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    
        @AliasFor(
            annotation = Configuration.class
        )
        boolean proxyBeanMethods() default true;
    }
    
    

    3.2 @SpringBootConfiguration

    @SpringBootConfiguration 是个复合注解,主要是@Configuration注解,表示主启动类也是个配置类,也可以配置Bean的相关信息。

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

    3.3 @ComponentScan

    @ComponentScan 可以指定包路径,然后Spring会去扫描指定路径下的类,将其实例化交由Spring容器进行管理。其中还有个Filter子注解,可以进行过滤操作。

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
        @AliasFor("basePackages")
        String[] value() default {};
    
        @AliasFor("value")
        String[] basePackages() default {};
    
        Class<?>[] basePackageClasses() default {};
    
        Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    
        Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
    
        ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
    
        String resourcePattern() default "**/*.class";
    
        boolean useDefaultFilters() default true;
    
        ComponentScan.Filter[] includeFilters() default {};
    
        ComponentScan.Filter[] excludeFilters() default {};
    
        boolean lazyInit() default false;
    
        @Retention(RetentionPolicy.RUNTIME)
        @Target({})
        public @interface Filter {
            FilterType type() default FilterType.ANNOTATION;
    
            @AliasFor("classes")
            Class<?>[] value() default {};
    
            @AliasFor("value")
            Class<?>[] classes() default {};
    
            String[] pattern() default {};
        }
    }
    
    

    3.4 @EnableAutoConfiguration

    @EnableAutoConfiguration是SpringBoot自动配置的核心注解,其中@Import({AutoConfigurationImportSelector.class}) 就是自动配置的核心入口,在基础知识部分我们讲过,@Import({AutoConfigurationImportSelector.class})可以自定义导入规则,主要就是AutoConfigurationImportSelector的selectImports来选择需要自动配置的类进行配置。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    
    

    selectImports 方法中主要是getCandidateConfigurations这个方法,这个方法又会调用SpringFactoriesLoader.loadFactoryNames主要作用是读取spring-boot-autoconfigure.jar包下的META-INF/spring.factories中配置可以进行自动配置的类,这些类都是以JavaConfig形式进行导入的,在满足了@Conditional中定义的加载条件后,Spring会讲这些类加载到IOC容器中。
    org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!this.isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            } else {
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                // 获取注解的元数据信息
                AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
               // 扫描spring.factories配置的自动配置类进行读取
                List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                configurations = this.removeDuplicates(configurations);
                Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
                configurations.removeAll(exclusions);
                configurations = this.filter(configurations, autoConfigurationMetadata);
                this.fireAutoConfigurationImportEvents(configurations, exclusions);
                return StringUtils.toStringArray(configurations);
            }
        }
    

    org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
        }
    

    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 {
                   // 扫描META-INF/spring.factories配置的自动配置类
                    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();
                            List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String)entry.getValue()));
                            result.addAll((String)entry.getKey(), factoryClassNames);
                        }
                    }
    
                    cache.put(classLoader, result);
                    return result;
                } catch (IOException var9) {
                    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var9);
                }
            }
        }
    

    接下来看看,Spring.factories内容是什么,其中定义了Initializers,Listeners,以及Auto Configure。其中关于自动配置的key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,value就是需要自动配置的类,这些类是个集合,以,\进行拼接,并且都是以Configuration结尾的,说明是这些类配置类,在启动中会根据类的配置信息进行加载。Spring.factories定义了哪些类可以被自动配置,每个配置类定了在什么条件下可以被自动配置,并将这些类实例化被Spring容器管理。

    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
    # Auto Configuration Import Listeners
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
    
    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnClassCondition
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
    org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
    org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
    
    .... 省略了很多
    

    这里以AopAutoConfiguration为例子,结合基础知识中的注解可知,当类路径下存在EnableAspectJAutoProxy.class,Aspect.class,Advice.class等类时才会注入AopAutoConfiguration,并且默认是创建CglibAutoProxyConfiguration配置。

    // 申明这是个配置类
    @Configuration
    // 定义加载类的条件
    @ConditionalOnClass({EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class})
    // 定义加载类的配置需要spring.aop.auto=true,缺省值是true
    @ConditionalOnProperty(
        prefix = "spring.aop",
        name = {"auto"},
        havingValue = "true",
        matchIfMissing = true
    )
    public class AopAutoConfiguration {
        public AopAutoConfiguration() {
        }
    
        @Configuration
        @EnableAspectJAutoProxy(
            proxyTargetClass = true
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "true",
            matchIfMissing = true
        )
        public static class CglibAutoProxyConfiguration {
            public CglibAutoProxyConfiguration() {
            }
        }
    
        @Configuration
        @EnableAspectJAutoProxy(
            proxyTargetClass = false
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "false",
            matchIfMissing = false
        )
        public static class JdkDynamicAutoProxyConfiguration {
            public JdkDynamicAutoProxyConfiguration() {
            }
        }
    }
    

    4. 总结

    要是面试官问你:Spring Boot的自动配置原理是什么,你可以这样回答哦:

    • SpringBoot主启动类有个注解是SpringBootApplication,它是个复合注解,主要包含@SpringConfiguration、@ComponentScan、@EnableAutoConfiguration注解,其中@EnableAutoConfiguration注解是自动配置的主要注解。
    • @EnableAutoConfiguration注解也是个复合注解,它主要是导入了AutoConfigurationImportSelecor类,该类的的selectImports方法会把满足条件的类加载到容器中。
    • selectImports方法会调用Spring 核心的loadSpringFactories方法,这个方法会扫描META-INF/spring.factories中配置的自动配置类,然后根据每个配置类的加载条件来判断是否需要加载对应的实例,交由Spring 容器管理。

    相关文章

      网友评论

        本文标题:SpringBoot源码系列(一):深入理解自动配置原理

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