1. Spring Boot运行原理
Spring 4.x中提供基于条件来配置Bean的能力,Spring Boot的自动配置也基于这一原理。Spring 关于自动配置的源码在spring-boot-autoconfiguration这个jar包中。
(1)注解@SpringBootApplicaiton
@SpringBootApplicaiton注解由@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan注解组成。它的核心功能是@EnableAutoConfiguration注解提供的。其中它的关键功能是@Import注解导入的配置功能,
该类内部使用SpringFactoriesLoader.loadFactoryNames方法来扫描具有META-INF/spring.factories文件的jar包。
(2)核心注解
注解 | 说明 |
@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的顶级接口之一)
2. 自定义http编码配置启动器(starter)
(1)常规项目中的http编码配置(配置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>
自动配置要满足两个条件:
a. 能配置CharacterEncodingFilter这个Bean
b. 能配置encoding和forceEncoding这两个属性
(2)配置参数
基于类型安全的配置,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;
}
}
(3)配置Bean
利用上述的配置,并根据条件来配置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;
}
}
3. 自定义启动器实战
(1)业务说明
自定义启动器提供Service层服务,返回相应的"hello " + msg字符串(msg值可在application.xml中配置)。在使用该启动器的项目中,调用服务取得字符串返回到前端,在浏览器中现实。
(2)新建starter的Maven项目
自定义的starter项目artifact Id采用 *-spring-boot-starter的方式(Spring Boot官方的启动器使用spring-boot-starter-*的方式)
在pom.xml中引入autoconfiguration依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
(3)配置参数
基于类型安全的配置,代码如下
/**
* 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;
}
}
(4) 判断依据类(第三方项目使用的类)
/**
* 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;
}
}
(5)自动配置类
/**
* 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;
}
}
(6)注册配置
如果想要自动配置生效,就需要注册自动配置类,在src/main/resources下新建META-INF/spring.factories,并填写如下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.test.wjb.hello.configuration.HelloServiceAutoConfiguration
如果有多个配置,用","隔开,此处"/"是为了换行后仍能读到属性
(7)部署到本地仓库
开发完成后,使用mvn install命令将该启动器打包部署到本地仓库(或私服等),idea编辑器可以直接运行install
(8)其它项目引入依赖
在需要使用的项目中引入该依赖包,如下:
<dependency>
<groupId>com.test.wjb</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
(9)配置使用服务
在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();
}
}
(10)测试
浏览器中输入:http://localhost:8080/test/sayHello进行测试,结果如下:
4. 总结
(1)基于类型安全的配置,使用@ConfigurationProperties引入配置,prefix属性指定配置key前缀,成员变量名称指定配置key名称,因此配置项key为:prefix.成员变量名称
(2)自动配置类条件注解@ConditionalOnProperty,prefix指定配置key前缀,value指定配置key名称,配置文件中配置内容为:prefix.value=ture/false(如hello.enabled=true)。matchIfMissing为当没有改配置时的默认值。配置值为true时才能开启自动配置(没有其它条件的情况下)。