美文网首页
SpringBoot 2.2.5 自动装配分析及手动实现

SpringBoot 2.2.5 自动装配分析及手动实现

作者: dgatiger | 来源:发表于2020-03-20 17:20 被阅读0次

    1.新建sprigboot项目

    项目pox.xml如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.5.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.demo.springboot</groupId>
        <artifactId>autoconfig-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>autoconfig-demo</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    

    除了项目的GAV之外都是默认的。

    2.自撸基本思路

    SpringBoot启动类,默认是@SpringBootApplication注解,而该注解主要有三个注解构成,即:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。

    各注解的作用参考官方文档"3.6. Using the @SpringBootApplication Annotation"

    参考对标@EnableAutoConfiguration注解,我们可以理出如下思路:

    • 该注解首先通过@Import导入AutoConfigurationImportSelector.class
    @Import(AutoConfigurationImportSelector.class)
    
    • AutoConfigurationImportSelector的核心函数selectImports调用getAutoConfigurationEntry,返回的是String[]。
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                    annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    
    • 跟进getAutoConfigurationEntry函数,核心调用的是getCandidateConfigurations。
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return EMPTY_ENTRY;
            }
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
            List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    
    
    • 继续跟进getCandidateConfigurations,核心调用的是SpringFactoriesLoader.loadFactoryNames。
        /**
         * Return the auto-configuration class names that should be considered. By default
         * this method will load candidates using {@link SpringFactoriesLoader} with
         * {@link #getSpringFactoriesLoaderFactoryClass()}.
         * @param metadata the source metadata
         * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
         * attributes}
         * @return a list of candidate configurations
         */
        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            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.loadFactoryNames,loadFactoryNames调用楼下的loadSpringFactories,返回的结果再链式调用getOrDefault,真正干活的出现了,就是loadSpringFactories!
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
            String factoryTypeName = factoryType.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 {
                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);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryTypeName = ((String) entry.getKey()).trim();
                        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }
    

    这里重点先看下面代码,即若classLoader参数不为空,就是说有传类加载器进来,而在本例中其实就是AutoConfigurationImportSelector的类加载器。

    Enumeration<URL> urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    

    若有传如类加载器进来就获得本类所在的资源文件,否则获得系统的资源文件,即"Finds all resources of the specified name from the search path used to
    load classes."
    本例中传入的是org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.class的类加载器,所以自然就是找该类所在的包里的资源文件。

    常量FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"

    这样就真相大白了,原来springboot的autoconfigure,其实就是让<font color=red>SpringFactoriesLoader去加载指定类所在位置的资源文件!</font>

    接下来我们看看org.springframework.boot.autoconfigure包里的spring.factories文件

    # 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.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
    
    # 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,\
    中间省略N行...总共有100+行
    org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
    org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
    org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
    
    # Failure analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=\
    org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
    org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
    
    # Template availability providers
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
    

    特别注意# Auto Configure部分有个

    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider
    

    的键,说明这个键跟后面100多个类有关联。

    接着看代码,发现loadSpringFactories将将配置文件加载进来,生成一个MultiValueMap,放入缓存并返回。

    接着看链式调用的getOrDefault函数,逻辑简单说就是以传入的类名为参数,返回的MultiValueMap类型变量中存在以本类名为键值的就返回对应的类List,否则返回空List。

    最终,这个List返回给了selectImports,然后交由org.springframework.beans.factory.support.DefaultListableBeanFactory通过反射生成一个又一个的单例对象,类的自动扫描装配到此结束。

    3.依葫芦画瓢

    3.1 建立自动装配注解@DemoEnableAutoConfig
    package com.demo.springboot.autoconfigdemo.autoconfig;
    
    import org.springframework.context.annotation.Import;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author : Alex Hu
     * date : 2020/3/20 下午13:27
     * description : 自定义个注解,模拟springboot的@EnableAutoConfiguration
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(value = {AutoConfigurationImportSelector.class})
    public @interface DemoEnableAutoConfig {
    }
    
    3.2 实现AutoConfigurationImportSelector.class
    package com.demo.springboot.autoconfigdemo.autoconfig;
    
    import org.springframework.context.annotation.ImportSelector;
    import org.springframework.core.io.support.SpringFactoriesLoader;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.util.Assert;
    import org.springframework.util.StringUtils;
    
    import java.util.List;
    
    /**
     * @author : Alex Hu
     * date : 2020/3/20 上午08:55
     * description : springboot 自动装配方式,从properties文件批量导入
     */
    public class AutoConfigurationImportSelector implements ImportSelector {
        //返回注解的class, META-INF/spring.factories 中的键名
        private Class<?> getSpringFactoriesLoaderFactoryClass() {
            return DemoEnableAutoConfig.class;
        }
    
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            List<String> configurations = getCandidateConfigurations();
            System.out.println("configurations: " + configurations);
            return StringUtils.toStringArray(configurations);
        }
    
    
        protected List<String> getCandidateConfigurations() {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                    //返回注解类的class
                    getSpringFactoriesLoaderFactoryClass(),
                    //当前类的classloader
                    this.getClass().getClassLoader()
            );
            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;
        }
    
    }
    
    
    3.3 建立配置文件

    文件位于resources/META-INF/目录,文件名spring.factories

    com.demo.springboot.autoconfigdemo.autoconfig.DemoEnableAutoConfig=\
    com.demo.springboot.autoconfigdemo.classes.ClassA,\
    com.demo.springboot.autoconfigdemo.classes.ClassB
    

    表示扫描ClassA与ClassB这两个类

    3.4 修改启动程序
    package com.demo.springboot.autoconfigdemo;
    
    import com.demo.springboot.autoconfigdemo.autoconfig.DemoEnableAutoConfig;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.ComponentScan;
    
    /**
     * @author Alex Hu
     */
    @DemoEnableAutoConfig
    //@ComponentScan
    public class AutoconfigDemoApplication {
    
        public static void main(String[] args) {
          SpringApplication.run(AutoconfigDemoApplication.class, args);
        }
    
    }
    
    
    3.5 检查成果

    运行启动程序,结果如下:

      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.2.5.RELEASE)
    
    2020-03-20 17:04:50.745  INFO 18192 --- [           main] c.d.s.a.AutoconfigDemoApplication        : Starting AutoconfigDemoApplication on huqiupingdeMacBook-Pro.local with PID 18192 (/Users/alexhu/IdeaProjects/autoconfig-demo/target/classes started by alexhu in /Users/alexhu/IdeaProjects/boot-demo)
    2020-03-20 17:04:50.747  INFO 18192 --- [           main] c.d.s.a.AutoconfigDemoApplication        : No active profile set, falling back to default profiles: default
    configurations: [com.demo.springboot.autoconfigdemo.classes.ClassA, com.demo.springboot.autoconfigdemo.classes.ClassB]
    ClassA.ClassA  构造器被执行
    ClassB.ClassB  构造器被执行
    2020-03-20 17:04:50.863  INFO 18192 --- [           main] c.d.s.a.AutoconfigDemoApplication        : Started AutoconfigDemoApplication in 0.5 seconds (JVM running for 0.92)
    
    Process finished with exit code 0
    

    目标两个类的构造函数被执行,说明spring帮我们实例化了这两个类。

    3.6 题外话

    若本例中的spring.factories,配置中的键直接修改为:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration
    

    启动程序的注解直接用系统自带的@EnableAutoConfiguration,又会是什么结果?大家可以先猜,猜完可以再试 ^ _^

    其实SpringBoot的自动装配核心内容并不复杂,调试跟进源代码是法宝。
    本演示项目源代码在这里

    相关文章

      网友评论

          本文标题:SpringBoot 2.2.5 自动装配分析及手动实现

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