Springboot自定义自动配置类

614 阅读19分钟

Springboot自定义配置类.mmap

Springboot自定义配置类.docx

前言

由于笔记是由MindManager转过来的,所以可能会有点乱,可以直接看上面的 mmap 脑图

Springboot自定义配置类

2.7.x以前的版本

相关知识

  • 查看官方地址
    docs.spring.io/spring-boot…
    • 什么 是SPI?
      • 是什么?
        • 简单点:根据接口找到实现类
        • 全名叫Service Provider Interface
        • 是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
      • 怎么玩?
        • 创建META-INF/services
        • 以完整接口名为文件名
          • image.png
        • 以所有需要被根据接口找到的实现类完整类全明
        • 多个实现类的话,每个实现类一行
          • 内容
com.vivo.study.spidemo.spi.impl.ImageHello
com.vivo.study.spidemo.spi.impl.TextHello 
     -  使用ServiceLoader加载所有该接口的实现类 
package com.vivo.study.spidemo.test;
import java.util.ServiceLoader;
import com.vivo.study.spidemo.spi.HelloSPI;
public class SPIDemo {
    public static void main(String[] args) {
        ServiceLoader<HelloSPI> serviceLoader = ServiceLoader.load(HelloSPI.class);
// 执行不同厂商的业务实现,具体根据业务需求配置
        for (HelloSPI helloSPI : serviceLoader) {
            helloSPI.sayHello();
        }
    }
}
      - 这里找到了两个HelloSPI接口的实现类
      - com.vivo.study.spidemo.spi.impl.ImageHello
      - com.vivo.study.spidemo.spi.impl.TextHello
  • 了解自动配置的 Bean
    你可以浏览到spring-boot-autoconfigure的源码以查看Spring提供的@Configuration类(请参阅 META-INF/spring.factories 文件)。
    • 自动配置由标准的@Configuration类实现
    • 当自动配置类需要被使用时,添加@Conditional去约束它
    • 通常,自动配置类使用 @ConditionalOnClass 和 @ConditionalOnMissingBean 注解。
    • 这确保自动配置只在有关类被找到且你没有定义你自己的@Configuration时适用。
  • 定位自动配置
    • spring boot 检测在你发布的jar包中META-INF/spring.factories文件的存在。该文件需要在EnableConfiguration key下面列出你的配置类,就像下列例子显示
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
  • 注意:自动配置也只能以这种方式加载。确保他们被定义在特定的包空间,并且确保他们永远不是组件扫描的目标。除此之外,自动配置类不需要开启主键扫描以找到额外的组件。应该改用特定的@Import注解。
    • 自动配置也只能以这种方式加载。
    • 确保他们被定义在特定(或者说独立的不被Spring自己按扫描到的)的包空间
    • 不要被组件扫描的方式扫描到自动配置包,而是@Import注解主动导入
  • 如果你提供特定的web配置,你的类可能需要在WebMvcAutoConfiguration之后加载。
  • 如果你的配置需要以特定的顺序加载,你可以使用@AutoConfigureAfter or @AutoConfigureBefore注解。
  • 记住这种方式保证的顺序不能被SpringBoot的组件扫描注解扫描,而是使用spring.factories或者@import注解导入
    • 否则你会发现顺序不生效
  • 就像标准的@Configuration类一样,自动配置类被加载的循序只影响他们Bean被定义的顺序。随后创建这些 bean 的顺序不受影响,由每个 bean 的依赖关系和任何 @DependsOn 关系决定。
  • 如果你想要序排序特定的彼此不应该有任何直接了解的的自动配置。你也可以使用 @AutoConfigureOrder注解。该注解就像@Order注解一样有相同的语义,为自动配置类提供特定的顺序。
  • 条件注解
    你总是想要去包含一个或多个@Conditional注解在你的自动配置类上。@ConditionalOnMissingBean注解是一个常见例子,被用于在他们不满足于默认配置时,开发人员重写自动配置
    spring boot 包含多个@Conditional注解,你可以在你的代码中,通过@Configuration类或者个人的@Bean 方法重用这些注解。这些注解包括:
    Class Conditions 类条件
    Bean Conditions Bean条件
    Property Conditions 属性条件
    Resource Conditions 资源条件
    Web Application Conditions web应用条件
    SpEL Expression ConditionsSpEL SpEL表达式条件
    • Class Conditions
      • 使用场景
        • 一般用来表示你是否导入了某个模块
      • @ConditionalOnClass and @ConditionalOnMissingClass注解类根据指定类的存在或者不存在而包含。
        由于注解元由ASM解析的事实,你可以使用value属性去引用真实的类,即使该类可能不存在于application的classpath上。
        如果你比较喜欢使用字符串去指定类名,你也可以使用 name 属性。
        • image.png
        • @ConditionalOnClass(ConnectionFactory.class) 和 @ConditionalOnClass(name)
      • @ConditionalOnClass的这种机制不适用于@Bean方法
        • 为了处理这种情况,可以使用单独的Configuration 类来隔离条件,间接的使函数可以使用@ConditionalOnClass,如下例所示:
@Configuration(proxyBeanMethods = false)
// Some conditions ...
public class MyAutoConfiguration {
    // Auto-configured beans ...
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(SomeService.class)
    public static class SomeServiceConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public SomeService someService() {
            return new SomeService();
        }
    }
}
     -  注意:在函数上使用[@ConditionalOnBean ](/ConditionalOnBean ) and @ConditionalOnMissingBean注解不会阻止创建配置类。但是 在类上使用Conditional和标记每个包含使用注解的@Bean函数的唯一不同之处在于, 如果Conditional不匹配,会防止配置类注册为bean。  
  • Bean Conditions
    • 使用场景
      • 主要用来判断Bean是否已经存在
        • image.png
    • @ConditionalOnBean 和 @ConditionalOnMissingBean注解让bean根据指定的bean的存在与否而包含。你可以使用 value 属性借助类型指定beans 或者 使用name安装名称去指定beans。search属性可以让你限制搜索Bean时应考虑的ApplicationContext层次结构。
    • 当注解在@Bean标注的方法上时,目标类型默认返回方法的类型,
@Configuration(proxyBeanMethods  = false) 
public class MyAutoConfiguration {
    @Bean  
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new SomeService();
    }
} 
  - 在上面的例子中,如果在ApplicationContext中没有包含SomeService类型的Bean时,SomeService将被创建。
  -  Tip:你需要非常小心的添加Bean定义的顺序,因为这些条件是根据到目前为止已经处理过的内容进行评估的。出于这个原因,我们建议在自动配置类上只使用@ConditionalOnBean和@ConditionalOnMissingBean注解(因为这些注解可以保证在任何用户定义的Bean定义被添加后加载)。 
  -  如果你的Bean的具体类实现了一个接口,那么Bean方法的返回类型应该是具体类而不是接口。 
  
  • Property Conditions
    • @ConditionalOnProperty注解让配置基于Spring环境属性而被包含。使用前缀和名称属性来指定应该被检查的属性。默认情况下,任何存在且不等于false的属性都被匹配。你也可以通过使用havingValue和matchIfMissing属性创建更高级的检查。
    • prefix+name or prefix+value属性
      • 用于查到,定位application.yml中的key
    • havingValue属性
      • 用来比较application,yml中的value值
    • matchIfMissing属性
      • 用来表示如果application.yml中没有写配置的情况下,是否加载我们的自动配置类
        • image.png
        • 这种情况是否自动加载该模块?
        • true:加载
        • false:不加载
  • Resource Conditions
    • @ConditionalOnResource注解让配置仅在特定资源存在时才被包含。资源可以通过使用通常的Spring约定来指定,如下面的例子所示:file:/home/user/test.dat。
  • Web Application Conditions
    • @ConditionalOnWebApplication和@ConditionalOnNotWebApplication注解允许根据应用是否是 "Web应用 "来包含配置。基于Servlet的Web应用是指任何使用Spring WebApplicationContext、定义会话范围或具有可配置WebEnvironment的应用。反应式Web应用是指任何使用ReactiveWebApplicationContext或具有ConfigurableReactiveWebEnvironment的应用。
    • @ConditionalOnWarDeployment注解允许根据应用程序是否是被部署到容器中的传统WAR应用程序而包含配置。这个条件将不符合使用嵌入式服务器运行的应用程序。
  • SpEL Expression Conditions
    • @ConditionalOnExpression注解允许根据SpEL表达式的结果来包含配置。
    • 在表达式中引用Bean将导致该Bean在上下文刷新处理中很早就被初始化。因此,该Bean将没有资格进行后处理(比如配置属性绑定),其状态可能是不完整的。
  • 模块命名
    • 你应该确保为你的starter提供一个合适的命名空间。不要以“spring-boot”作为你模块开头的名字,即便你使用不同的Maven groupId。在未来,我们可能为您的项目提供官方的支持
    • 根据经验,你需要在starter模块之后命名一个组合模块。例如,假设你为“acme”创建一个starter,你命名自动配置模块 “acme-spring-boot”,和starter模块为 “acme-spring-boot-starter”,如果你只有一个结合了两者的模块,命名它为“acme-spring-boot-starter”
  • 配置Key
    • 创建Properties
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(value  = "example") 
public class ExampleProperties {
} 
  • 使用Annotation Processor生成你的元数据
  • 使用注解处理器生成您自己的元数据
    你可以很简单的从@ConfigurationProperties注释的项目中使用“spring-boot-configuration-processor”jar包生成你的配置元数据文件。该jar包含一个你的项目编译时调用的java注解处理器
    • 配置注解处理器
      • 添加依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>

  • 如果你在你的项目中使用AspectJ,你需要确保注解处理器只运行一次。有几种方法可以做到这一点。在Maven中,你可以明确配置maven-apt-plugin,只在那里添加对注释处理器的依赖。你也可以让AspectJ插件运行所有处理,并在maven-compiler-plugin配置中禁用注释处理,如下所示

  • 如果你在项目中使用Lombok,你需要确保其注释处理器在spring-boot-configuration-processor之前运行。要用Maven做到这一点,你可以使用Maven编译器插件的annotationProcessors属性按正确顺序列出注释处理器。如果不使用该属性,注释处理器由classpath上的可用依赖项拾取,请确保lombok依赖项定义在spring-boot-configuration-processor依赖项之前。

  • 自动元数据生成

    • @ConfigurationProperties 注释的类和方法。如果该类也被@ConstructorBinding注解,那么预计会有一个构造函数,并且每个构造函数参数会创建一个属性。否则,属性是通过标准的getter和setter的存在来发现的,并对集合和map类型进行特殊处理(即使只有一个getter存在,也会被发现)。注解处理器也支持使用@Data、@Value、@Getter和@Setter lombok注释。
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@ConfigurationProperties(prefix = "my.server")
public class MyServerProperties {
 
    /**
     * Name of the server.
     */
    private String name;
 
    /**
     * IP address to listen to.
     */
    private String ip = "127.0.0.1";
 
    /**
     * Port to listener to.
     */
    private int port = 9797;
 
    public String getName() {
        return this.name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getIp() {
        return this.ip;
    }
 
    public void setIp(String ip) {
        this.ip = ip;
    }
 
    public int getPort() {
        return this.port;
    }
 
    public void setPort(int port) {
        this.port = port;
    }
    // fold:off
  • 注释处理器应用一些启发式方法来从源模型中提取默认值。默认值必须以静态方式提供。特别是,不要引用另一个类中定义的常量。另外,注释处理器不能自动检测 Enums 和 Collectionss 的默认值。对于无法检测到默认值的情况,应提供手动元数据。考虑以下示例
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@ConfigurationProperties(prefix = "my.messaging")
public class MyMessagingProperties {
 
    private List<String> addresses = new ArrayList<>(Arrays.asList("a", "b"));
 
    private ContainerType containerType = ContainerType.SIMPLE;
 
    public List<String> getAddresses() {
        return this.addresses;
    }
    public void setAddresses(List<String> addresses) {
        this.addresses = addresses;
    }
    public ContainerType getContainerType() {
        return this.containerType;
    }
    public void setContainerType(ContainerType containerType) {
        this.containerType = containerType;
    }
    public enum ContainerType {
        SIMPLE, DIRECT
    }
}
 
 

  • 为了记录上述类中属性的默认值,您可以将以下内容添加到模块的手动元数据中:
{"properties": [
  {
    "name": "my.messaging.addresses",
    "defaultValue": ["a", "b"]
  },
  {
    "name": "my.messaging.container-type",
    "defaultValue": "simple"
  }
]} 
  • 嵌套属性
    • 注释处理器自动将内部类视为嵌套属性。我们可以为它创建一个子命名空间,而不是在命名空间的根目录记录 ip 和端口。考虑更新的示例:
import org.springframework.boot.context.properties.ConfigurationProperties;
 
@ConfigurationProperties(prefix = "my.server")
public class MyServerProperties {
 
    private String name;
 
    private Host host;
 
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Host getHost() {
        return this.host;
    }
    public void setHost(Host host) {
        this.host = host;
    }
 
    public static class Host {
 
        private String ip;
 
        private int port;
 
        public String getIp() {
            return this.ip;
        }
        public void setIp(String ip) {
            this.ip = ip;
        }
        public int getPort() {
            return this.port;
        }
        public void setPort(int port) {
            this.port = port;
        }
    }
}


  • 前面的例子为my.server.name、my.server.host.ip和my.server.host.port属性产生元数据信息。你可以在一个字段上使用@NestedConfigurationProperty注解来表示一个普通的(非内部)类应该被当作是嵌套的。

  • 这对集合和map没有影响,因为这些类型被自动识别,并且为它们中的每一个生成一个元数据属性。

    • 添加额外的元数据
      • Spring Boot的配置文件处理是相当灵活的,通常情况下,可能存在未与@ConfigurationProperties Bean绑定的属性。你也可能需要调整现有键的一些属性。为了支持这种情况并让你提供自定义的 "提示",注解处理器自动将META-INF/additional-spring-configuration-metadata.json中的项目合并到主元数据文件中。
      • 如果你引用了一个已经被自动检测到的属性,那么描述、默认值和废弃信息将被覆盖,如果指定的话。如果手动属性声明在当前模块中没有被识别,它将被作为一个新的属性添加。
      • additional-spring-configuration-metadata.json文件的格式与常规的spring-configuration-metadata.json完全相同。附加属性文件是可选的。如果你没有任何附加属性,请不要添加该文件。
  • "autoconfigure "模块

    • autoconfigure模块包含了开始使用该库所需的一切。它还可能包含配置键的定义(如@ConfigurationProperties)和任何可用于进一步定制组件初始化方式的回调接口。
      • 你应该把对库的依赖标记为可选的,这样你就可以更容易地把自动配置模块纳入你的项目中。如果你这样做,该库就不会被提供,而且在默认情况下,Spring Boot会退缩。
    • Spring Boot使用注释处理器来收集元数据文件(META-INF/spring-autoconfigure-metadata.properties)中的自动配置条件。如果该文件存在,它被用来急切地过滤不匹配的自动配置,这将改善启动时间。建议在包含自动配置的模块中添加以下依赖关系。
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure-processor</artifactId>
  <optional>true</optional>
</dependency>

  • 如果你在应用程序中直接定义了自动配置,请确保配置spring-boot-maven-plugin,以防止repackage目标将依赖关系加入fat jar中。
<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-autoconfigure-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

相关包的作用

  • spring-boot-autoconfigure包
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <optional>true</optional>
</dependency>
 

  • 通过灵活的Auto-configuration注解使SpringBoot中的功能实现模块化和可被替换扩展。
  • spring-boot-autoconfigure思路类似SPI(Service Provider Interface),都是不同的实现类实现了定义的接口,加载时候去查找classpath下的实现类,不同在于前者使用autoconfigure实现后者使用的是ServiceLoader。
  • spring-boot-configuration-processor包
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
 

  • spring-boot-configuration-processor的作用是生成配置的元数据信息,即META-INF目录下的spring-configuration-metadata.json文件,从而告诉spring这个jar包中有哪些自定义的配置
  • 其中spring-configuration-metadata.json文件是在编译的时候自动生成的
  • spring-boot-autoconfigure-processor包
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
</dependency>
 

org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration=
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration.ConditionalOnClass=org.springframework.data.r2dbc.core.R2dbcEntityTemplate,org.springframework.r2dbc.core.DatabaseClient
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration.ConditionalOnSingleCandidate=org.springframework.r2dbc.core.DatabaseClient
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration.ConditionalOnBean=org.springframework.r2dbc.core.DatabaseClient
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration.ConditionalOnClass=io.r2dbc.spi.ConnectionFactory,org.springframework.data.r2dbc.repository.R2dbcRepository
  • 这个文件需要手动创建
  • 在启动时会读取这个文件,先根据条件情况加载,这样不会出现任何配置类都加载一边的情况,加快spring启动的速度
  • spring-boot会通过spring-boot的SPI机制,获得所有自动装配配置主类信息

项目结构

  • image.png
  • 上面这种结构是我故意的,其实最好的结构是直接使用“example-spring-boot-starter”作为项目,而非内部的子模块"example-spring-boot-autoconfiguation"
  • 我这样的做的目的是看到过一个开源项目,使用的就是Spring的AutoConfiguration这种形式,组合整个项目

具体步骤

  • 基础依赖包
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure</artifactId>
  <optional>true</optional>
</dependency>


  • 还可以添加lombok依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
 

  -  不过这个依赖需要首先被项目加载,然后才是annotation processor
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <optional>true</optional>
</dependency>
 

  • 可以spring-boot-autoconfigure-processor包,在启动是根据条件判断是否加载部分配置类
    • 加快springboot启动速度
  • 完整依赖
<properties>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure-processor</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
 

  • 总共有四个必须的角色
    • properties对象
package com.zhazha.autoconfiguration;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "com.zhazha.example")
    @Data
    public class ExampleProperties {
        private Boolean enable = false;
        private String username = "root";
        private String password = "root";
        private Host host = new Host("localhost", 8777);

        @Data
            @AllArgsConstructor
            @NoArgsConstructor
            public static class Host {
                private String ip = "127.0.0.1";
                private Integer port = 8080;
            }
    }



  • 自动配置类
package com.zhazha.autoconfiguration;

import com.zhazha.service.ExampleTools;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// @Configuration 这个注解最好别加,加了可能被 spring 注解扫描器扫描到
@EnableConfigurationProperties(value = ExampleProperties.class)
@ConditionalOnClass(value = SpringApplication.class)
@ConditionalOnProperty(prefix = "com.zhazha.example", name = "enable", matchIfMissing = true)
public class ExampleAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public ExampleTools exampleTools(ExampleProperties properties) {
        System.err.println("加载 Example");
        ExampleTools exampleTools = new ExampleTools();
        exampleTools.setUsername(properties.getUsername());
        exampleTools.setPassword(properties.getPassword());
        exampleTools.setHost(properties.getHost());
        return exampleTools;
    }

}
 
 

  - 注意:[@Configuration ](/Configuration ) 这个注解最好别加,加了可能被 spring 注解扫描器扫描到 
  • 需要自动配置,在外部项目使用的类
package com.zhazha.service;

import com.zhazha.autoconfiguration.ExampleProperties;
import lombok.Data;

@Data
public class ExampleTools {

    private String username;
    private String password;
    private ExampleProperties.Host host;

    public String hello() {
        return "用户名为:" + username + ">>>" + "密码为:" + password;
    }

    public String host() {
        return "host: " + host;
    }

}
 
 

  • spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.zhazha.autoconfiguration.ExampleAutoConfiguration
  • 创建spring-autoconfigure-metadata.properties(可选)
com.zhazha.autoconfiguration.ExampleAutoConfiguration=
com.zhazha.autoconfiguration.ExampleAutoConfiguration.ConditionalOnClass=org.springframework.boot.SpringApplication
  • maven安装自动配置项目到maven仓库
    • image.png
    • 也可以使用mvn clean install -Dmaven.test.skip=true
    • install之后会发现
      • image.png
    • 多创建了一个文件,该文件就是spring-boot-configuration-processor这个包自动创建的
    • 主要功能:application,yml的提示

使用方式

  • 使用方式就非常简单了
  • image.png
  • application.yml
server:
  port: 8080
com:
  zhazha:
    example:
      enable: true
      username: zhazha
      password: 123456
      host:
        port: 8088
  • 启动类
import com.zhazha.service.ExampleTools;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Random;


@SpringBootApplication
    @RestController
    public class Application {

        public static void main(String[] args) throws Exception {
            SpringApplication.run(Application.class, args);
        }

        @Resource
        private ExampleTools exampleTools;

        @GetMapping("hello")
        public String hello() {
            String hello = exampleTools.hello();
            String host = exampleTools.host();
            return "example: " + "\t hello: " + hello + " \t host: " + host + "\t random: " + new Random().nextInt();
        }

    }

实战

  • 项目根据Spring AutoConfiguration形式进行组合
    • 项目结构
      • image.png
      • example-spring-boot
        • 这是主项目
      • example-spring-boot-autoconfiguation
        • 这个项目带着自动配置功能
        • 该项目可以自己启动,因为有启动类,也可以在example-spring-boot项目中启动,然后使用
    • example-spring-boot-autoconfiguation自动配置项目
      • 依赖
<dependencies>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
    <optional>true</optional>
  </dependency>
</dependencies>


  • properties类
package com.zhazha.autoconfiguration.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "com.zhazha.example")
@Data
public class ExampleProperties {
    private Boolean enable;
}
  • 加载类
package com.zhazha.autoconfiguration.module;

import com.zhazha.autoconfiguration.ModuleApplication;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class ExampleModuleLoader implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
        int scan = scanner.scan(ModuleApplication.class.getPackageName());
        System.err.println("example-autoconfiguration 模块加载了 " + scan + "个类");
    }
}
  • 核心代码
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
int scan = scanner.scan(ModuleApplication.class.getPackageName());
System.err.println("example-autoconfiguration 模块加载了 " + scan + "个类");
  • 就是不知道这种方式加载的类是否存在问题

  • 是否还有更优解

  • 自动配置类

package com.zhazha.autoconfiguration.config;

import com.zhazha.autoconfiguration.module.ExampleModuleLoader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;

//@Configuration
@EnableConfigurationProperties(value = ExampleProperties.class)
@ConditionalOnProperty(prefix = "com.zhazha.example", name = "enable", matchIfMissing = false)
@Import(ExampleModuleLoader.class)
public class ExampleAutoConfiguration {
}
  • 创建META-INF文件
    • spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.zhazha.autoconfiguration.config.ExampleAutoConfiguration
  • spring-autoconfigure-metadata.properties
com.zhazha.autoconfiguration.config.ExampleAutoConfiguration=
com.zhazha.autoconfiguration.config.ExampleAutoConfiguration.ConditionalOnClass=org.springframework.boot.SpringApplication
  • service
package com.zhazha.autoconfiguration.service;

import org.springframework.stereotype.Service;

@Service
public class ModuleService {

    public String module() {
    return "zhazha ModuleService";
    }

}
  • controller
package com.zhazha.autoconfiguration.controller;

import com.zhazha.autoconfiguration.service.ModuleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Random;

@RestController
@Slf4j
public class Module01Controller {

    @Resource
    private ModuleService moduleService;

    @GetMapping("module01")
    public String module01() {
        return "module-1 \t " + moduleService.module() + "\t" + new Random().nextInt();
    }
}
 

  • 启动类(可选)
package com.zhazha.autoconfiguration;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ModuleApplication {

  public static void main(String[] args) throws Exception {
    SpringApplication.run(ModuleApplication.class, args);
  }

}
 

  • example-spring-boot
    • 依赖
<dependencies>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>compile</scope>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <dependency>
    <groupId>com.zhazha.starter</groupId>
    <artifactId>example-spring-boot-autoconfiguation</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>provided</scope>
  </dependency>
</dependencies>


server:
  port: 8080
com:
  zhazha:
    example:
      enable: true
  • 我们的自动配置项目在这里被启动

  • 启动类

package com.zhazha.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
    public class ExampleApplication {

        public static void main(String[] args) throws Exception {
            SpringApplication.run(ExampleApplication.class, args);
        }

    }
  • controller
package com.zhazha.example.controller;

import com.zhazha.autoconfiguration.service.ModuleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class IndexController {

  @Resource
  private ModuleService moduleService;

  @GetMapping("index")
  public String index() {
    return "index Controller\t" + moduleService.module();
  }

}
  • 自定义一个Jasync的spring-boot-starter
    • 背景
      • jasync如果按照官方自动配置,会报错
    • 步骤
      • 依赖
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.3</version>
  <relativePath/>
</parent>

<groupId>com.zhazha.jasync</groupId>
<artifactId>jasync-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<properties>
  <maven.compiler.source>11</maven.compiler.source>
  <maven.compiler.target>11</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
  </dependency>
  <dependency>
    <groupId>com.github.jasync-sql</groupId>
    <artifactId>jasync-r2dbc-mysql</artifactId>
    <version>2.1.1</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>


    • 需要添加,防止maven报错
    • 主要原因是:子模块的父项目不是原本的父项目,而是其他父项目
    • 在这个例子中,我们子项目的父项目是spring-boot-parent,所以需要添加
  • true
    • 这个也是需要添加的
    • maven的scope的编译模式会传递依赖,所以需要使用这项功能阻止依赖被传递到外部项目中(provider等模式不传递依赖)
    • 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
    <groupId>com.github.jasync-sql</groupId>
    <artifactId>jasync-r2dbc-mysql</artifactId>
    <version>2.1.1</version>
</dependency>

  • 而上面的两个包之所以不需要添加optional是因为需要有这两个包,外部需要有这两个包

    • properties类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data  
@ConfigurationProperties(prefix  = "com.zhazha.jasync") 
public class JasyncProperties {
    private boolean enabled = false;
} 
  • 决定了配置最终是否在主项目中使用

    • 自动配置类
import com.github.jasync.r2dbc.mysql.JasyncConnectionFactory;
import com.github.jasync.sql.db.Configuration;
import com.github.jasync.sql.db.mysql.pool.MySQLConnectionFactory;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.ConnectionPoolConfiguration;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.r2dbc.connection.R2dbcTransactionManager;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.Objects;

//@AutoConfiguration
@EnableConfigurationProperties(value = JasyncAutoConfiguration.class)
@ConditionalOnClass(ConnectionFactory.class)
@ConditionalOnProperty(prefix = "com.zhazha.jasync", name = "enabled", havingValue = "true")
@AutoConfigureOrder(value = Ordered.HIGHEST_PRECEDENCE + 12)
@EnableTransactionManagement
public class JasyncAutoConfiguration {

    @Bean
    ConnectionPool connectionFactory(R2dbcProperties properties) {
        JasyncConnectionFactory connectionFactory = createConnectionFactory(properties);
        R2dbcProperties.Pool pool = properties.getPool();
        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
        ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
        map.from(pool.getMaxIdleTime()).to(builder::maxIdleTime);
        map.from(pool.getMaxLifeTime()).to(builder::maxLifeTime);
        map.from(pool.getMaxAcquireTime()).to(builder::maxAcquireTime);
        map.from(pool.getMaxCreateConnectionTime()).to(builder::maxCreateConnectionTime);
        map.from(pool.getInitialSize()).to(builder::initialSize);
        map.from(pool.getMaxSize()).to(builder::maxSize);
        map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery);
        map.from(pool.getValidationDepth()).to(builder::validationDepth);
        return new ConnectionPool(builder.build());
    }

    @NotNull
    private static JasyncConnectionFactory createConnectionFactory(R2dbcProperties properties) {
        ConnectionFactoryOptions connectionFactoryOptions = ConnectionFactoryOptions.parse(properties.getUrl());
        Configuration configuration = new Configuration(
                properties.getUsername(),
                Objects.requireNonNull(connectionFactoryOptions.getValue(ConnectionFactoryOptions.HOST)).toString(),
                (Integer) connectionFactoryOptions.getValue(ConnectionFactoryOptions.PORT),
                properties.getPassword(),
                (String) connectionFactoryOptions.getValue(ConnectionFactoryOptions.DATABASE));
        JasyncConnectionFactory connectionFactory = new JasyncConnectionFactory(new MySQLConnectionFactory(configuration));
        return connectionFactory;
    }

    @Bean
    public ReactiveTransactionManager transactionManager(ConnectionPool factory) {
        return new R2dbcTransactionManager(factory);
    }

}
  • 这个类不能被spring boot 组件扫描器扫描到

  • 只能被spring.factories 或者 @import注解被spring注入

  • 所以我把@AutoConfiguration注释掉了

  • 创建META-INF/spring.factories

    • 内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.zhazha.jasync.config.JasyncAutoConfiguration

2.7.x新版本

有什么变化?

  • 新版本推荐使用@AutoConfiguration代替@Configuration
    • Configuration注解还能使用就是了
  • 使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports代替META-INF/spring.factories
    • 内容从key value变成value
com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration 
  • 还可以使用“#”添加你的注释
  • 旧版本的spring.factories可能在未来的版本被放弃
  • 使用@AutoConfiguration注解的before, beforeName, after and afterName属性来确定加载顺序
    • 当然 @AutoConfigureBefore and @AutoConfigureAfter这两个注解还可以使用
    • before表示还未被加载的自动配置类
    • after表示已经被加载的自动配置类
    • 这里的还未和已经以被@AutoConfiguration注解的类被加载的那一瞬间为基准
    • 这种,自动配置类之间能够规定配置类的加载顺序,可以保证A类和B类之间的加载顺序
  • 使用@AutoConfigureOrder注解来指定在自动配置类之间的顺序
    • 这种虽然也能规定加载顺序,但是和前面的那种明确保证A类和B类之间的加载顺序关系不同,是统一的顺序
    • 只会影响自动配置类之间的顺序,不会影响AnnotationConfigApplicationContext.register(Class...)的注册顺序

怎么做?

  • 删除spring.factories改用org.springframework.boot.autoconfigure.AutoConfiguration.imports

    org.springframework.boot.autoconfigure.AutoConfiguration.imports

    • 内容

      com.zhazha.autoconfiguration.config.ExampleAutoConfiguration

  • 修改自动配置类的注解

package com.zhazha.autoconfiguration.config;

import com.zhazha.autoconfiguration.module.ExampleModuleLoader;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;

@AutoConfiguration
@EnableConfigurationProperties(value = ExampleProperties.class)
@ConditionalOnProperty(prefix = "com.zhazha.example", name = "enable", matchIfMissing = false)
@Import(ExampleModuleLoader.class)
public class ExampleAutoConfiguration {
}
 

  • 从Configuration到AutoConfiguration
  • 但实际上这个注解可以不需要
package com.zhazha.autoconfiguration.config;

import com.zhazha.autoconfiguration.module.ExampleModuleLoader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;

//@AutoConfiguration
@EnableConfigurationProperties(value = ExampleProperties.class)
@ConditionalOnProperty(prefix = "com.zhazha.example", name = "enable", matchIfMissing = false)
@Import(ExampleModuleLoader.class)
public class ExampleAutoConfiguration {
}