一、外部化配置(WebMvcConfigurer)
简绍
对Spring MVC的默认行为进行变更,或者进行扩展,通常要通过WebMvcConfigurer
进行配置。它定义了很多课供重写的方法,通过对方法进行重写,就可以实现我们想要的效果。
在Spring Boot 2.x之前,WebMvcConfigurerAdapter
是WebMvcConfigurer
的实现抽象类,可以通过继承WebMvcConfigurerAdapter
重写它的方法进行配置。到SpringBoot2.x时代的时候, 被标记为了@Deprecated
,通过实现WebMvcConfigurer
接口,通过重写默认方法来进行配置。总体来说,这两种配置方式基本相同。
主要配置说明
addInterceptors注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(sleuthInterceptor);
registry.addInterceptor(idempotencyOperationInterceptor)
// 拦截所有请求
.addPathPatterns("/**")
// 排除掉查询类的 POST 请求
.excludePathPatterns("/**/search/**","xx");
}
此方法用来专门注册拦截器(Interceptor),通过registry#addInterceptor
进行拦截器的注册,拦截器必须是HandlerInterceptor
的子类,在下面在 《对SpringMVC进行扩展 》会进行详细的说明。
addArgumentResolvers添加参数解析器
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
resolvers.add(new PropertiesHandlerMethodArgumentResolver());
}
此方法可以用来添加参数解析器(argumentResolver),通过resolvers#add
进行添加参数解析器。注意,此处添加的Resolver
优先级会低于系统内建的Resolver
,如果想添加优先级高于内建的Resolver
,可以通过requestMappingHandlerAdapter#setArgumentResolvers
方法进行覆盖,在下面在 《对SpringMVC进行扩展 》会进行详细的说明。
addReturnValueHandlers添加返回值处理程序
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
这个是用来配置,注意,这个配置和 addArgumentResolvers
添加参数解析器 的配置类似,添加的自定义Handler
优先级会低于系统内建的Handler
,如果想添加优先级高于内建的Handler
,需要通过requestMappingHandlerAdapter#setReturnValueHandlers
方法进行覆盖,在下面在《对SpringMVC进行扩展 》会进行详细的说明。
configureMessageConverters配置消息转换器
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = jsonConverter.getObjectMapper();
// 解决 18位 Long 类型转换成 JSON 时造成的数据丢失
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
converters.add(jsonConverter);
}
消息转换器可以对接收或返回的消息进行转换,比如解决中文乱码、json中Long精度丢失等,我们可以使用系统提供的消息转换器,或我们自定义转换器,通过这个方法converters#add
进行注册使用,会把这里添加的converter
依次放在最高优先级(List的头部)。有多个自定义的converter
时,可以改变相互之间的顺序,但是都在内置的converter
前面。
这个配置的使用场景比较常见,在下面在《对SpringMVC进行扩展 》会进行详细的说明。
extendMessageConverters扩展消息转换器
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = jsonConverter.getObjectMapper();
// 解决 18位 Long 类型转换成 JSON 时造成的数据丢失
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
converters.add(jsonConverter);riverroad
}
这个与上个 configureMessageConverters
类似,不同点在于这个方法在 configureMessageConverters
之后运行,这时系统内置的converter
已经添加完毕,此时我们同样可以可以通过改变converters
列表中的converter
实现处理顺序的变更。
addFormatters格式化配置
@Override
public void addFormatters(FormatterRegistry registry) {
}
类型转换器和格式化器,部分功能和消息转换器相似。不同它的源类型必须是一个String
, 目标类型是java类型。在下面在《对SpringMVC进行扩展 》 会进行说明。
addCorsMappings设置跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
这个是关于跨域问题的设置
addResourceHandlers自定义资源映射
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//将请求映射到指定的位置
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
}
可以设置静态资源的映射,这个在目前开发中用的不多。
configurePathMatch配置路径匹配
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 表示不区分URL的最后一个字符是否是斜杠/
configurer.setUseSuffixPatternMatch(true);
}
让开发人员可以根据需求定制URL路径的匹配规则,常用的setUseSuffixPatternMatch
方法,用来设置使用后缀模式匹配的方式,比如设置为true的话(默认为true),URL后面加个斜杠并不影响路径访问,例如“/user”等同于“/user/。如果需要定制path匹配发生的过程,可以提供自己定制的PathMatcher
和UrlPathHelper
,但是这种需求不常见。
configureViewResolvers视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
//请求视图文件的前缀地址
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
//请求视图文件的后缀
internalResourceViewResolver.setSuffix(".jsp");
registry.viewResolver(internalResourceViewResolver);
}
这个对我们来说很熟悉,配置html、Jsp页面视图时就会用到InternalResourceViewResolver
配置类,然后设置preffix
、suffix
参数进行配置视图文件路径前缀与后缀,不过现在目前开发中开发全部转向了前后端分离方式,这个已经配置几乎不会在遇到了。
二,对SpringMVC进行扩展
简绍
Spring MVC提供众多的常用的功能,基本上能满足我们日常的使用,但有时候我们会有特殊的需求,而Spring MVC没有默认的支持,此时就是需要我们对它进行扩展,下面就对Spring MVC常见的扩展方式进下介绍说明。
扩展点说明:
-
HandlerMapping
(处理请求映射)处理请求的映射。保存请求URL到具体的方法的映射关系,我们可以编写任意的
HandlerMapping
实现类,依据任何策略来决定一个web请求到HandlerExecutionChain
对象的生成。通常我们不需要对他进行扩展。 -
HandlerAdapter
(处理适配器)真正调用Controller的地方,其实就是适配各种Controller。HandlerAdapter就是你可以提供自己的实现类来处理handler对象,我们一般不会对他进行扩展。
-
HandlerInterceptor
(接口拦截器)通过自定义拦截器,我们可以在一个请求被真正处理之前、请求被处理但还没输出到响应中、请求已经被输出到响应中之后这三个时间点去做任何我们想要做的事情,这个是我们在Spring MVC中用到最多的一种扩展方式。
-
HandlerMethodArgumentResolver
(处理方法参数解释器)接收到请求参数的时候,会通过它的不同实现类对参数进行处理,通过对它进行扩展,能让我们实现对参数进行自定义操作,之前超哥写过一个自定注入Header参数到接收类的参数解释器,这个是我们对Spring MVC常用的扩展方式之一。
-
HandlerMethodReturnValueHandler
(处理方法返回值处理器)程序方法运行结束后的返回值进行处理,转换成我们所需要的格式写入返回请求。
-
Converter
(类型转换器)对数据类型进行转换,主要是用到的是
HttpMessageConverter
( http消息转换器),用来对请求和响应的数据进行处理。 -
Formatter
(格式化器)对接收到的参数进行处理和格式化,只能应用于输入为String类型的数据。
-
ViewResolver
(视图解析器)完成从
ModelAndView
到真正的视图的过程,ViewResolver
接口是在DispatcherServlet
中进行调用的,当DispatcherServlet
调用完Controller后,会得到一个ModelAndView
对象,然后DispatcherServlet
会调用render方法进行视图渲染。在目前前后端分离的情况下,这个我们一般不会进行扩展。 -
HandlerExceptionResolver
(异常处理)用不到,略
HandlerInterceptor (接口拦截器)
HandlerInterceptor
是一个接口,用过实现这个接口,我们可以实现出一个自定义的拦截器,这个接口有三个方法,如下图所示:
public interface HandlerInterceptor {
//在业务处理器处理请求之前被调用,其返回值表示是否中断后续操作
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception{
}
// 在业务处理器处理请求完成之后,生成视图之前执行
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception{
}
// 在DispatcherServlet完全处理完请求之后被调用,可用于清理资源,日志打印
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception{
}
}
最后将拦截器通过WebMvcConfigurer
配置类中的 addInterceptors
方法进行注册,即可生效。
HandlerMethodArgumentResolver(处理方法参数解释器)
处理方法参数解释器可以对方法参数进行处理,通过它可以获取该方法参数上的一些信息 ,如方法参数中的注解信息等,根据获取到的信息对参数数据进行处理。通过实现这个接口或继承它的实现类,就可以实现一个自定义的处理方法参数解释器,然后将Resolver
通过WebMvcConfigurer
配置类中的 addArgumentResolvers
方法进行注册,即可生效。
package org.springframework.web.method.support;
public interface HandlerMethodArgumentResolver {
//判断是否使用这个组件
boolean supportsParameter(MethodParameter parameter);
//对参数进行处理
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
在Spring MVC中,系统已经提供了多种不同用处的 处理方法参数解释器 ,当有请求到来时,系统首先会从系统提供的解释器中寻找合适的Resolver
,如果匹配到,才会查找注册的自定义实现Resolver
,所以通常我们要创建自定义注解放在要处理的参数上,方便使用自定义的Resolver
进行处理。
选择 Resolver
进行处理的流程:
// class:HandlerMethodArgumentResolverComposite
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
//先从缓存中查找
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
// 未找到默认的Resolver
if (result == null) {
//遍历Resolver
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
//判断是否支持此Resolver
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
//写入缓存
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
如果我们确实需要配置高于系统内置的自定义Resolver
的时候,可以通过如下的方式进行配置:
在WebConfig
配置类中,通过@PostConstruct
注解在一个方法上,可以让这个方法在Bean依赖注入完成后被自动调用,然后在这个方法里对 Resolver
集合进行重新设置,就可以实现将自定义 Resolver
优先级提升到内置的之前了。
@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@PostConstruct
public void init() {
// 获取当前 RequestMappingHandlerAdapter 所有的 Resolver 对象
List<HandlerMethodArgumentResolver> resolvers =
requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> newResolvers =
new ArrayList<>(resolvers.size() + 1);
// 添加 PropertiesHandlerMethodArgumentResolver 到集合首位
newResolvers.add(new PropertiesHandlerMethodArgumentResolver());
// 添加 已注册的 Resolver 对象集合
newResolvers.addAll(resolvers);
// 重新设置 Resolver 对象集合
requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
}
}
题外话:可以通过实现自定义自动把Tid一类信息注入到参数里,下面用一个简单的demo进行展示:
按需把请求头参数写入方法参数的Spring MVC扩展处理器
HandlerMethodReturnValueHandler(处理方法返回值处理程序)
HandlerMethodReturnValueHandler
可以用来对程序方法运行结束后的返回值进行处理,转换成我们所需要的格式并返回。
package org.springframework.web.method.support;
public interface HandlerMethodReturnValueHandler {
//检验是否支持本处理器处理
boolean supportsReturnType(MethodParameter returnType);
//具体处理方法
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception;
}
与HandlerMethodArgumentResolver
一样,系统已经提供了多种不同的 Handler
了,自定义的Handler
添加进去优先级都会在内置Handler
之后。常规配置的方式通过 WebConfig
进行配置,如果要配置高于系统内置的自定义 Handler
时,可以参照下方的配置方式,与参数解释器的配置方式基本相同。
@Configuration
public class RestWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@PostConstruct
public void init() {
// 获取当前 HandlerMethodReturnValueHandler 所有的 Handler 对象
List<HandlerMethodReturnValueHandler> handlers =
requestMappingHandlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newHandlers =
new ArrayList<>(handlers.size() + 1);
// 添加 PropertiesHandlerMethodReturnValueHandler 到集合首位
newHandlers.add(new PropertiesHandlerMethodReturnValueHandler());
// 添加 已注册的 Handler 对象集合
newHandlers.addAll(handlers);
// 重新设置 Handler 对象集合
requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
}
}
选择 Handler
进行处理的流程:
// class: HandlerMethodReturnValueHandlerComposite
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
//遍历Handler
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
//找到并返回
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
HttpMessageConverter(Http消息转换器)
HttpMessageConveter
是用来处理请求和响应数据的,我们经常会用到@RequestBody
和@ResponseBody
,通过这两个注解,可以在 Controller 中直接使用 Java 对象作为请求参数和返回内容,而完成这之间转换作用的便是HttpMessageConverter
。Spring 为我们内置了大量的HttpMessageConverter
,例如, MappingJackson2HttpMessageConverter
、StringHttpMessageConverter
等。
已包含常用的消息转换器:
名称 | 作用 | 读支持MediaType | 写支持MediaType |
---|---|---|---|
MappingJackson2HttpMessageConverter | 使用Jackson的ObjectMapper转换Json数据 | application/json | application/json |
StringHttpMessageConverter | 数据与String类型的相互转换 | text/* | text/plain |
ByteArrayHttpMessageConverter | 数据与字节数组的相互转换 | / | application/octet-stream |
下面是HttpMessageConverter
接口,实现Http消息转换就必须实现这个接口,不过我们通常不会直接实现这个接口,而是通过继承它的子类进行扩展处理,默认提供的Converter
对视通过继承它的抽象类进行扩展。
public interface HttpMessageConverter<T> {
//判断是否对接收请求进行处理
boolean canRead(Class<?> clazz, MediaType mediaType);
//判断是否对响应进行处理
boolean canWrite(Class<?> clazz, MediaType mediaType);
//返回此转换器支持的MediaType对象列表
List<MediaType> getSupportedMediaTypes();
//对接收的请求进行处理
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
//对响应进行处理
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
通常被继承的抽象类是AbstractHttpMessageConverter
,例如常用的MappingJackson2HttpMessageConverter
就是对它的继承,这样能减少编写处理方法的工作量,如果我们要进行扩展,通常会在系统已有Converter
上进行配置,少数时候会进行继承扩展。
配置方式见上方 configureMessageConverters
配置消息转换器和 extendMessageConverters
扩展消息转换器的说明。
注:目前在目前开发中的开发中,对请求的消息和响应进行完全自定义的场合不多,多数都是对已有的MessageConverter
进行设置和增强。
Formatter(格式化器)
Formatter
和Converter
类似, 是将一种类型转换成另一种类型, 但是, Formatter
的源类型必须是一个String
, 目标类型是java类型。在SpringMVC中,处理的多数输入都是文本方式的输入,因此, 选择Formatter
比选择Converter
更合适。
Formatter接口的结构如下:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
public interface Printer<T> {
String print(T var1, Locale var2);
}
public interface Parser<T> {
T parse(String var1, Locale var2) throws ParseException;
}
这里的T表示输入字符串要转换的目标类型。parse方法利用指定的Locale将一个String解析成目标类型。print方法相反,它是返回目标对象的字符串表示法。
配置方式见上方addFormatters
。