手写spring-boot-starter-data-redis

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的自动装配又有了一定的学习。

 

posted @ 2021-06-28 17:41  coffeebabe  阅读(690)  评论(0编辑  收藏  举报