美文网首页
实现自动装配的一个小demo及其原理

实现自动装配的一个小demo及其原理

作者: guessguess | 来源:发表于2020-07-18 12:40 被阅读0次

    首先说一下情景,很多时候我们可能会做一些小组件,如果不使用自动装配的话,那么是有点麻烦的,需要手动指定扫描的路径,才可以交给spring去做处理。那么问题来了,有没有一种比较简单的方式可以做到,不用去指定扫描路径,仅仅通过添加maven的依赖就解决了呢。那就是通过spring的自动装配机制。

    首先先创建一个工程,作为被依赖的组件,后续再创建一个工程使用这个组件。

    组件工程的结构如下图所示


    截屏2020-07-18上午11.25.49.png

    maven依赖

        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.5.RELEASE</version>
        </parent>
        
        <dependencies>
            <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
        </dependencies>
    

    配置类代码

    @Configuration
    @ComponentScan
    public class Configutation {
    
    }
    

    服务的代码

    @Service
    public class HelloService {
        public String sayHello() {
            return "auto-config hello";
        }
    }
    

    随后在resource目录下创建MATE-INF目录,并且放置一个spring.factories文件
    spring.factories的内容,其实就是我们自定义需要被自动装配的配置类

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    auto.Configutation
    

    maven install一下

    随后我们来创建一个工程,用于测试这个组件是否可以使用
    首先添加依赖

    <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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.gee</groupId>
        <artifactId>sb-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.5.RELEASE</version>
        </parent>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <!-- 开启热部署 -->
            <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional>
            </dependency>
    
            <!-- 单元测试 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!-- 自定义的组件-->
            <dependency>
                <groupId>com.gee</groupId>
                <artifactId>my-auto-configuration</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <!-- 指定maven编译的jdk的版本 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            
    
                <!-- 打包成springboot专用的jar包,指定入口信息等等 -->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
    
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <skipTests>true</skipTests>
                    </configuration>
                </plugin>
    
            </plugins>
        </build>
    </project>
    

    配置类

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

    获取bean的一个工具类,必须位于配置类的包/子包路径下,因为没有指定ComponentScan的包路径,默认就是配置类的包/子包路径下

    @Component
    public class ApplicationContextUtils implements ApplicationContextAware{
        private ApplicationContext context;
        public static ApplicationContext APP_CONTEXT;
        public <T> T getBean(Class<T> t) {
            return context.getBean(t);
        }
        
        public String[] getBeanNames() {
            return context.getBeanDefinitionNames();
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.context = applicationContext;
            APP_CONTEXT = applicationContext;
        }
    
        public ApplicationContext getContext() {
            return context;
        }
    
        public void setContext(ApplicationContext context) {
            this.context = context;
        }
    }
    

    在src/test/java下,创建一个包,顺便写一个测试的启动类,以及对应的测试方法,测试一下

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes={Config.class})
    public class ConfigTest {
        @Test
        public void getProducer() {
            System.out.println(ApplicationContextUtils.APP_CONTEXT.getBean(HelloService.class).sayHello());
    
        }
    }
    

    输出结果如下

    auto-config hello
    2020-07-18 11:38:50.332  INFO 4859 --- [      Thread-10] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@306e95ec: startup date [Sat Jul 18 11:38:22 CST 2020]; root of context hierarchy
    2020-07-18 11:38:50.342  WARN 4859 --- [      Thread-10] o.s.b.f.support.DisposableBeanAdapter    : Invocation of destroy method failed on bean with name 'rocketMQTemplate': java.lang.IllegalStateException: Shutdown in progress
    2020-07-18 11:38:50.342  WARN 4859 --- [      Thread-10] o.s.b.f.support.DisposableBeanAdapter    : Destroy method 'shutdown' on bean with name 'defaultMQProducer' threw an exception: java.lang.IllegalStateException: Shutdown in progress
    

    说明这种用法是可行的。
    那么下面来说说原理。这里就不说的太详细了,下面是之前对自动装配原理的一些分析
    https://www.jianshu.com/p/3ae65b2b4087
    https://www.jianshu.com/p/f6966f2c7d8d

    那么为什么组件中的spring.factories必须得以org.springframework.boot.autoconfigure.EnableAutoConfiguration作为key呢?
    答案也是很简单的。spring给自动装配定义了好几种类型,而org.springframework.boot.autoconfigure.EnableAutoConfiguration是其中一种类型。

    先来看看流程吧。


    截屏2020-07-18上午11.53.21.png

    从流程我们可以看出,入口还是在容器刷新的时候,通过执行beanDefinitionRegistryPostProcesor,完成自动装配。

    那么我们看看执行的关键代码。注释中,会讲解每个步骤的大致作用。

    class ConfigurationClassParser {
        private void processDeferredImportSelectors() {
            //如果成员变量中的导入选择器为null,则说明不需要进行处理
            //成员中的导入选择器其实是在处理@import标签时,判断如果是importSelector类型,则会放置到成员变量中。
            //@SpringBootApplication->@EnableAutoConfiguration->@Import(AutoConfigurationImportSelector.class)
            //所以实际上,这里的导入选择器只会是AutoConfigurationImportSelector
            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            if (deferredImports == null) {
                return;
            }
            //先进行排序
            deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
            //这里其实就是组->组对应的导入选择器,grouping封装了group以及List<ImportSelector>
            Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
            Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
            //根据importSelector中的group进行分组
            for (DeferredImportSelectorHolder deferredImport : deferredImports) {
                //获取该导入选择器的组
                Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
                //若该分组不存在则进行创建
                DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent(
                        (group != null ? group : deferredImport),
                        key -> new DeferredImportSelectorGrouping(createGroup(group)));
                //将该导入选择器加入到对应的分组中
                grouping.add(deferredImport);
                configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getConfigurationClass());
            }
            //遍历执行每个分组,由于这里只有AutoConfiurationImportSelector所以我们只关注这个类即可。
            for (DeferredImportSelectorGrouping grouping : groupings.values()) {
                //获取需要自动装配的所有类,逐个去处理,那么如何获取则是关键。
                grouping.getImports().forEach(entry -> {
                    ConfigurationClass configurationClass = configurationClasses.get(entry.getMetadata());
                    try {
                        //处理自动装配的类,这里其实就是对自动装配的配置类进行处理,与处理@import的方法是一致的,感兴趣可以往里面看看。
                        processImports(configurationClass, asSourceClass(configurationClass),
                                asSourceClasses(entry.getImportClassName()), false);
                    }
                    catch (BeanDefinitionStoreException ex) {
                        throw ex;
                    }
                    catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to process import candidates for configuration class [" +
                                configurationClass.getMetadata().getClassName() + "]", ex);
                    }
                });
            }
        }
    }
    

    如何获取自动装配的类,定位到最后,与预期一致AutoConfigurationGroup,AutoConfigurationImportSelector是核心

    public class AutoConfigurationImportSelector
            implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
            BeanFactoryAware, EnvironmentAware, Ordered {
    
          private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
                BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
    
            @Override
            //AutoConfigurationGroup对应的importSelector只有AutoConfigurationImportSelector,所以看看AutoConfigurationImportSelector的selectImports方法即可。
            public void process(AnnotationMetadata annotationMetadata,
                    DeferredImportSelector deferredImportSelector) {
                String[] imports = deferredImportSelector.selectImports(annotationMetadata);
                for (String importClassName : imports) {
                    this.entries.put(importClassName, annotationMetadata);
                }
            }
    }
    

    AutoConfigurationImportSelector如何找到自动装配的类,看下面方法

        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                AnnotationAttributes attributes) {
            //获取配置类的方法,获取key=EnableAutoConfiguration的className对应的所有配置类
            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;
        }
        
        //这个方法其实就帮我们指定了,我们要获取spring.factories内容中key为EnableAutoConfiguration的className的内容
        protected Class<?> getSpringFactoriesLoaderFactoryClass() {
            return EnableAutoConfiguration.class;
        }
    

    SpringFactoriesLoader的工作原理

        //这个方法其实就是加载所有的spring.factories,获取所有的文件内容,并且生成一个map,然后根据传进来的 factoryClassName作为key,取factoryClassName对应的values,我们刚刚传进来的factoryClassName不就是org.springframework.boot.autoconfigure.EnableAutoConfiguration吗?这个key也跟我们自定义的spring.factories的key一样,所以key是必须使用spring这里规定的.如果我们自己随意定义一个key,则并不会被spring处理。
        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    
        //这个类就是加载meta-inf下的spring.factories的内容,并且生成map
        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()) {
                        List<String> factoryClassNames = Arrays.asList(
                                StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                        result.addAll((String) entry.getKey(), factoryClassNames);
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }
    

    相关文章

      网友评论

          本文标题:实现自动装配的一个小demo及其原理

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