spring boot(二)自动装配原理 - 实例分析(HttpEncodingAutoConfiguration)
在了解了Spring Boot的运作原理后,现在来简单的分析一个Spring Boot内置的自动配置功能:http的编码配置。
我们在常规项目中配置Http编码的时候是在web.xml添加一个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>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
spring boot要完成自动配置,就要满足两个条件:
- 能配置CharacterEncodingFilter这个Bean;
- 能配置encoding和forceEncoding两个参数。
1.自动配置
我们在org.springframework.boot.autoconfigure.web.servlet包中找到HttpEncodingAutoConfiguration类后,可以看到源码如下:
@Configuration
@EnableConfigurationProperties(HttpProperties.class)
// Spring 底层 @Conditional 注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效,该注解是判断当前应用是否是web应用,如果是, HttpEncodingAutoConfiguration 配置类生效;
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
// 判断当前项目有没有这个类 CharacterEncodingFilter;SpringMVC 中进行乱码解决的过滤器,如果有则 HttpEncodingAutoConfiguration 配置生效;
@ConditionalOnClass(CharacterEncodingFilter.class)
// 判断 application.properties 配置文件中是否存在 spring.http.encoding.enabled,如果不存在,判断也是成立的,因为 matchIfMissing=true,即缺省时默认为 true。
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
@Bean // 使用java方式配置CharacterEncodingFilter的Bean
@ConditionalOnMissingBean // spring容器中没有这个类时
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
private static class LocaleCharsetMappingsCustomizer implements
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final HttpProperties.Encoding properties;
LocaleCharsetMappingsCustomizer(HttpProperties.Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
可以看到这个类上总共有5个注解,我们分别来看这些注解的作用。
[email protected]
@Configuration:表示这个类是一个配置类,并将这个类加入IOC容器。
[email protected]
@EnableConfigurationProperties(HttpProperties.class):启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中。
进入这个注解,源码如下:
// 在application.properties使用prefix+属性名的方式可以修改默认配置
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
private boolean logRequestDetails;
private final Encoding encoding = new Encoding();
public boolean isLogRequestDetails() {
return this.logRequestDetails;
}
public void setLogRequestDetails(boolean logRequestDetails) {
this.logRequestDetails = logRequestDetails;
}
public Encoding getEncoding() {
return this.encoding;
}
public static class Encoding {
// spring boot默认的编码格式就是UTF-8
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Charset charset = DEFAULT_CHARSET;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
public Charset getCharset() {
return this.charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public boolean isForce() {
return Boolean.TRUE.equals(this.force);
}
public void setForce(boolean force) {
this.force = force;
}
public boolean isForceRequest() {
return Boolean.TRUE.equals(this.forceRequest);
}
public void setForceRequest(boolean forceRequest) {
this.forceRequest = forceRequest;
}
public boolean isForceResponse() {
return Boolean.TRUE.equals(this.forceResponse);
}
public void setForceResponse(boolean forceResponse) {
this.forceResponse = forceResponse;
}
public Map<Locale, Charset> getMapping() {
return this.mapping;
}
public void setMapping(Map<Locale, Charset> mapping) {
this.mapping = mapping;
}
public boolean shouldForce(Type type) {
Boolean force = (type != Type.REQUEST) ? this.forceResponse
: this.forceRequest;
if (force == null) {
force = this.force;
}
if (force == null) {
force = (type == Type.REQUEST);
}
return force;
}
public enum Type {
REQUEST, RESPONSE
}
}
}
前面有说到spring boot的理念是“约定大于配置”,可如果我们想修改默认配置改怎么修改呢?我们只需要在application.properties配置文件中,按照prefix + 属性名,就可以修改对应的属性。在本例中,我们如果想修改项目的默认编码为GBK,只需要在全局配置文件中添加一行代码即可,代码如下:
spring.http.encoding.charset=GBK
通过上面的分析,我们知道了在配置文件中修改默认配置的原理。关于配置文件可配置属性,可以参考官方文档。
1.3Conditional
剩下的三个注解都是ConditionalOnXXX类型的注解,这类注解都是@Conditional注解的派生注解,作用是必须是@Conditional指定的条件成立,才给容器中添加组件,配置里的内容才会生效。
针对本例来说,必须@ConditionalOnWebApplication、@ConditionalOnClass、@ConditionalOnProperty三个注解对应的条件都生效,HttpEncodingAutoConfiguration配置类才会生效。
1.4快速查看当前生效的配置类
在spring.factories文件中,我们可以看到有上百个默认配置,如果我们想知道当前有哪些默认配置生效了,哪些没有生效,这样一个个去分析明显是不现实的。我们可以在application.properties配置文件中添加以下配置,来快速查看当前生效的默认配置:
debug=true
此时再启动应用时,就可以在控制台看到生效的配置与没有生效的配置,并且可以看到配置没有生效的原因。
Positive matches(生效的配置):
-----------------
HttpEncodingAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.web.filter.CharacterEncodingFilter' (OnClassCondition)
- found 'session' scope (OnWebApplicationCondition)
- @ConditionalOnProperty (spring.http.encoding.enabled) matched (OnPropertyCondition)
HttpEncodingAutoConfiguration#characterEncodingFilter matched:
- @ConditionalOnMissingBean (types: org.springframework.web.filter.CharacterEncodingFilter; SearchStrategy: all) did not find any beans (OnBeanCondition)
...
Negative matches(没有生效的配置):
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
[email protected]扩展注解
除了以上解析到的注解,SpringBoot 还为我们提供了更多的有关 @Conditional 的派生注解。它们的作用:必须是 @Conditional 指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效: