WebMvcConfigurer技术内幕
一、前言
在Spring 5后,官方废弃了WebMvcConfigurerAdapter
。因此,Springboot 2.x后该类被标记为@Deprecated,Springboot官方也重新给出了两种实现自定义配置类的方式:
- 实现
WebMvcConfigurer
接口 (本文重点) - 继承
WebMvcConfigurationSupport
类
在Spring的WebMvcConfigurer
接口中,为我们提供了很多的方法去自定义SpringMVC的配置,使用者只需根据需求,让自定义配置类去实现相应的方法即可。
那WebMvcConfigurer
接口是何许人
也呢?WebMvcConfigurer接口是Spring内部的提供给使用者自定义配置的一种方式,采用JavaBean
的形式来代替传统的xml配置文件形式进行针对框架和业务个性化定制。是基于java-based
方式的Spring MVC
配置,只需要创建一个配置类并实现WebMvcConfigurer
接口即可。而弃用的WebMvcConfigurerAdapter
抽象类也是对WebMvcConfigurer
接口的简单抽象,只是增加了一些默认实现。
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {....}
二、WebMvcConfigurer源码分析
public interface WebMvcConfigurer {
/** HandlerMappings 路径匹配选项配置,可自定义访问路径的匹配规则 */
default void configurePathMatch(PathMatchConfigurer configurer) {}
/** 内容协商配置 */
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
/** 异步请求支持配置,只能设置两个值:超时时间(毫秒)和AsyncTaskExecutor(异步任务执行器) */
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
/** 默认静态资源处理器 */
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
/** 注册自定义转化器*/
default void addFormatters(FormatterRegistry registry) {}
/** 拦截器配置 */
default void addInterceptors(InterceptorRegistry registry) {}
/** 资源处理 */
default void addResourceHandlers(ResourceHandlerRegistry registry) {}
/** CORS(跨域资源共享)配置 */
default void addCorsMappings(CorsRegistry registry) {}
/** 视图跳转控制器 */
default void addViewControllers(ViewControllerRegistry registry) {}
/** 配置视图解析 */
default void configureViewResolvers(ViewResolverRegistry registry) {}
/** 添加自定义方法参数处理器 */
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
/** 添加自定义返回结果处理器 */
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}
/** 配置消息转换器,重载会覆盖默认注册的HttpMessageConverter */
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
/** 配置消息转换器,仅添加一个自定义的HttpMessageConverter. */
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
/** 配置异常转换器 */
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
/** 添加异常转化器 */
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
/** 获取校验器 */
@Nullable
default Validator getValidator() {return null;}
/** 获取消息码处理器 */
@Nullable
default MessageCodesResolver getMessageCodesResolver() {return null;}
}
2.1、configurePathMatch
实现configurePathMatch方法,可自定义的path
的匹配规则 参考:Springboot官网学习 7
public void configurePathMatch(PathMatchConfigurer configurer) {
// 是否存在尾/来进行匹配 如:/user和/user/等效的,同样可以进行匹配
configurer.setUseTrailingSlashMatch(true);
// 这个配置需要传入一个UrlPathHelper对象,UrlPathHelper是一个处理url地址的帮助类
// 它里面有一些优化url的方法,比如:
// getSanitizedPath,就是将// 换成/ ,所以我们在输入地址栏的时候,//也是没有问题的,默认即支持
UrlPathHelper urlPathHelper = new UrlPathHelper();
configurer.setUrlPathHelper(urlPathHelper);
// 路径匹配器 PathMatcher是一个接口,springmvc 默认使用的是AntPathMatcher,可根据需要配置 PathMatcher(路径匹配器)
//configurer.setPathMatcher();
// 配置路径前缀:对含有AdminController注解的controller添加/admin地址前缀,对含有AppController注解的controller添加/app地址前缀
configurer.addPathPrefix("admin", c -> c.isAnnotationPresent(AdminController.class));
configurer.addPathPrefix("app", c -> c.isAnnotationPresent(AppController.class));
// 配置路径前缀:分包形式添加
// configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.admin"));
// configurer.addPathPrefix("app", c -> c.getPackage().getName().contains("com.osy.controller.app"));
}
2.2、configureContentNegotiation
configureContentNegotiation:内容协商配置
????什么是内容协商呢?很迷惑吧?第一次看到时,我也头大了。我们知道服务端可以以多种形式进行响应:即MIME(MediaType)
媒体类型(如JSON、XML、cbor)。但对于某一个客户端(浏览器、APP…)来说它只需要一种响应形式。所以,这样就需要在客户端和服务端有一种机制来保证统一的响应形式,而这种机制就是内容协商机制。通俗来说,就是统一客户端与服务器端的响应格式的一种机制。参考文章
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
/* 是否通过请求Url的扩展名来决定media type */
configurer.favorPathExtension(true)
/* 是否检查Accept请求头 */
.ignoreAcceptHeader(true)
/**
* 通过什么样的参数来获取返回值,如:
* http://localhost:8080/login/getMediaType?mediaType=json
*/
.parameterName("mediaType")
/* 设置默认的mediatype媒体类型 */
.defaultContentType(MediaType.TEXT_HTML)
/* 请求以.html结尾的会被当成MediaType.TEXT_HTML*/
.mediaType("html", MediaType.TEXT_HTML)
/* 请求以.json结尾的会被当成MediaType.APPLICATION_JSON*/
.mediaType("json", MediaType.APPLICATION_JSON);
}
2.3、configureDefaultServletHandling和addResourceHandlers
在Spring MVC中,当项目访问静态资源(如.html.js.css等)时,是不需要经过servlet的,但是如果配置了DispatcherServlet核心servlet的url-pattern为"/xx",这就导致所有的请求都会经过DispatcherServlet。因此,在项目中就需要单独对静态资源处理进行配置。参考文章
WebMvcConfigurer
配置静态资源处理有两种种方式:
/**
* 方式一:使用默认servlet处理静态资源
*/
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
//启用默认servlet支持
configurer.enable();
}
/**
* 方式二:使用spring mvc处理静态资源
* @param registry
*/
// 可通过配置文件来配置静态文件路径
@Value("${file.staticFilePath}")
private String filePath;
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 默认的资源映射需要填写,不然不能正常访问
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
//配置外部资源目录的映射,/image目录为前端访问的路径,后面配置静态资源的绝对路径
registry.addResourceHandler("/image/**").addResourceLocations("file:"+uploadFolder);
//调用基类的方法
super.addResourceHandlers(registry);
}
2.4、addFormatters
可以自定义转化器与数据库做交互或者可以将时间按照要求配置格式,比如URL
传如userName
和userId
,经过转化器可以拿到一个User
对象。参考文章
2.5、addInterceptors
较为常用的方法,可自定义编写拦截器,并指定拦截路径。参考文章
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle,请求执行前执行");
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle,请求结束后执行");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion,整个请求(DispatcherServlet渲染了视图执行)结束后执行");
}
}
然后编写自定义配置类,如下:
@Configuration
public class MyConfigurer implements WebMvcConfigurer {
@Bean
public MyInterceptor getMyInterceptor(){
return new MyInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.getMyInterceptor())
.addPathPatterns("/login","/configurePathMatch");
}
}
运行结果:
preHandle,请求执行前执行
执行业务需求
postHandle,请求结束后执行
afterCompletion,整个请求(DispatcherServlet渲染了视图执行)结束后执行
2.6、addReturnValueHandlers
当使用者在需要Controller
的返回对象作统一的封装时,可以配置addReturnValueHandlers
方法,返回统一格式。ResponseBody,Convertor或者View,也可以实现。参考文章
/**
* 统一的结果封装类 ResultInfo,并序列化成json
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
ResultInfo resultInfo = new ResultInfo<>(ResultInfo.OK, "success", returnValue);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.addHeader("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
response.getWriter().append(JSON.toJSONString(resultInfo));
}
2.7、extendHandlerExceptionResolvers
在项目中,我们无可避免的会出现异常,而且这些异常并没有进行捕获,经常出现的bug如空指针异常等等。那么我们就需要有对异常的全局处理,进行兜底
处理,从而避免不可预料的问题。extendHandlerExceptionResolvers
方法可以添加自定义的或者修改已配置默认的HandlerExceptionResolver
。参考文章
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
// 自定义异常处理器一般请放在首位
exceptionResolvers.add(0, new AbstractHandlerExceptionResolver() {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
ModelAndView mv = new ModelAndView();
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setJsonPrefix("fsxJson"); // 设置JSON前缀,有的时候很好用的哦
//view.setModelKey(); // 让只序列化指定的key
mv.setView(view);
// 这样添加key value就非常方便
mv.addObject("code", "100001");
mv.addObject("message", "业务异常,请联系客服处理");
return mv;
}
});
}