• SpringBoot自动装配原理


    springboot的自动装配是starter的基础,简单来说,就是将Bean装配到Ioc。

    本文我们先学习redis的starter如何实现自动装配,然后手写一个redis的starter的,来学习spring如何通过starter实现自动装配。

    一、学习spring-boot-starter-data-redis如何实现自动装配

    首先,新建一个springboot项目,添加starter依赖

    compile("org.springframework.boot:spring-boot-starter-data-redis")

    在yml中添加redis数据源:

      redis:
        database: 8
        host: 127.0.0.1
        #    password:
        port: 6379
        timeout: 1000ms
        lettuce:
          pool:
            max-active: 20
            min-idle: 1
            max-idle: 8
            max-wait: 10000

    编写一个controller,测试

    /**
     * @author cgg
     * @version 1.0
     * @date 2021/4/1
     */
    @RestController
    @Slf4j
    @Api(tags = "测试")
    @RequestMapping("test")
    public class TestController {
    
        @Resource
        private RedisTemplate redisTemplate;
    
        @GetMapping("/")
        @ApiOperation("测试")
        public void helloWorld() {
            System.out.println("hello world");
        }
    }

    可以看到,在项目中,我们并没有使用注解或者xml将redisTemplate注入到Ioc容器中就可以使用,说明容器中已经存在了,其实这就是springBoot的自动装配。

    其实springboot 通过一个starter依赖就能实现自动装配,是starter遵守了约定规范,才实现了自动装配,下面我们就学习一下原理,并学习starter的规范,为我们手写自己的starter做准备。

    springboot实现自动装配是通过 @SpringBootApplication 注解中的 @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 {
    
    .......
    }

    接下来,再看 @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 {};
    
    }

    其中使用了@import 导入了 AutoConfigurationImportSelector类,那我们我们继续往下看 AutoConfigurationImportSelector 实现了 DeferredImportSelector,而DeferredImportSelector实现了ImportSelector

    其中的重写了selectImports ,返回了一个String【】数组,spring把返回的数组中的类名全部装配到容器中。继续看AutoConfigurationImportSelector代码。

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
            ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    
        private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
    
        private static final String[] NO_IMPORTS = {};
    
        private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    
        private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    
        private ConfigurableListableBeanFactory beanFactory;
    
        private Environment environment;
    
        private ClassLoader beanClassLoader;
    
        private ResourceLoader resourceLoader;
    
        private ConfigurationClassFilter configurationClassFilter;
    
        @Override
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); //可以看到 autoConfigurationEntry.getConfigurations()才是需要装配的类名称数组。那么就需要查看 getAutoConfigurationEntry(annotationMetadata)
    } ......

    继续跟到getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) 方法中

        protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }

    由于调用栈过深,我说下源码的调用链:getCandidateConfigurations  ->  loadFactoryNames ->  loadSpringFactories  ->  

    在loadSpringFactories 可以发现下面的代码

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

    而 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 接下来我们再看一下项目启动后,getCandidateConfigurations返回值。

    这里看到第一个返回值是 MybatisPlusAutoConfiguration 那么他是怎么来的呢,再看下一张图

     相信看到这里,大多数同学已经明白了,其实就是获取了  每一个 starter 的 "META-INF/spring.factories" 中声明类全名。根据org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为Key获取value作为返回值。

     然后再说一下getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) 其他方法的含义:

     getAttributes获得@EnableAutoConfiguration注解中的exclude 、 excludeName等

     getCandidateConfigurations获取自动装配的配置类,重点。

     removeDuplicates去除重复的配置项。

     getExclusions根据 @EnableAutoConfiguration配置的exclude将不需要的配置类移除。

     最后对自动装配的核心流程做一个总结:

      1、通过@Import(AutoConfigurationImportSelector) 实现配置类的导入,

      2、AutoConfigurationImportSelector实现了ImportSelector,重写了selectImport用来实现批量导入

      3、通过Spring的SpringFactoriesLoader机制,扫描"META-INF/spring.factories" 路径下的配置类,实现自动装配。

      4、通过条件筛选,把不符合条件的配置类移除,最终完成自动装配。

    整体来看,springboot的自动装配完美的体现了:约定由于配置。

    另外,还有一个文件需要注意:

    上述文件的作用和@condition作用是一样的,只是将条件配置类放置了文件中,原理和上面的自动装配配置类一样,好处在于降低了springboot得启动时间,因为这个文件的装配发生在配置类装载之前。同样,这也是“约定由于配置”的体现。

    二、手写一个spring-boot-starter-cgg-redis

    接下来,我们手写一个starter。通过上述分析,stater主要具有三点功能:

         1、实现相关组件的Jar依赖

         2、自动装配Bean

         3、自动声明并且加载application.properties中的配置

    我们实现的是一个基于redis简化版的stater,主要是想通过动手操作学习自动装配的原理。

    1、新建一个项目,命名为 spring-boot-starter-cgg-redis,添加Jar包依赖,我们使用Redisson来实现。添加redisson依赖。

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.16.0</version>
    </dependency>

    2、定义属性类,获取application.properties的属性

    @Data
    @ConfigurationProperties(prefix = "cgg.redisson")
    public class RedissonProperties {
        private String host = "localhost";
        private String password;
        private int port = 6379;
        private int timeout;
        private boolean ssl;
    
    }

    3、编写自动装配的配置类,会将RedissonClient装配到Ioc容器中

    @Configuration
    @ConditionalOnClass(Redisson.class)
    @EnableConfigurationProperties(RedissonProperties.class)
    public class RedissonAutoConfiguration {
        @Bean
        public RedissonClient redissonClient(RedissonProperties redissonProperties) {
            Config config = new Config();
            String prefix = "redis://";
            if (redissonProperties.isSsl()) {
                prefix = "rediss://";
            }
            SingleServerConfig singleServerConfig = config.useSingleServer().setAddress(prefix + redissonProperties.getHost() + ":" + redissonProperties.getPort())
                    .setConnectTimeout(redissonProperties.getTimeout());
    
            if (ObjectUtils.isEmpty(redissonProperties.getPassword())) {
                singleServerConfig.setPassword(redissonProperties.getPassword());
            }
            return Redisson.create(config);
        }
    }

    4、在项目resources下META-INF/spring.factories文件,文件内容为:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
      cgg.redisson.propieties.RedissonAutoConfiguration

    5、最后已经将我们的项目打成Jar,然后别的项目就可以使用我们的spring-boot-starter-cgg-redis,需要添加依赖

    <dependency>
        <groupId>cgg.redisson</groupId>
        <artifactId>spring-boot-stater-cgg-redis</artifactId>
        <version>1.0.0</version>
    </dependency>

    最后再项目的依赖文件application.properties中添加配置:

    cgg.redisson.host=127.0.0.1
    cgg.redisson.port=6379

    启动项目,发现无需配置redissonClient也可以直接使用,说明通过我们的stater自动装配 Ioc容器中已经有了RedissonClient.

    至此,手写一个stater就完成了,相信看到这里,对SpringBoot的自动装配又有了一定的学习。

  • 相关阅读:
    Python 面向对象
    pymysql增删改查
    pymysql简单链接示例
    mysql权限管理
    mysql五补充部分:SQL逻辑查询语句执行顺序
    mysql安装与基本管理
    mysql索引原理与慢查询优化2
    ftp 服务
    Linux 并发链接数
    Linux
  • 原文地址:https://www.cnblogs.com/wa1l-E/p/14945459.html
Copyright © 2020-2023  润新知