springboot自动装配之自定义starter

10,329 阅读4分钟

Spring Boot 自动配置,顾名思义,是希望能够自动配置,将我们从配置的苦海中解脱出来。那么既然要自动配置,它需要解三个问题:

1、满足什么条件?
2、创建那些bean?
3、创建的bean都有那些属性?
举个例子:当我们创建一个springboot项目,引入spring-boot-starter-web依赖,会自动给我们创建一个8080端口的tomcat,同时通过配置文件application.yaml配置项目的自定义端口(server.port)。

简单说明下:
1、什么样的条件?因为我们引入spring-boot-starter-web依赖。
2、创建了哪些bean?创建了一个内嵌的tomcat并且启动。
3、创建的bean有哪些属性?通过application.yaml配置了自定义端口(server.port)。

springboot源码中有个这样的类:EmbeddedWebServerFactoryCustomizerAutoConfiguration
下载源码去码云(快的一批)

这个类主要的目的就是负责创建内嵌的服务器,目前包括tomcat/netty/Jetty/Undertow四种服务器。
通过在类上添加 @Configuration 注解,声明这是一个 Spring 配置类。
通过在方法上添加 @Bean 注解,声明该方法创建一个 Spring Bean。

<1.1> 处,在类上添加了@Configuration注解,声明这是一个配置类。因为它的目的是自动配置,所以类名以 AutoConfiguration 作为后缀。
<1.2>处,是用于初始化 Tomcat的配置类。

如此,我们可以得到结论一,通过 @Configuration 注解的配置类,可以解决“创建哪些 Bean”的问题。

<2.1>在类上添加了 @ConditionalOnWebApplication条件注解,表示当前配置类需要在当前项目是 Web 项目的条件下,才能生效。在SpringBoot项目中,会将项目类型分成Web 项目(使用 SpringMVC或者WebFlux)和非Web项目。这样我们就很容易理解,为什么EmbeddedWebServerFactoryCustomizerAutoConfiguration 配置类会要求在项目类型是 Web 项目,只有 Web项目才有必要创建内嵌的Web 服务器呀。

@ConditionalOnClass条件注解,表示当前配置类需要在当前项目有指定类的条件下,才能生效。(上图1.2下面那一行)。

如此,我们可以得到结论二,通过条件注解,可以解决“满足什么样的条件?”的问题。

<3.1> 处,使用 @EnableConfigurationProperties 注解,让 ServerProperties 配置属性类生效。在 SpringBoot定义了@ConfigurationProperties注解,用于声明配置属性类,将指定前缀的配置项批量注入到该类中。

<3.2>处,在创建 TomcatWebServerFactoryCustomizer对象时,都会将ServerProperties传入其中,作为后续创建的Web服务器的配置。也就是说,我们通过修改在配置文件的配置项,就可以自定义 Web 服务器的配置。

如此,我们可以得到结论三,通过配置属性,可以解决“创建的 Bean 的属性?”的问题。

目前为止我们已经比较清晰的理解SpringBoot是怎么解决我们上面提出的三个问题,但是这样还是无法实现自动配置。例如说,我们引入的spring-boot-starter-web等依赖SpringBoot是怎么知道要扫码哪些配置类的。

在我们通过 SpringApplication#run(Class<?> primarySource, String... args) 方法,启动 Spring Boot 应用的时候,有个非常重要的组件 SpringFactoriesLoader 类,会读取 META-INF 目录下的 spring.factories 文件,获得每个框架定义的需要自动配置的配置类。

如此,原先 @Configuration注解的配置类,就升级成类自动配置类。这样,SpringBoot 在获取到需要自动配置的配置类后,就可以自动创建相应的Bean,完成自动配置的功能。

Spring Boot 约定读取application.yaml、application.properties等配置文件,从而实现创建 Bean的自定义属性配置,甚至可以搭配 @ConditionalOnProperty 注解来取消 Bean 的创建。

自定义starter实现

起一个项目,两面再起两个module

module1用来提供starter,module2用来使用starter。(最后打印出的端口号看我们是否引用成功)

这个是配置的属性类,也就是读取application.properties中以lmd.server为前缀的属性(注意,这个application.properties是你在module2的配置文件)。

下面这个配置自动装配类

然后在module2的pom.xml文件引入module1的依赖。

接着在module2的application.properties定义属性(我们设置端口为8011)。

最后一步,直接运行module2的启动类。

最后看成功引用到了自定义starter。

注:module1的pom文件

4.0.0 com.example demo 0.0.1-SNAPSHOT demo Demo project for Spring Boot

<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.3.0.RELEASE</spring-boot.version>
</properties>

<dependencies>
    <!-- 引入 Spring Boot Starter 基础库 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.1.8.RELEASE</version>
    </dependency>
</dependencies>

module2的pom文件

4.0.0 com.springboot stater 0.0.1-SNAPSHOT stater Demo project for Spring Boot

<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>