Spring 4.x中提供基于条件来配置Bean的能力,Spring Boot的自动配置也基于这一原理。Spring 关于自动配置的源码在spring-boot-autoconfiguration这个jar包中。
@SpringBootApplicaiton注解由@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan注解组成。它的核心功能是@EnableAutoConfiguration注解提供的。其中它的关键功能是@Import注解导入的配置功能,
该类内部使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。
注解 |
说明 |
@ConditionalOnBean |
当容器里有指定的Bean的条件下 |
@ConditionalOnClass |
当类路径下有指定的类的条件下 |
@ConditionalOnExpression |
基于SpEL表达式作为判断条件 |
@ConditionalOnJava |
基于JVM版本作为判断条件 |
@ConditionalOnJndi |
在JNDI存在的条件下查找指定的位置 |
@ConditionalOnMissingBean |
当容器里没有指定的Bean的情况下 |
@ConditionalOnMissingClass |
当类路径下没有指定的类的条件下 |
@ConditionalOnNotWebApplication |
当前项目不是Web项目的条件下 |
@ConditionalOnProperty |
指定的属性是否有指定的值 |
@ConditionalOnResource |
类路径是否有指定的值 |
@ConditionalOnSingleCandidate |
当指定Bean在容器中只有一个,或者虽然有多个但是指定首选的Bean |
@ConditionalOnWebApplication |
当前项目是Web项目的条件下 |
以@ConditionalOnWebApplication注解为例
可以看出它有一个type属性,用于指定条件是哪种web项目,其中包含ANY(任何)、SERVLET(servlet web项目)、REACTIVE(reactive web项目)。
根据Type类型进行相应的检查,通过构造ConditionOutcome类的对象来帮助我们,通过isMatch方法返回boolean值来确定条件。
ANY类型即为既不是servlet web项目也不是reactive web项目
如servlet web项目的判断条件是:
(1)GenericWebApplicationContext是否在类路径中
(2)容器中是否有名为session的scope
(3)当前容器的Environmemt是否为ConfigurableWebEnvironment
(4)当前容器的ResourceLoader是否为WebApplicationContext(ResourceLoader是WebApplicationContext的顶级接口之一)
encodingFilter
org.springFramework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
自动配置要满足两个条件:
a. 能配置CharacterEncodingFilter这个Bean
b. 能配置encoding和forceEncoding这两个属性
基于类型安全的配置,Spring Boot的自动配置也是基于这一点实现的,这里的配置类可以在application.propeties中直接配置,代码如下:
/**
* 在application.properties配置的时候前缀是spring.http.encoding
* 具体的key就是spring.http.encoding.charset和spring.http.encoding.force
* 即prefix + 成员变量名称的格式
* 当application.properties有配置时使用配置的值,否则使用默认值
*/
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
//默认使用UTF-8编码方式,forceEncoding默认为true,设置为true则强制覆盖之前的编码格式
private Charset charset = DEFAULT_CHARSET;
private boolean force = true;
public Charset getCharset() {
return charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public boolean isForce() {
return force;
}
public void setForce(boolean force) {
this.force = force;
}
}
利用上述的配置,并根据条件来配置CharacterEncodingFilter这个Bean,代码如下:
/**
* 1. @Configuration:该类为配置类
* 2. @EnableConfigurationProperties:开启属性注入,使用 @Autowired注入HttpEncodingProperties
* 3. @ConditionalOnClass:在类路径下有CharacterEncodingFilter类的条件下
* 4. @ConditionalOnProperty:当设置了spring.http.encoding.enabled=true的条件下,如果没有设置则默认true,即满足条件
* 5. 3和4为自动配置的满足条件(与关系)
*/
@Configuration
@EnableConfigurationProperties(HttpEncodingProperties.class)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value="enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//自动注入HttpEncodingProperties
@Autowired
private HttpEncodingProperties httpEncodingProperties;
//当容器中没有CharacterEncodingFilter这个Bean时创建这个Bean
//使用httpEncodingProperties参数的值
@Bean
@ConditionalOnMissingBean(CharacterEncodingFilter.class)
public CharacterEncodingFilter getHttpEncodingProperties() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding(HttpEncodingProperties.DEFAULT_CHARSET.name());
characterEncodingFilter.setForceEncoding(httpEncodingProperties.isForce());
return characterEncodingFilter;
}
}
自定义启动器提供Service层服务,返回相应的"hello " + msg字符串(msg值可在application.xml中配置)。在使用该启动器的项目中,调用服务取得字符串返回到前端,在浏览器中现实。
自定义的starter项目artifact Id采用 *-spring-boot-starter的方式(Spring Boot官方的启动器使用spring-boot-starter-*的方式)
在pom.xml中引入autoconfiguration依赖
org.springframework.boot
spring-boot-autoconfigure
2.2.5.RELEASE
基于类型安全的配置,代码如下
/**
* 1. 引入配置,配置项为hello.msg
* 2. msg默认值为world
*/
@ConfigurationProperties(prefix = "hello")
public class HelloServiceProperties {
private static final String DEFAULT_MSG = "world";
public String msg = DEFAULT_MSG;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
/**
* 1. sayHello()方法提供返回字符串的服务
*/
public class HelloService {
private String msg;
public String sayHello() {
return "hello " + msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
/**
* 1. @Configuration:该类为配置类
* 2. @EnableConfigurationProperties:开启属性注入,使用 @Autowired注入HelloServiceProperties
* 3. @ConditionalOnClass:在类路径下有HelloService类的条件下
* 4. @ConditionalOnProperty:但设置了hello.enabled=true的条件下,如果没有设置则默认true,即满足条件
* 5. matchIfMissing默认值为false,此时就需要在配置文件中设置hello.enabled=true才能进行自动配置
* 6. 3和4为自动配置的满足条件(与关系)
*/
@Configuration
@EnableConfigurationProperties(HelloServiceProperties.class)
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix = "hello", value = "enabled", matchIfMissing = true)
public class HelloServiceAutoConfiguration {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService getHelloService() {
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
如果想要自动配置生效,就需要注册自动配置类,在src/main/resources下新建META-INF/spring.factories,并填写如下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.test.wjb.hello.configuration.HelloServiceAutoConfiguration
如果有多个配置,用","隔开,此处"/"是为了换行后仍能读到属性
开发完成后,使用mvn install命令将该启动器打包部署到本地仓库(或私服等),idea编辑器可以直接运行install
在需要使用的项目中引入该依赖包,如下:
com.test.wjb
hello-spring-boot-starter
1.0.0
在apllication.properties中加入所需的配置
# 当自动配置类的@ConditionalOnProperty的matchIfMissing=false时,
# 需要使用如下配置开启自动配置
# hello.enabled=true
# 配置msg的值
hello.msg=my first starter
编写业务代码,返回内容
/**
* 1. 定义Controller
* 2. @Autowired自动注入服务
* 3. helloService.sayHello()获取内容
*/
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private HelloService helloService;
@RequestMapping("/sayHello")
public String index() {
return helloService.sayHello();
}
}
浏览器中输入:http://localhost:8080/test/sayHello进行测试,结果如下:
(1)基于类型安全的配置,使用@ConfigurationProperties引入配置,prefix属性指定配置key前缀,成员变量名称指定配置key名称,因此配置项key为:prefix.成员变量名称
(2)自动配置类条件注解@ConditionalOnProperty,prefix指定配置key前缀,value指定配置key名称,配置文件中配置内容为:prefix.value=ture/false(如hello.enabled=true)。matchIfMissing为当没有改配置时的默认值。配置值为true时才能开启自动配置(没有其它条件的情况下)。