web应用自动配置
在之前的文章中分析过,@SpringBootApplication会使用到@Import注解来引入AutoConfigurationImportSelector
AutoConfigurationImportSelector则会通过spi机制来返回需要加载的自动配置类
其中就包括DispatcherServletAutoConfiguration和WebMvcAutoConfiguration
前者用来进行servlet相关的配置,后者用来进行mvc相关组件的配置
DispatcherServletAutoConfiguration
首先看下这个类都使用了哪些注解
// 用来指定自动初始化的顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 指定当前类为配置类
@Configuration(proxyBeanMethods = false)
// 当应用类型是servlet时才加载
@ConditionalOnWebApplication(type = Type.SERVLET)
// 类路径中有DispatcherServlet时才加载
@ConditionalOnClass(DispatcherServlet.class)
// 在ServletWebServerFactoryAutoConfiguration加载完成之后再加载
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
}
另外该类中存在如下几个内部类
DispatcherServletConfiguration
// 指定当前类为配置类
@Configuration(proxyBeanMethods = false)
// 满足DefaultDispatcherServletCondition时才进行加载
@Conditional(DefaultDispatcherServletCondition.class)
// 类路径中出现ServletRegistration才加载
@ConditionalOnClass(ServletRegistration.class)
// 使WebMvcProperties生效
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
// 初始化DispatcherSerlvet,bean的名称为dispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// 通过注入的webMvcProperties来配置dispatcherSerlvet
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
// 初始化MultipartResolver,用来上传文件
// 这里的加载条件是,上下文中存在MultipartResolver的一个bean,但是该bean的名称不是multipartResolver
// 这里会将这个bean的名称命名为multipartResolver
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
下面看下WebMvcProperties这个类
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
}
可以看到这个类主要用来接收配置中spring.mvc开头的配置
DefaultDispatcherServletCondition
接着看下用来代表DispatcherServletConfiguration加载条件的DefaultDispatcherServletCondition
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DefaultDispatcherServletCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Default DispatcherServlet");
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 从beanFactory中获取类型为DispatcherServlet的bean的名称
List<String> dispatchServletBeans = Arrays
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
if (dispatchServletBeans.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
// 如果已经包含名称为dispatcherSerlet并且类型为DispatcherServlet的bean,那么此时不匹配
return ConditionOutcome
.noMatch(message.found("dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
// 如果已经包含名称为dispatcherSerlvet的bean,那么此时不匹配
return ConditionOutcome.noMatch(
message.found("non dispatcher servlet bean").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
if (dispatchServletBeans.isEmpty()) {
// 当前不存在类型为DispatcherServlet的bean,那么匹配
return ConditionOutcome.match(message.didNotFind("dispatcher servlet beans").atAll());
}
// 虽然有类型DispatcherServlet的bean,但是名称不为dispatcherServlet,仍然匹配通过
return ConditionOutcome.match(message.found("dispatcher servlet bean", "dispatcher servlet beans")
.items(Style.QUOTE, dispatchServletBeans)
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
}
DispatcherServletRegistrationConfiguration
主要作用就是注册dispatcherServlet,并且配置一些属性
// 当前类是配置类
@Configuration(proxyBeanMethods = false)
// 满足DispatcherServletRegistrationCondition才加载
@Conditional(DispatcherServletRegistrationCondition.class)
// 类路径中有ServletRegistration才加载
@ConditionalOnClass(ServletRegistration.class)
// 开启WebMvcProperties的属性自动注入
@EnableConfigurationProperties(WebMvcProperties.class)
// 导入DispatcherServletConfiguration
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
// 返回名称为dispatcherServletRegistration 类型为dispatcherServletRegistration的bean
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
// 当beanFactory中同时存在类型为DispatcherServlet和名称为dispatcherServlet的bean时才加载
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
// 默认的path为/
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
// 设置上传文件方面的配置
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet>
implements DispatcherServletPath {
private final String path;
/**
* Create a new {@link DispatcherServletRegistrationBean} instance for the given
* servlet and path.
* @param servlet the dispatcher servlet
* @param path the dispatcher servlet path
*/
public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
super(servlet);
Assert.notNull(path, "Path must not be null");
this.path = path;
// 添加urlMappings
super.addUrlMappings(getServletUrlMapping());
}
@Override
public String getPath() {
return this.path;
}
// 不支持手动修改urlMapping
@Override
public void setUrlMappings(Collection<String> urlMappings) {
throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
}
@Override
public void addUrlMappings(String... urlMappings) {
throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
}
}
下面看下getServletUrlMapping
default String getServletUrlMapping() {
if (getPath().equals("") || getPath().equals("/")) {
return "/";
}
if (getPath().contains("*")) {
return getPath();
}
if (getPath().endsWith("/")) {
return getPath() + "*";
}
return getPath() + "/*";
}
DispatcherServletRegistrationCondition
这个类的主要作用是用来判断是否需要加载DispatcherServletRegistrationConfiguration
@Order(Ordered.LOWEST_PRECEDENCE - 10)
private static class DispatcherServletRegistrationCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
ConditionOutcome outcome = checkDefaultDispatcherName(beanFactory);
if (!outcome.isMatch()) {
return outcome;
}
return checkServletRegistration(beanFactory);
}
private ConditionOutcome checkDefaultDispatcherName(ConfigurableListableBeanFactory beanFactory) {
// 获取beanFactory中类型为DispatcherServlet的bean的名称
List<String> servlets = Arrays
.asList(beanFactory.getBeanNamesForType(DispatcherServlet.class, false, false));
// 判断是否存在名称为dispatcherServlet的bean
boolean containsDispatcherBean = beanFactory.containsBean(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// 如果存在名称为dispatcherServlet但是类型不是DispatcherServlet的bean,那么不匹配
if (containsDispatcherBean && !servlets.contains(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)) {
return ConditionOutcome.noMatch(
startMessage().found("non dispatcher servlet").items(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
}
// 否则返回匹配
return ConditionOutcome.match();
}
private ConditionOutcome checkServletRegistration(ConfigurableListableBeanFactory beanFactory) {
ConditionMessage.Builder message = startMessage();
// 获取beanFactory中类型为ServletRegistrationBean的bean
List<String> registrations = Arrays
.asList(beanFactory.getBeanNamesForType(ServletRegistrationBean.class, false, false));
// 判断是否存在名称为dispatcherServletRegistration的bean
boolean containsDispatcherRegistrationBean = beanFactory
.containsBean(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
if (registrations.isEmpty()) {
if (containsDispatcherRegistrationBean) {
// 不存在类型为ServletRegistrationBean,但是存在名称为dispatcherServletRegistration的bean,那么不匹配
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
// 既不存在类型为ServletRegistrationBean也不存在名称为dispatcherServletRegistration的bean,那么匹配
return ConditionOutcome.match(message.didNotFind("servlet registration bean").atAll());
}
if (registrations.contains(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)) {
// 存在名称为dispatcherServletRegistration并且类型为ServletRegistrationBean的bean,那么不匹配
return ConditionOutcome.noMatch(message.found("servlet registration bean")
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
if (containsDispatcherRegistrationBean) {
// 存在名称为dispatcherServletRegistration的bean,不匹配
return ConditionOutcome.noMatch(message.found("non servlet registration bean")
.items(DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
return ConditionOutcome.match(message.found("servlet registration beans").items(Style.QUOTE, registrations)
.append("and none is named " + DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME));
}
private ConditionMessage.Builder startMessage() {
return ConditionMessage.forCondition("DispatcherServlet Registration");
}
}
WebMvcAutoConfiguration
首先看下WebMvcAutoConfiguration使用了哪些注解
// 当前类是配置类
@Configuration(proxyBeanMethods = false)
// 只有当前应用是servlet应用时才加载
@ConditionalOnWebApplication(type = Type.SERVLET)
// 当类路径中宝苦口Servlet DispatcherServlet WebMvcConfigurer时才进行配置
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// WebMvcConfigurationSupport不存在时才进行配置
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
// 在DispatcherServletAutoConfiguration,TaskExecutionAutoConfiguration,ValidationAutoConfiguration初始化之后进行初始化
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
}
该类中主要包含如下几个内部类,下面分别进行分析
WebMvcAutoConfigurationAdapter
// 当前类是配置类
@Configuration(proxyBeanMethods = false)
// 引入EnableWebMvcConfiguration
@Import(EnableWebMvcConfiguration.class)
// 执行WebMvcProperties和ResourceProperties中配置的绑定
// 分别用来解析spring.mvc和spring.resources为前缀的配置项
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
}
viewResolver
@Bean
// 当存在ViewResolver类型的bean才加载
@ConditionalOnBean(ViewResolver.class)
// 当不存在名称为viewResolver且不存在类型为ContentNegotiatingViewResolver的bean
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
// 设置管理器
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
// 设置优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
ContentNegotiatingViewResolver虽然继承了ViewResolver,但是并不会直接进行视图的解析,而是通过上下文来找到合适的视图解析器来进行解析
ContentNegotiatingViewResolver
下面的代码会从beanFactory中获取实现了ViewResolver接口的bean放到viewResolvers中
@Override
protected void initServletContext(ServletContext servletContext) {
// 从spring上下文中找到实现了ViewResolver接口的bean
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
// 将找到的这些bean添加到viewResovlers中
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
if (matchingBeans.contains(vr)) {
continue;
}
String name = vr.getClass().getName() + i;
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
// 使用viewResolvers中的解析器来解析当前视图
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最佳匹配的
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
从上面的代码也能看出,ContentNegotiatingViewResolver不会直接解析视图,而是将解析工作交给beanFactory中ViewResolver的实现类bean
BeanNameViewResolver
BeanNameViewResolver根据名称来匹配beanFactory中的View类型的bean
@Bean
// 存在View类型的bean才加载
@ConditionalOnBean(View.class)
// 不存在BeanNameViewResolver类型的bean才加载
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
BeanNameViewResolver
BeanNameViewResolver同样继承了ViewResolver,因此同样具有解析视图的能力,但是和上面的ContentNegotiatingViewResolver不同,该类通过视图的名称在beanFactory中寻找同名的类型为View的bean
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
/**
* Specify the order value for this ViewResolver bean.
* <p>The default value is {@code Ordered.LOWEST_PRECEDENCE}, meaning non-ordered.
* @see org.springframework.core.Ordered#getOrder()
*/
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = obtainApplicationContext();
// 找到类型为View并且名称匹配视图名称的bean
if (!context.containsBean(viewName)) {
// Allow for ViewResolver chaining...
return null;
}
if (!context.isTypeMatch(viewName, View.class)) {
if (logger.isDebugEnabled()) {
logger.debug("Found bean named '" + viewName + "' but it does not implement View");
}
// Since we're looking into the general ApplicationContext here,
// let's accept this as a non-match and allow for chaining as well...
return null;
}
return context.getBean(viewName, View.class);
}
}
静态资源处理
WebMvcAutoConfigurationAdapter实现了WebMvcConfigurer接口,该接口主要是用来作为回调,来对配置进行定制化
对静态资源的处理,主要在addResourceHandlers这个方法中
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// resourceProperties中的配置项都是spring.resources前缀
// 是否开启默认的静态资源处理,默认值是true,如果开启,会执行下面的代码来对资源处理进行配置
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
// 判断是否有对/webjars/**的映射处理,如果没有进行下面的映射配置
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
// 这里的staticPathPattern默认是/**
// 这里path可以配置
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
// 判断是否有对应的映射,默认的映射位置是[/META-INF/resources/, /resources/, /static/, /public/]
// 因此,默认情况下,我们可以将静态资源放在上述几个目录下面
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
@EnableWebMvc
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
可以看到@EnableWebMvc注解,主要导入了DelegatingWebMvcConfiguration
下面看下DelegatingWebMvcConfiguration继承层次
WebMvcConfigurationSupport
首先看下WebMvcConfigurationSupport,实现了ApplicationContextAware和ServletContextAware,所以可以拿到ApplicationContext和ServletContext
另外该类有许多用来注入bean的方法
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
并且有很多空的protected修饰符的方法
protected void addViewControllers(ViewControllerRegistry registry) {
}
这些空的protected修饰符的方法会在上面这些注入bean的方法中被调用,这些空的protected修饰符的方法就是提供给子类来进行定制化操作的
DelegatingWebMvcConfiguration
下面看下实现类DelegatingWebMvcConfiguration,从下面的代码可以看到,DelegatingWebMvcConfiguration 首先是一个配置类
其次通过setConfigurers方法注入了WebMvcConfigurer类型的bean
然后实现了WebMvcConfigurationSupport提供的定制化mvc的拓展点方法
在拓展点方法中,会遍历所有注入的WebMvcConfigurer类型的bean,并且调用它们对应的方法来分别执行各自的定制逻辑
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
@Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
this.configurers.configurePathMatch(configurer);
}
@Override
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
this.configurers.configureContentNegotiation(configurer);
}
@Override
protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
this.configurers.configureAsyncSupport(configurer);
}
@Override
protected void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
this.configurers.configureDefaultServletHandling(configurer);
}
@Override
protected void addFormatters(FormatterRegistry registry) {
this.configurers.addFormatters(registry);
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
this.configurers.addResourceHandlers(registry);
}
@Override
protected void addCorsMappings(CorsRegistry registry) {
this.configurers.addCorsMappings(registry);
}
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
this.configurers.configureViewResolvers(registry);
}
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
this.configurers.addArgumentResolvers(argumentResolvers);
}
@Override
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
this.configurers.addReturnValueHandlers(returnValueHandlers);
}
@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.configureMessageConverters(converters);
}
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
this.configurers.extendMessageConverters(converters);
}
@Override
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
}
@Override
protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
this.configurers.extendHandlerExceptionResolvers(exceptionResolvers);
}
@Override
@Nullable
protected Validator getValidator() {
return this.configurers.getValidator();
}
@Override
@Nullable
protected MessageCodesResolver getMessageCodesResolver() {
return this.configurers.getMessageCodesResolver();
}
}
定制化mvc配置
如果需要定制化mvc配置,可以通过以下几个方法,其中最简单的并且能够保留spring boot自动配置功能的方式是实现一个WebMvcConfigurer并且注入
implements WebMvcConfigurer
Spring boot自动配置会自动引入DelegatingWebMvcConfiguration,该类会扫描类型为WebMvcConfigurer的bean,并且执行对应的定制化逻辑
通过这种方式实现的定制化, 不会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置
@EnableWebMvc + implements WebMvcConfigurer
因为@EnableWebMvc会引入DelegatingWebMvcConfiguration,并且DelegatingWebMvcConfiguration本身继承了WebMvcConfigurationSupport
而Spring boot关于mvc相关的自动化配置的前提是不存在WebMvcConfigurationSupport类型的bean
因此使用这种方式会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置
extends WebMvcConfigurationSupport 或者extends DelegatingWebMvcConfiguration
道理同上,都会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置