210630:springBoot自动配置-自定义start

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了210630:springBoot自动配置-自定义start相关的知识,希望对你有一定的参考价值。

参考技术A

Spring Boot关于自动配置的源码在spring-boot-autoconfigure-x.x.x.x.jar中:

当然,自动配置原理的相关描述,官方文档貌似是没有提及。不过我们不难猜出,Spring Boot的启动类上有一个@SpringBootApplication注解,这个注解是Spring Boot项目必不可少的注解。那么自动配置原理一定和这个注解有着千丝万缕的联系!

@SpringBootApplication是一个复合注解或派生注解,在@SpringBootApplication中有一个注解@EnableAutoConfiguration,翻译成人话就是 开启自动配置 ,其定义如下:

而这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。

这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,如下图所示:

这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。

每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的 条件注解 有如下几项:

以ServletWebServerFactoryAutoConfiguration配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)。

在ServletWebServerFactoryAutoConfiguration类上,有一个@EnableConfigurationProperties注解: 开启配置属性 ,而它后面的参数是一个ServerProperties类,这就是习惯优于配置的最终落地点。

在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties,它的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties负责导入这个已经绑定了属性的bean到spring容器中(见上面截图)。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties类,它与配置文件中定义的prefix关键字开头的一组属性是唯一对应的。

至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。

而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。

可能到目前为止还是有所疑惑,但面试的时候,其实远远不需要回答的这么具体,你只需要这样回答:

Starter可以理解为一个可拔插式的插件,提供一系列便利的依赖描述符,您可以获得所需的所有Spring和相关技术的一站式服务。应用程序只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。用一句话描述,就是springboot的场景启动器。

官方定义的starter命名都是spring-boot-starter-xxx,我们自己定义的一般都 xxx-spring-boot-starter,新建一个ruangh-id-gengerate-spring-boot-starter项目

导入springboot 包spring-boot-starter

定义一个映射配置新的的类IdProperties和LockieProperties,添加注解ConfigurationProperties("machine.id"),我们的配置文件以machine.id开头,比如mathine.id.name=boot-order,另外一个配置是已spring.lockie开头的

IdService里面有个getId方法用来生成自定义的ID

在resources目录下创建META-INF目录,并添加文件spring.factories。在这个文件中配置EnableAutoConfiguration,具体如下:

创建一个模块,在pom文件中增加依赖

启动类上添加注解

配置文件增加配置参数

添加测试类

运行一下查看结果,看到结果是我们想要的,至此我们自定义一个starter就成功了,总结一下就是要创建3个类1个配置文件, xxProperties,xxService,xxAutoConfiguation和spring.factories

Spring boot运行原理-自定义自动配置类

在前面SpringBoot的文章中介绍了SpringBoot的基本配置,今天我们将给大家讲一讲SpringBoot的运行原理,然后根据原理我们自定义一个starter pom。
本章对于后续继续学习SpringBoot至关重要,了解SpringBoot运行原理对于我们深入学习SpringBoot有着非常重要的作用。

SpringBoot的自动配置从何而来

要想了解SpringBoot的自动配置,我们可以在源码看到相关代码和配置。
SpringBoot关于自动配置的源码在spring-boot-autoconfigure-2.1.x.jar内,打开maven依赖我们可以看见

技术图片

如果想了解SpringBoot为我们做了哪些自动配置,可以通过下面方式查看当前项目中已启用和未启用的自动配置的报告。
  1. 运行jar时增加--debug参数:
java -jar xx.jar --debug
  1. 在application.properties中设置属性:
debug=true
启动时,通过控制台我们可以看到哪些配置已使用自动配置,哪些配置没有自动配置。

已启用自动配置
技术图片

未启用自动配置
技术图片

仔细看上图我们可以发现,相关如

@ConditionalOnClass found required class ...    \\   @ConditionalOnClass did not find required class ...

的字眼非常多,可见@ConditionalOnClass注解可能在自动配置中起着主要作用,那究竟是如何起作用的呢?

运行原理

关于SpringBoot的运作原理,我们还是回归到@SpringBootApplication注解上来,这个注解是一个组合注解,它的核心功能是一个开启自动配置注解@EnableAutoConfiguration

下面我们来看下@EnableAutoConfiguration注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default ;

    String[] excludeName() default ;

这里我们重点关注@Import的导入功能,AutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames方法来扫描META-INF/spring.factories文件中描述的jar包,所以我们立马到刚刚打开的自动配置类中的META-INF/spring.factories中找一下是否真的有这样一个文件。

好家伙,还真的有
技术图片

马上打开看一看
技术图片

核心注解

我们打开上面配置的任何其中一个注解,一般都有下面的条件注解,打开源码spring-boot-autoconfigure下的org/springframework/boot/condition看看都有哪些注解

技术图片

简单介绍一下每个注解代表代表的条件:
@ConditionalOnBean: 当容器里有指定的Bean条件下。
@ConditionalOnClass: 当类路径下有指定的类的条件下。
@ConditionalOnExpression: 基于SpEL表达式作为判断条件。
@ConditionalOnJava: 基于JVM版本作为判断条件。
@ConditionalOnjndi: 在基于JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean: 当容器里没有Bean的情况下。
@ConditionalOnMIssingClass: 当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication: 当前项目不是Web项目的条件下。
@ConditionalOnProperty: 指定的属性是否有指定的值。
@ConditionalOnResource: 类路径是否有指定的值。
@ConditionalOnSingleCandidate: 当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean
@ConditionalOnWebApplication: 当前项目是web项目的条件下
这些注解都是组合了@Conditional元注解,只是使用了不同的条件(Condition)
下面我们简单分析一下@ConditionalOnWebApplication注解。
@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication 
    ConditionalOnWebApplication.Type type() default ConditionalOnWebApplication.Type.ANY;

    public static enum Type 
        ANY,
        SERVLET,
        REACTIVE;

        private Type() 
        
    

看看OnWebApplicationCondition.class 是如何定义条件的

这里我们主要看isWebApplication方法,判断条件主要如下:
(1)GenericWebApplicationContext是否在类路径中;
(2)容器中是否有名为session的scope;
(3)当前容器的Environment是否为ConfigurableWebEnvironment
(4)当前的ResourceLoader是否为WebApplicationContext(ResourceLoader是ApplicationContext的顶级接口之一);
(5)构造ConditionOutcom类的isMatch方法返回布尔值来确定条件。

==这里的isWebApplication()方法,spring-boot-1.x和2.x有区别,在1.x中只判断了servlet,而在2.x增加了了reative和any的webapplication判断==

简单示例

在以往的web项目中,通常需要在web.xml中配置一个filter,对请求进行编码,如下所示:
<filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
因此如果要实现自动配置的话,需要满足一下条件:
(1)能配置CharacterEncodingFilter这个Bean;
(2)能配置encoding和forceEncoding这两个参数。

参数配置

在上一章我们讲到了类型安全的配置,Spring Boot的自动配置也是基于这一点实现的,这里的配置可以在application.properties中直接配置。
源码如下图:

技术图片

技术图片

代码解释:

(1)在application.properties配置的前缀是spring.http.encoding;
(2)默认编码方式为UTF-8,若修改可配置spring.http.encoding.charset=编码;
(3)设置force,默认为true,若修改可配置spring.http.encoding.force=false;

配置Bean

上面我们已经配置好相关参数,现在根据条件配置CharacterEncodingFilter的Bean,下面看看源码:

@Configuration
@EnableConfigurationProperties(HttpProperties.class) //1
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass(CharacterEncodingFilter.class)//2
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = "enabled",
    matchIfMissing = true
)//3
public class HttpEncodingAutoConfiguration 
    private final Encoding properties;

    public HttpEncodingAutoConfiguration(HttpProperties properties) 
        this.properties = properties.getEncoding();
    

    
    @Bean//4
    @ConditionalOnMissingBean//5
    public CharacterEncodingFilter characterEncodingFilter() 
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    

代码解释:
(1)开启属性注入,通过@EnableCondigurationProperties声明
(2)当CharacterEncodingFilter在类路径的条件下;
(3)当设置spring.http.encoding.enabled的情况下,如果没有设置则默认为true,即条件符合;
(4)像使用Java配置的方式配置CharacterEncoding这个Bean;
(5)当容器中没有这个Bean的时候新建Bean

实战-自定义一个starter pom

前面我们已经详细讲述了spring boot是如何实现自动化配置的,现在我们来动手自己写一个starter pom实现自动化配置。

要求:当某个类存在的时候,自动配置这个类的Bean,并可将Bean的属性在application.properties中配置

创建一个普通MAVEN工程

创建一个普通MAVEN工程,并加入spring boot自动配置依赖

技术图片

属性配置

我们仿照HttpProperties来配置,我们自定义starter 的配置文件
@ConfigurationProperties(prefix = "xicent.service")
public class MyServiceProperties 
    private MyServiceProperties.MyProperties myProperties = new MyServiceProperties.MyProperties();

    public MyProperties getMyProperties() 
        return myProperties;
    

    public void setMyProperties(MyProperties myProperties) 
        this.myProperties = myProperties;
    

    public static class MyProperties
        public static final String DEFAULT_NAME;
        private String author;
        private String age;
        
        static 
            DEFAULT_NAME = "wjx";
        
        
    省略 get/set..

使用类型安全的方式获取属性。author如果不设置,会给默认值。

判断依据类

public class MyService 
    private String name;
    private String age;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public String getAge() 
        return age;
    

    public void setAge(String age) 
        this.age = age;
    

这个类作为我们的判断依据类,如果存在,则创建这个类的Bean。

自动配置类(关键)


@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "xicent.service",value = "enabled",matchIfMissing = true)
public class MyServiceAutoConfiguration 
    private MyServiceProperties.MyProperties properties;

    public MyServiceAutoConfiguration(MyServiceProperties properties) 
        this.properties = properties.getMyProperties();
    
    
    @Bean
    @ConditionalOnMissingBean(MyService.class)
    public MyService myService()
        MyService myService = new MyService();
        myService.setName(properties.getAuthor());
        myService.setAge(properties.getAge());
        
        return myService;
    

这里我们仿照了HttpEncodingAutoConfiguration的写法,其实MyServiceProperties也可以直接用@Autowired直接注入的。
@ConditionalOnClass判断MyService这个类是否在类路径中存在,并且容器中没有这个Bean的情况下,我们对这个Bean进行自动配置。

注册配置

在前面我们也带大家看过,每个自动配置的包中,在src/main/resources/META-INF下都会有一个spring.factories配置文件。
下面我们就将我们刚刚写好的自动配置类,在这个配置文件中进行注册。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=  com.xicent.starter.config.MyServiceAutoConfiguration
如果有多个自动配置,用","隔开,此处的"\\"是为了换行后仍能读到属性。

添加在仓库

如果是公司提供给其他项目使用,则可以直接上传到公司私服。这里为了方便测试,我们就打包到本地仓库。
直接点击idea->maven project->lifecycle->install

技术图片

新建Spring Boot项目加入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.xicent</groupId>
            <artifactId>mystarter</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

mystarter是我们刚刚手动写好的自动配置,加入web是为了待会用接口访问,方便测试。

添加配置

刚刚我们的自动配置中允许配置两个参数,其中author如果不配置,提供默认值。
xicent.service.my-properties.age=23
xicent.service.my-properties.author=kris

注入依赖

@SpringBootApplication
@RestController
public class TeststarterApplication 

    @Autowired
    MyService myService;

    @GetMapping("/")
    String testStarter()
        return myService.getName()+":"+myService.getAge();
    

    public static void main(String[] args) 
        SpringApplication.run(TeststarterApplication.class, args);
    


访问接口

技术图片

如果不配置author
技术图片

到这里,我们自定义的starter pom就大功告成啦~ 是不是感觉其实挺简单的,Spring Boot自动配置的神秘面纱也就被我们悄悄揭开了。

如果您觉得有用记得分享喔~
公众号搜索:喜讯XiCent 获取更多福利~


公众号搜索:喜讯XiCent 获取更多福利资源~

以上是关于210630:springBoot自动配置-自定义start的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 自动配置覆盖自定义(elasticsearchTemplate)配置

Spring boot运行原理-自定义自动配置类

自定义的Spring Boot starter如何设置自动配置注解

springboot自定义配置应用详解

Spring Boot自定义配置实现IDE自动提示

嵌入式Servlet容器自动配置启动自定义配置原理