Bootstrap

springboot 自定义starter

一个简单的自定义starter流程记录,不涉及深入讲解。

springboot的优势之一是我们需要使用什么只需要引入一个xx-strater或spring-boot-starter-xx的依赖就行,不需要担心 xx 中需要的依赖。

xx-strater是第三方提供的依赖,如 mybatis-spring-boot-starter

spring-boot-starter-xx是springboot提供的依赖,如spring-boot-starter-data-redis

要自己写一个starter就要先知道springboot是怎么实现自动装配的。

自动装配主要涉及到三个注解,这三个注解都在启动类的@SpringBootApplication注解里

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

 @SpringBootConfiguration 将类标识为配置文件,里面使用了@Configuration注解

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

@ComponentScan 扫描启动类所在包及其子包下的所有@Service,@Controller,@RestController,@Component,@Repository等可以将类表示为bean的注解,这些注解的类会转化为bean,由spring容器管理

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    //....
}

@EnableAutoConfiguration 是自动装配的关键注解,适用于 动态启用某个功能的,底层原理是使用 @Import注解导入一下配置,实现Bean的动态加载

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	/**
	 * Environment property that can be used to override when auto-configuration is
	 * enabled.
	 */
	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 {};

}

springboot自动装配的关键注解是

@EnableAutoConfiguration,该注解上使用

@Import(AutoConfigurationImportSelector.class) 【import有四种实现方式,这是其一】

AutoConfigurationImportSelector实现 ImportSelector重写selectImports()方法,

selectImports()最终返回一个String[],即所需要的所有Bean

疑问:selectImports()方法里如何获取到所需的所有bean呢?

selectImports()中调用了getAutoConfigurationEntry()方法,

getAutoConfigurationEntry()方法内部调用了getCandidateConfigurations()方法,点进去可以看到下面的信息,说明是读取的 META-INF/spring.factories这个文件里面的内容

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.");

META-INF/spring.factories文件里有org.springframework.boot.autoconfigure.EnableAutoConfiguration 这个key,后面的value就是自定义的一个提供Bean的配置文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.custom.customconfig.configuration.CustomConfiguration

所有如果要向自定义一个starter,首先要先自定义一个configuration

前面说了xx-starter这个项目只是整合了所有的依赖,并不涉及方法的实现,所以xx-starter和xx-configuration是两个项目或模块,提供bean是在xx-configuration项目中实现的,最后在xx-starter项目中引入xx-configuration的依赖就行了。

所以第一步,先创建两个module : custom-auto-configuration 和 custom-spring-boot-starter

custom-auto-configuration 就来实现bean的配置文件和spring.factories

custom-auto-configuration项目结构

src/main/java/com/custom/customconfig

        /configuration 

                -- CustomConfiguration.java(配置文件类)

        /entity

                --  OriginFire.java(实体类)

        /properties

                -- CustomRedisProperties.java(属性类)

                -- OriginFireProperties.java(属性类)

src/main/resources

        /META-INF

                -- spring.factories

属性类是啥???--- 读取用户在application.properties或application.yml中配置的属性【因为在创建Bean的时候有些属性值需要根据用户的配置来赋值】

实现代码:

OriginFire.java

@Data
@AllArgsConstructor
public class OriginFire {
    private int degree;
    private String fireName;
    private String location;

}

OriginFireProperties.java 

@Data
@ConfigurationProperties(prefix = "origin.fire")
public class OriginFireProperties {
    private int degree = 1163;
    private String fireName = "tengchong volcano";
    private String location = "tengchong";
}

注意类上使用了@ConfigurationProperties(prefix = "origin.fire"),这个注解允许您将配置外部化并将外部源 (如属性文档) 中的属性绑定到 Java 对象。

说白了就是可以将我们自己在项目的application.properties或application.yml中配置的相对应的属性值绑定到对象上,需要配合@EnableConfigurationProperties注解使用【在CustomConfiguration.java中用到了】

prefix = "origin.fire" 是限定前缀,只读取这个前缀下面的属性,比如有两个degree属性,一个在origin.fire下面origin.fire.degree=1200,另一个是在外部 degree=30,有这个限定前缀,最终获取的值是1200

CustomRedisProperties.java

/**
 * 读取配置文件中的配置信息
 */
@Data
@ConfigurationProperties(prefix = "spring.redis")
public class CustomRedisProperties {
    private String host;
    private int port;

}

CustomConfiguration.java

/**
 * 用于创建bean
 */
@Configuration
@EnableConfigurationProperties({CustomRedisProperties.class, OriginFireProperties.class})
public class CustomConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "jedis")
    public Jedis jedis(CustomRedisProperties properties){
        return new Jedis(properties.getHost(), properties.getPort());
    }
    @Bean
    @ConditionalOnProperty(name = "origin.fire.location")
    public OriginFire originFire(OriginFireProperties properties){
        return new OriginFire(properties.getDegree(), properties.getFireName(), properties.getLocation());
    }

}

 CustomConfiguration.java中使用

注解@Configuration 标注这个类是个配置类,用于提供Bean,这个注解也会使CustomConfiguration.java本身注册为spring bean

注解@EnableConfigurationProperties() 主要是用来吧properties或yml配置文件转化为bean来使用,其作用就是使@ConfigurationProperties注解生效

@Bean 将对象注册为spring bean

@ConditionalOnMissingBean(name = "jedis") 是一个限制是否创建bean的条件,这个注解的限制是只有在 jedis 这个bean不存在的时候才调用这个方法创建bean,如果存在就不再次创建了

@ConditionalOnProperty(name = "origin.fire.location") 也是一个限制是否创建bean的条件,这个注解是限制在properties或yml中必须又origin.fire.location属性才会创建bean,否则不创建

最后在项目的resources下面创建 META-INF/spring.factories, 将自己定义的CustomConfiguration配置进来

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.custom.customconfig.configuration.CustomConfiguration

至此custom-auto-configuration项目中的配置类就写完了

 最后在custom-spring-boot-starter 的pom.xml文件中引入custom-auto-configuration的依赖即可

        <dependency>
            <groupId>com.custom</groupId>
            <artifactId>custom-auto-configuration</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

至此 自定义的starter就完成拉,打好jar包,就可以在其他项目中使用了。

;