1、Spring MVC自动配置
Spring Boot对Spring MVC自动配置的详细可以参考管方文档。Spring Boot为Spring MVC提供的AutoConfiguration适用于大多数应用场景,Spring Boot对Spring MVC做了以下默认的自动配置:
- 引入ContentNegotiatingViewResolver 和 BeanNameViewResolver 。
- 对静态资源的支持,包括对WebJars 的支持
- 自动注册Converter,GenericConverter,Formatter。
- 对HttpMessageConverters 的支持。
- 自动注册MessageCodeResolver。
- 对静态资源的支持
- 对自定义Favicon的支持
- 自动使用 ConfigurableWebBindingInitializer bean。
Spring Boot默认情况下是自动配置好Spring MVC的,可以直接使用,但是Spring Boot也支持我们修改Spring Boot对SpringMVC的配置。如果保留Spring Boot MVC特性,我们只需添加其他的MVC配置(拦截器,格式化处理器,视图控制器等)。我们可以添加自己的WebMvcConfigurerAdapter 类型的@Configuration类(配置类),而不需要注解@EnableWebMvc。如果希望使用自定义的RequestMappingHandlerMapping,RequestMappingHandlerAdapter,或ExceptionHandlerExceptionResolver,我们可以声明一个WebMvcRegistrationsAdapter实例提供这些组件。
但是如果想全面控制Spring MVC,我们可以添加自己的@Configuration类,并使用@EnableWebMvc注解。这样Spring Boot就不会对MVC进行配置了。然后我们就可以像刚开始使用Spring MVC那样对他进行配置。
2、Spring MVC自动配置原理细节
Spring Boot对Spring MVC的自动配置主要是通过WebMvcAutoConfiguration
这个类实现的,接下来我们就结合这个类来简单分析一下自动配置的细节。
2.1 ContentNegotiatingViewResolver 和 BeanNameViewResolver
这两个一听名字就知道是和视图解析器有关,也确实是这样的,他们自动配置了ViewReslover,然后由ViewReslover得到View对象,View对象调用他的render方法渲染页面等等。其中BeanNameViewResolver 就是SpringMVC中的一个视图解析器,他可以通过视图名来获得视图解析器,而ContentNegotiatingViewResolver的作用就是组合所有的视图解析器,下面他们的源码:
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean //只会创建一个
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
//给这个视图解析器设置执行顺序order,他的级别是很低的
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
@Bean
@ConditionalOnBean(ViewResolver.class)
@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是如何组合所有视图解析器的
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
implements ViewResolver, Ordered, InitializingBean {
@Nullable
private List<ViewResolver> viewResolvers;
@Override
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
//遍历BeanFactoryutils中的视图解析器
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);
}
}
因此,我们可以实现自己的视图解析器,然后ContentNegotiatingViewResolver把它注册到容器中。 定制自己的视图解析器,我们可以在启动类中实现ViewResolver接口,编写我们自己的视图解析器,然使用@Bean标签配置给IOC容器。
package com.xust.iot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import java.util.Locale;
@SpringBootApplication
public class SpringBootWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootWebApplication.class, args);
}
//直接在容器中添加我们的视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
/**
* 实现ViewResolver
*/
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
//在这里面写我们自己的视图处理逻辑
return null;
}
}
}
3、Converter,GenericConverter,Formatter
这些功能在Spring Boot中也有默认的自动配置,这里我们要了解的是如何扩展配置Converter和Formatter。源码:
@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
//在文件中配置日期格式化的规则
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
}
//添加格式化组件
@Override
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
我们也可以定制自己的转换器
package com.xust.iot;
import com.xust.iot.bean.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.converter.Converter;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import java.util.Locale;
@SpringBootApplication
public class SpringBootWebApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootWebApplication.class, args);
}
@Bean
public Converter<?,?> userToStringConverter(){
return new UserToStringConverter();
}
//把User对象转换成String
public static class UserToStringConverter implements Converter<User,String>{
@Override
public String convert(User source) {
//写我们自己的转换规则
return null;
}
}
}
4、HttpMessageConverters
Spring MVC 使用HttpMessageConverter 接口转换HTTP 请求和响应,合适的默认配置可以开箱即用,例如对象自动转换为JSON(使用Jackson库)或XML(如果Jackson XML扩展可用,否则使用JAXB),字符串默认使用UTF-8编码。可以使用Spring Boot 的HttpMessageConverters 类添加或自定义转换类:
@Configuration
public class FastJsonHttpMessageConvertersConfig extends WebMvcConfigurerAdapter {
@Bean
public FastJsonConfig fastJsonConfig() {
FastJsonConfig fastJsonConfig = new FastJsonConfig();
SerializerFeature writeMapNullValue = SerializerFeature.WriteMapNullValue;
SerializerFeature WriteNullStringAsEmpty = SerializerFeature.WriteNullStringAsEmpty;
SerializerFeature WriteNullNumberAsZero = SerializerFeature.WriteNullNumberAsZero;
SerializerFeature WriteNullListAsEmpty = SerializerFeature.WriteNullListAsEmpty;
fastJsonConfig.setSerializerFeatures(writeMapNullValue, WriteNullStringAsEmpty,
WriteNullNumberAsZero, WriteNullListAsEmpty);
return fastJsonConfig;
}
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters(
@Qualifier("fastJsonConfig") FastJsonConfig fastJsonConfig) {
FastJsonHttpMessageConverter4 fastConverter = new FastJsonHttpMessageConverter4();
fastConverter.setFastJsonConfig(fastJsonConfig);
HttpMessageConverter<?> converter = fastConverter;
return new HttpMessageConverters(converter);
}
}
5、扩展Spring Boot对Spring MVC的配置
想要扩展Spring Boot的MVC功能,我们要WebMvcConfigurer接口,但是这样太麻烦了,因此Spring Boot提供了一个适配器类WebMvcConfigurerAdapter,它里面全部是一些空方法,我们可以继承WebMvcConfigurerAdapter类,然后我们只需要按照我们的需要重写里面的方法就好了。
package com.xust.iot.configurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
@Configuration
public class ApplicationMVCConfig extends WebMvcConfigurerAdapter {
//在这里可以配置拦截器,文件上传解析器,异常解析器....只要是在原本spring-mvc.xml文件中可以配置的在这里都可以配置
//视图映射
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//相当于<mvc:view-controller path="/hello" view="success.html"/>
registry.addViewController("/hello").setViewName("success");
}
/**
* 拦截器 拦截hello请求
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/hello");
}
//异常解析处理器
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(new MyExceptionHandler()) ;
}
}
6、全面接管Spring Boot对Spring MVC的自动配置
官网中的一句话:If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
意思就是我们可以配置类上加上EnableWebMvc
来全面接管Spring MVC,这样一来SpringBoot就不会对Spring MVC进行配置了,一切都需要我们来配置。
package com.xust.iot.configurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@EnableWebMvc
@Configuration
public class ApplicationMVCConfig implements WebMvcConfigurer {
//在这里可以配置拦截器,文件上传解析器,异常解析器....只要是在原本spring-mvc.xml文件中可以配置的在这里都可以配置
//配置视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resourceViewResolver=new InternalResourceViewResolver();
resourceViewResolver.setPrefix("/**");
resourceViewResolver.setSuffix("/.html");
registry.viewResolver(resourceViewResolver);
registry.order(10);
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("success.html");
}
/**
* 拦截器 拦截hello请求
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/hello");
}
}