文章目录
1. WebMvcConfigurationSupport、WebMvcConfigurer 区别
springboot中我们通过继承WebMvcConfigurerAdapter进行springmvc相关的配置,如拦截器、消息转换、视图解析器等。在springboot2.0后,该接口被废弃,官方推荐直接implements WebMvcConfigurer ,或者extends WebMvcConfigurationSupport。
查看源码发现@EnableWebMvc实际上引入了一个继承WebMvcConfigurationSupport的DelegatingWebMvcConfiguration。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
然后查看WebMvcAutoConfiguration的源码发现:
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
...
}
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})意思是如果存在它修饰的类的bean,则不需要再创建这个bean。由此可得出结论:如果有bean继承了DelegatingWebMvcConfiguration,WebMvcConfigurationSupport,或者开启了@EnableWebMvc,那么 @EnableAutoConfiguration 中的WebMvcAutoConfiguration 将不会被自动配置,而是使用自定义的WebMvcConfigurationSupport的配置。
总结
推荐前两种方式
-
implements WebMvcConfigurer : 不会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置。
-
extends WebMvcConfigurationSupport :会覆盖@EnableAutoConfiguration关于WebMvcAutoConfiguration的配置。注意:使用WebMvcConfigurationSupport类配置拦截器时一定要重写addResourceHandlers来实现静态资源的映射
-
@EnableWebMvc : 等于扩展了WebMvcConfigurationSupport但是没有重写任何方法。
-
extends WebMvcConfigurerAdapter spring2.0不再建议使用,被废弃。官方推荐直接实现WebMvcConfigurer 。
2. 自定义消息转换器MessageConverters
以下配置代码用于使用LocalDate,LocalTime ,LocalDateTime属性接收前端传来的标准时间字符串,并返回指定的时间格式,LocalDate对应 yyyy-MM-dd
,LocalDateTime对应yyyy-MM-dd HH:mm:dd
,LocalTime对应 HH:mm:dd
,不论参数的形式传递如body、pathVariable、requestParam里,只要时间字符串满足格式要求都可以转为对应的时间日期属性。
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.jun.cloud.common.util.DateUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* WebMvc配置:
* 1.消息转换器的配置,提供Jackson的支持
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//对外提供的api接口验证及追踪日志
//registry.addInterceptor(new RestApiInterceptor()).addPathPatterns("/api/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//添加对swagger-ui的处理
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
//registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/static/");
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
/**
* 注意这里使用的是extendMessageConverters,这个方法不会覆盖springmvc已默认添加的HttpMessageConverter
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(customerMappingJackson2HttpMessageConverter());
converters.add(stringHttpMessageConverter());
}
@Bean
public MappingJackson2HttpMessageConverter customerMappingJackson2HttpMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(customDateObjectMapper());
return messageConverter;
}
@Bean
public StringHttpMessageConverter stringHttpMessageConverter(){
StringHttpMessageConverter stringConvert = new StringHttpMessageConverter();
List<MediaType> stringMediaTypes = new ArrayList<MediaType>(){{
add(new MediaType("text","plain",Charset.forName("UTF-8")));
}};
stringConvert.setSupportedMediaTypes(stringMediaTypes);
return stringConvert;
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(localDateTimeConverter());
registry.addConverter(localDateConverter());
registry.addConverter(localTimeConverter());
}
/**
* Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json
*/
@Bean
public ObjectMapper customDateObjectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//LocalDateTime系列序列化和反序列化模块,继承自jsr310,这里修改了日期格式
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_yyyy_MM_dd_HH_mm_ss)));
javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_yyyy_MM_dd)));
javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_HH_mm_ss)));
javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_yyyy_MM_dd_HH_mm_ss)));
javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_yyyy_MM_dd)));
javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_HH_mm_ss)));
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
/**
* LocalDate转换器,用于转换RequestParam和PathVariable参数
*/
private Converter<String, LocalDate> localDateConverter() {
return new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String source) {
if(!ObjectUtils.isEmpty(source)){
return LocalDate.parse(source, DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_yyyy_MM_dd));
}
return null ;
}
};
}
/**
* LocalDateTime转换器,用于转换RequestParam和PathVariable参数
*/
private Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String source) {
if(!ObjectUtils.isEmpty(source)) {
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_yyyy_MM_dd_HH_mm_ss));
}
return null ;
}
};
}
/**
* LocalTime转换器,用于转换RequestParam和PathVariable参数
*/
private Converter<String, LocalTime> localTimeConverter() {
return new Converter<String, LocalTime>() {
@Override
public LocalTime convert(String source) {
if(!ObjectUtils.isEmpty(source)) {
return LocalTime.parse(source, DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT_HH_mm_ss));
}
return null;
}
};
}
}
注意implements WebMvcConfigurer后,要使用extendMessageConverters
方法,这里通过@Bean的方式注入,也可以直接new一个如:
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(customDateObjectMapper());
converters.add(messageConverter);
converters.add(stringHttpMessageConverter());
}
3. 静态资源
3.1 静态资源位置
默认情况下,Spring Boot从classpath下的/static(/public,/resources或/META-INF/resources)文件夹,或从ServletContext根目录提供静态内容。这是通过Spring MVC的ResourceHttpRequestHandler实现的,你可以自定义WebMvcConfigurerAdapter并覆写addResourceHandlers方法来改变该行为(加载静态文件),即上面的
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
//添加对swagger-ui的处理
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
你可以设置spring.resources.staticLocations属性自定义静态资源的位置(配置一系列目录位置代替默认的值),如果你这样做,默认的欢迎页面将从自定义位置加载,所以只要这些路径中的任何地方有一个index.html,它都会成为应用的主页。
此外,除了上述标准的静态资源位置,有个例外情况是Webjars内容。任何在/webjars/**路径下的资源都将从jar文件中提供,只要它们以Webjars的格式打包。
3.2 错误页面
如果想为某个给定的状态码展示一个自定义的HTML错误页面,你需要将文件添加到/error文件夹下。错误页面既可以是静态HTML(比如任何静态资源文件夹下添加的),也可以是使用模板构建的,文件名必须是明确的状态码或一系列标签。
例如,映射404到一个静态HTML文件,你的目录结构可能如下:
src/
main/
java/
<source code>
resources/
public/
error/
404.html
<other public assets>
使用FreeMarker模板映射所有5xx错误,你需要如下的目录结构:
src/
main/
java/
<source code>
resources/
templates/
error/
5xx.ftl
<other templates>
对于更复杂的映射,你可以添加实现ErrorViewResolver接口的beans:
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
return ...
}
}
你也可以使用Spring MVC特性,比如@ExceptionHandler方法和@ControllerAdvice,ErrorController将处理所有未处理的异常。