关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言
上一期我们介绍了addCorsMappings、addViewControllers以及configureViewResolvers,分别用来处理跨域、视图转发控制以及视图解析。我们继续WebMvcConfigurer配置的分享,这一期了解两个方法:
addArgumentResolversaddReturnValueHandlers
分别用来处理参数解析以及返回值的处理。
02 方法11
addArgumentResolvers
java
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
作用:添加自定义方法参数解析器。
使用场景:
- 解析自定义注解参数
- 统一用户信息提取
- 特殊参数类型处理
2.1 使用说明
这个方法用起来比较简单,可以针对Web提交的数据,进行预处理,处理成符合预期的参数。也可以通过传递的参数,过滤掉垃圾数据,防止无意义的数据在网络传播或者入库。
通过方法我们可以看到,需要我们定义一个HandlerMethodArgumentResolver即可,然后放到集合中去,由Spring统一管理。

supportsParameter:处理参数的条件resolveArgument:重新封装参数
2.2 自定义参数解析器
java
@Slf4j
public class WjsonArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(
MethodParameter parameter) {
log.info("supportsParameter: ...");
return parameter.getParameterType()
.equals(Wjson.class);
}
@Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory)
throws Exception {
log.info("resolveArgument: ...");
String content = webRequest.getParameter("content");
return new Wjson(content, "v1");
}
}
定义的方法通过判断接收的参数类型是不是Wjson(自定义的类),如果是,就处理参数,额外在参数增加版本号v1。
2.3 配置
java
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new WjsonArgumentResolver());
}
2.4 控制层与测试
java
@GetMapping("/testWjson")
public void testWjson(Wjson wjson) {
log.info("testWjson: wjson={} ", wjson);
}
传递参数时,无论版本号传或者不传,都会被处理成v1。
测试结果:

03 方法12
addReturnValueHandlers
java
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
作用:添加自定义返回值处理器。
使用场景:
- 统一响应格式封装
- 特殊返回值类型处理
- 响应数据加密
3.1 使用说明
这个方法配置方式完全和addArgumentResolvers相同,但是在定义过程中可能会会被系统自带的HandlerMethodReturnValueHandler优先取代。这个我们在后面会解释原因。
我们同样需要实现HandlerMethodReturnValueHandler处理自己的响应结果。
3.2 自定义返回值处理器
java
@Slf4j
public class WjsonReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
log.info("supportsReturnType 。。。。。");
return returnType.getParameterType()
.equals(Wjson.class);
}
@Override
public void handleReturnValue(
Object returnValue,
MethodParameter returnType,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest)
throws Exception {
log.info("handleReturnValue 。。。。。");
// 标记请求已处理,避免视图解析
mavContainer.setRequestHandled(true);
HttpServletResponse response = webRequest
.getNativeResponse(HttpServletResponse.class);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write("<h1>测试</h1>");
}
}
假设我们要拦截返回值为Wjson的方法,统一跳转自定义页面。其中mavContainer.setRequestHandled(true);非常重要,表示请求已经处理,不会再次被框架处理。
3.3 配置
java
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
handlers.add(new WjsonReturnValueHandler());
}
3.4 控制层与测试
java
@GetMapping("/testWjsonReturn")
public Wjson testWjsonReturn() {
return new Wjson("test", "v2");
}
注意这里的方法没有加@ResponseBody注解,加了的话会导致配置不生效。
测试结果:

3.5 注意事项
当我们的返回值为Void.class、String.class或者加了@ResponseBody注解,我们的配置就不会生效了。这是小编测试时遇到的最大问题。
源码的关键类:
RequestMappingHandlerAdapterHandlerMethodReturnValueHandlerComposite
通过源码追踪发现:

自定义的WjsonReturnValueHandler排在后面,前面的有RequestResponseBodyMethodProcessor,专门处理@ResponseBody注解。

ViewNameMethodReturnValueHandler专门处理返回值为Void.class和String.class的方法:

知道了原因,下面就是如何解决?
解决的思路很简单,就是将自定义的处理器放在前面即可。通过list.add(0, xx)是行不通的,因为配置的List是定制的处理器。

而定制的处理器会加载在框架默认的之后。

所以我们要拿到处理器的集合并将我们自定的加载在前民就好了。继续跟踪源码:

发现获取的处理器集合是个不可变结合,无法做添加处理。那我们就获取到框架自带的处理器,重新设置到RequestMappingHandlerAdapter里面即可。
最终方法
java
@Autowired
private RequestMappingHandlerAdapter handlerAdapter;
@PostConstruct
public void init() {
List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> list = new ArrayList<>(returnValueHandlers);
list.add(0, new WjsonReturnValueHandler());
handlerAdapter.setReturnValueHandlers(list);
}
这种处理方法,无需再使用addReturnValueHandlers处理了。如图,已经加载了最前面。

04 小结
关于类似的配置下一期还会介绍两个。集合的配置能不能通过list.add(0, xxx),取决于框架的取值。所以当我们添加的配置不是能生效时,多半原因是和框架自定义的冲突导致的。需要我们结合源码,调整顺序就可以解决。这一期就介绍到这里了。