Spring Boot学习笔记02--深入了解自动配置

摘要

看完本文你将掌握如下知识点:

  1. SpringBoot都帮我们做了哪些自动配置
  2. 我们如何接管SpringBoot的自动配置
  3. 注册Servlet、Filter、Listener的方法

SpringBoot系列Spring Boot学习笔记


SpringBoot的自动配置

1.自动配置类都存放在spring-boot-autoconfigure-1.4.2.RELEASE.jar下的
org.springframework.boot.autoconfigure路径下;
2.application.properties中配置debug=true后启动容器,可以看到服务器初始化的自动配置如下:

  • DispatcherServletAutoConfiguration
    注册org.springframework.web.servlet.DispatcherServlet

  • EmbeddedServletContainerAutoConfiguration
    注册容器类型,如类路径下存在org.apache.catalina.startup.Tomcat,就会注册Tomcat容器

  • ErrorMvcAutoConfiguration
    注册异常处理器

  • HttpEncodingAutoConfiguration
    注册http编码过滤器

  • HttpMessageConvertersAutoConfiguration
    注册json或者xml处理器

  • JacksonAutoConfiguration
    注册json对象解析器

  • JmxAutoConfiguration
    注册JMX管理器

JMX与Spring集成
spring通过annotation注解注册MBean到JMX实现监控java运行状态

  • MultipartAutoConfiguration
    注册文件传输处理器

  • ServerPropertiesAutoConfiguration
    用于初始化容器相关的配置属性,如服务地址、端口、contextPath,并根据当前容器类型初始化各个容器的特有属性,如tomcat的maxThreads、uriEncoding等等,其对应的属性类为ServerProperties

  • WebClientAutoConfiguration
    注册RestTemplate

  • WebMvcAutoConfiguration
    注册SpringMvc相关处理器,如ResourceResolver、RequestMappingHandlerAdapter、ExceptionHandlerExceptionResolver、ViewResolver、LocaleResolver,等等

  • WebSocketAutoConfiguration
    注册webSocket相关处理器,根据容器类型注册不同的处理器

3.如果依赖中加入了其它功能的依赖,SpringBoot还会实现这些功能的自动适配,比如我们增加数据库的JPA的功能,就会启用对JpaRepositoriesAutoConfiguration的自动配置功能。关于数据库方面的内容将在后文介绍。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

说明
从各个AutoConfiguration配置类中可以看到如下注解,基于这些注解可以确定这些AutoConfiguration的初始化顺序:

  • @AutoConfigureOrder(-2147483648):数越小越先初始化

  • @AutoConfigureAfter({EmbeddedServletContainerAutoConfiguration.class}):在指定的配置类初始化后再加载

  • @AutoConfigureBefore({WebMvcAutoConfiguration.class}):在指定的配置类初始化前加载


接管SpringBoot的自动配置

我们介绍过**@SpringBootApplication这个注解,因其包含@EnableAutoConfiguration@ComponentScan**注解,可以自动扫描相关的自动配置类,从而实现自动配置功能的。
上面介绍默认情况下SpringBoot默认会初始化很多的自动配置,这些配置有些我们在项目中可能用不到,那要如何去掉呢?

去掉不需要的自动配置类

比如我们不需要开启webSocket和JMX的自动配置,我们需要在**@SpringBootApplication这个注解中指定exclude**属性

1
2
3
4
5
6
@SpringBootApplication(exclude = {WebSocketAutoConfiguration.class,JmxAutoConfiguration.class})
public class SpringBootWebDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootWebDemoApplication.class, args);
}
}

明确指定需要启用哪些自动配置

我们可以去掉**@SpringBootApplication注解,改用*@Configuration、@Import、@ComponentScan*注解,在@Import**注解中明确指定需要启用哪些自动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//@SpringBootApplication(exclude = {WebSocketAutoConfiguration.class,JmxAutoConfiguration.class})
@Configuration
@Import({
DispatcherServletAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
ErrorMvcAutoConfiguration.class,
HttpEncodingAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
JacksonAutoConfiguration.class,
MultipartAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
WebMvcAutoConfiguration.class
})
@ComponentScan
public class SpringBootWebDemoApplication {

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

说明:

  • 这里推荐使用第一种方式:@SpringBootApplication(exclude={})

  • 实际上,开启默认的自动配置功能,只是会影响项目启动时间,所以没有特殊需要,可以不需要关闭某个自动配置功能;

  • 在某些情况,比如项目需要多数据源时,在项目中就会包含多个DataSource的Bean,因为DataSourceAutoConfiguration自动配置只能绑定一个数据源,此时发现多个DataSource的Bean被Spring注册就会抛出异常。

1.这时就可以采用去掉DataSourceAutoConfiguration的方式;
2.或者也可以在某一个DataSource的Bean上声明**@Primary注解,指定其为主数据源,这时DataSourceAutoConfiguration只会加载被指定@Primary**注解的主数据源,这样就可以享受到SpringBoot自动配置带来的好处。


接管WebMvc自动配置

对于一个web项目,最重要的就是Mvc相关的控制,SpringBoot通过WebMvcAutoConfiguration来完成与Mvc有关的自动配置。如果希望完全接管WebMvc自动配置,可以在项目中创建一个注解了**@EnableWebMvc**的配置类,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package com.example;

import org.apache.log4j.Logger;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.handler.SimpleServletHandlerAdapter;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.util.Properties;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example", useDefaultFilters = false, includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})
})
public class MvcConfig extends WebMvcConfigurationSupport {

private static final Logger logger = Logger
.getLogger(MvcConfig.class);

/**
* 描述 : <注册视图处理器>. <br>
*<p>
<使用方法说明>
</p>
* @return
*/
@Bean
public ViewResolver viewResolver() {
logger.info("ViewResolver");
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/jsp/function/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}

/**
* 描述 : <注册消息资源处理器>. <br>
*<p>
<使用方法说明>
</p>
* @return
*/
@Bean
public MessageSource messageSource() {
logger.info("MessageSource");
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("config.messages.messages");

return messageSource;
}

/**
* 描述 : <注册servlet适配器>. <br>
*<p>
<只需要在自定义的servlet上用@Controller("映射路径")标注即可>
</p>
* @return
*/
@Bean
public HandlerAdapter servletHandlerAdapter(){
logger.info("HandlerAdapter");
return new SimpleServletHandlerAdapter();
}

/**
* 描述 : <本地化拦截器>. <br>
*<p>
<使用方法说明>
</p>
* @return
*/
@Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
logger.info("LocaleChangeInterceptor");
return new LocaleChangeInterceptor();
}

/**
* 描述 : <基于cookie的本地化资源处理器>. <br>
*<p>
<使用方法说明>
</p>
* @return
*/
@Bean(name="localeResolver")
public CookieLocaleResolver cookieLocaleResolver(){
logger.info("CookieLocaleResolver");
return new CookieLocaleResolver();
}

/**
* 描述 : <添加拦截器>. <br>
*<p>
<使用方法说明>
</p>
* @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// TODO Auto-generated method stub
logger.info("addInterceptors start");
registry.addInterceptor(localeChangeInterceptor());
logger.info("addInterceptors end");
}

/**
* 描述 : <资源访问处理器>. <br>
*<p>
<可以在jsp中使用/static/**的方式访问/WEB-INF/static/下的内容>
</p>
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
logger.info("addResourceHandlers");
registry.addResourceHandler("/static/**").addResourceLocations("/WEB-INF/static/");
}

/**
* 描述 : <文件上传处理器>. <br>
*<p>
<使用方法说明>
</p>
* @return
*/
@Bean(name="multipartResolver")
public CommonsMultipartResolver commonsMultipartResolver(){
logger.info("CommonsMultipartResolver");
return new CommonsMultipartResolver();
}

/**
* 描述 : <异常处理器>. <br>
*<p>
<系统运行时遇到指定的异常将会跳转到指定的页面>
</p>
* @return
*/
@Bean(name="exceptionResolver")
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
logger.info("CP_SimpleMappingExceptionResolver");
SimpleMappingExceptionResolver simpleMappingExceptionResolver= new SimpleMappingExceptionResolver();
simpleMappingExceptionResolver.setDefaultErrorView("common_error");
simpleMappingExceptionResolver.setExceptionAttribute("exception");
Properties properties = new Properties();
properties.setProperty("java.lang.RuntimeException", "common_error");
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}

}

此时debug模式运行项目,会看到WebMvcAutoConfiguration没有被自动配置,说明我们自己定义的MvcConfig已经完全接管了默认的自动配置,这是因为WebMvcAutoConfiguration有一个条件注解:

1
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})

而我们本例中MvcConfig就是WebMvcConfigurationSupport的实现类,同时加入**@EnableWebMvc**注解也会导入一个WebMvcConfigurationSupport的实现类:DelegatingWebMvcConfiguration
,所以MvcConfig继承WebMvcConfigurationSupport不是必须的,但是可以方便我们编码。


参考:SpringMVC4零配置–Web上下文配置【MvcConfig】


如果希望可以继续使用WebMvcAutoConfiguration的自动配置,而只是需要修改或者增加MVC中的某些配置时,我们可以创建一个配置类,并继承于抽象类WebMvcConfigurerAdapter,我们可以通过实现抽象类的方法来注册自己的控制器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
public WebMvcConfigurerAdapter() {
}

public void configurePathMatch(PathMatchConfigurer configurer) {
}

public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}

public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}

public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}

public void addFormatters(FormatterRegistry registry) {
}

public void addInterceptors(InterceptorRegistry registry) {
}

public void addResourceHandlers(ResourceHandlerRegistry registry) {
}

public void addCorsMappings(CorsRegistry registry) {
}

public void addViewControllers(ViewControllerRegistry registry) {
}

public void configureViewResolvers(ViewResolverRegistry registry) {
}

public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}

public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}

public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}

public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}

public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
}

public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
}

public Validator getValidator() {
return null;
}

public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}

比如我们可以增加一个视图跳转控制器,如下:

1
2
3
4
5
6
7
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/demo/123").setViewName("/demo");
}
}

注册Servlet、Filter、Listener的方法

1.如果是war包项目,我们可以将Servlet、Filter、Listener注册到WebApplicationInitializer的实现类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Order(1)
public class CommonInitializer implements WebApplicationInitializer {

@Override
public void onStartup(ServletContext servletContext)
throws ServletException {

//Log4jConfigListener
servletContext.setInitParameter("log4jConfigLocation", "classpath:log4j.properties");
servletContext.addListener(Log4jConfigListener.class);


//OpenSessionInViewFilter
OpenSessionInViewFilter hibernateSessionInViewFilter = new OpenSessionInViewFilter();
FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(
"hibernateFilter", hibernateSessionInViewFilter);
filterRegistration.addMappingForUrlPatterns(
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE), false, "/");


//DemoServlet
DemoServlet demoServlet = new DemoServlet();
ServletRegistration.Dynamic dynamic = servletContext.addServlet(
"demoServlet", demoServlet);
dynamic.setLoadOnStartup(2);
dynamic.addMapping("/demo_servlet");


}
}

2.如果是jar包部署方式,则可以将其注册到任意一个**@Configuration**配置类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class WebConfig {

@Bean
public ServletRegistrationBean servletRegistrationBean_demo1(){
return new ServletRegistrationBean(new DemoServlet(),"/demo-servlet1");
}

@Bean
public ServletRegistrationBean servletRegistrationBean_demo2(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.addUrlMappings("/demo-servlet2");
servletRegistrationBean.setServlet(new DemoServlet2());
return servletRegistrationBean;
}

@Bean
public FilterRegistrationBean filterRegistrationBean(){

FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new OpenSessionInViewFilter());
Set<String> set = new HashSet<String>();
set.add("/");
filterRegistrationBean.setUrlPatterns(set);
return filterRegistrationBean;
}

@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean(){
ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean();
servletListenerRegistrationBean.setListener(new Log4jConfigListener());
servletListenerRegistrationBean.addInitParameter("log4jConfigLocation","classpath:log4j.properties");
return servletListenerRegistrationBean;
}
}

总结

一句话概括SpringBoot的自动配置–就是一组基于条件注解实现Bean注册的Spring配置类。