前言
由于笔记是由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 - 以完整接口名为文件名
- 以所有需要被根据接口找到的实现类完整类全明
- 多个实现类的话,每个实现类一行
- 内容
- 创建
- 是什么?
- 什么 是SPI?
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 属性。- @ConditionalOnClass(ConnectionFactory.class) 和 @ConditionalOnClass(name)
- @ConditionalOnClass的这种机制不适用于@Bean方法
- 为了处理这种情况,可以使用单独的Configuration 类来隔离条件,间接的使函数可以使用@ConditionalOnClass,如下例所示:
- 使用场景
- Class Conditions
@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是否已经存在
- 主要用来判断Bean是否已经存在
- @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中没有写配置的情况下,是否加载我们的自动配置类
- 这种情况是否自动加载该模块?
- true:加载
- false:不加载
- 用来表示如果application.yml中没有写配置的情况下,是否加载我们的自动配置类
- 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)中的自动配置条件。如果该文件存在,它被用来急切地过滤不匹配的自动配置,这将改善启动时间。建议在包含自动配置的模块中添加以下依赖关系。
- autoconfigure模块包含了开始使用该库所需的一切。它还可能包含配置键的定义(如@ConfigurationProperties)和任何可用于进一步定制组件初始化方式的回调接口。
<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机制,获得所有自动装配配置主类信息
项目结构
- 上面这种结构是我故意的,其实最好的结构是直接使用“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仓库
- 也可以使用mvn clean install -Dmaven.test.skip=true
- install之后会发现
- 多创建了一个文件,该文件就是spring-boot-configuration-processor这个包自动创建的
- 主要功能:application,yml的提示
使用方式
- 使用方式就非常简单了
- 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形式进行组合
- 项目结构
- 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 {
}