1,需求
需要统一回包格式,用比较优雅的方式处理。也就是返回格式为{code, data, msg}。
2,前言
这个需求的暴力实现是使用拦截器HandlerInterceptorAdapter,配合自定义注解。但是不够优雅,后面一直有在看这个,但是奈何没有找到合适的方法。虽然已经发现实现HandlerMethodReturnValueHandler接口,添加支持类型并更改回包,然后在添加到RequestMappingHandlerAdapter中去生效。但是一顿操作之后,发现这个自定义的返回值处理器没有生效,甚至原因也找到了,自定义的优先级太低,spring有默认的returnValueHandler,并且处理逻辑是只会被一个处理器处理。所以就失败了。虽然后边想着可以用反射或者是继承RequestMappingHandlerAdapter这个类,然后重新注册为Bean,但是这样更不优雅,因此就停滞了。直到昨天又看见了这个没有完成的实验,因此就来看了下。
3,分析
使用ModelAndViewMethodReturnValueHandler的原因是我们会使用@RestController或者是@ResponseBody告诉Spring返回的是ModelAndView,接口返回为Json。那我们可以使用@Controller替代@RestController。然后再配合我们自定义的返回值处理器。就可以实现返回值类型的封装。
4,实验开始:
(4.1)这里是RequestMappingHandlerAdapter的获取返回值处理器的方法。这里会加入我们自定义的返回值处理器
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
// 第一个放进来的是ModelAndViewReturnValueHandler,并且处理的逻辑是,被一个处理后,就不会给其他的returnValueHandler来处理。
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
this.reactiveRegistry, this.taskExecutor, this.contentNegotiationManager));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}// 我们自定义的处理器在这里,优先级很低,所以执行不到
// Catch-all
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
}
else {
handlers.add(new ModelAttributeMethodProcessor(true));
}
return handlers;
}
(4.2)这个是我们自定义的回包格式,如果回包格式不统一的话,需要包装一下。
public class ReturnValueHandlerTest implements HandlerMethodReturnValueHandler {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supportsReturnType(MethodParameter returnType) {
RequestMappingTest annotation = returnType.getAnnotatedElement().getDeclaredAnnotation(RequestMappingTest.class);
// 实际上,这里的注解可是自定义,我这里时为了方便使用的路由映射注解
return annotation != null;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true);// 这一步标识这个request已经被处理过了,不然会重复处理。这个很重要的
Object result = returnValue;
if (!(returnValue instanceof CResponse)) {// 这里判断一下回包是不是通用的格式,如果不是,需要封装
result = new CResponse<>(returnValue);
}
HttpServletResponse nativeResponse = webRequest.getNativeResponse(HttpServletResponse.class);
assert nativeResponse != null;
byte[] bytes = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(result);
nativeResponse.resetBuffer();
nativeResponse.getOutputStream().write(bytes);// 这里直接使用流写出
nativeResponse.getOutputStream().flush();
}
}
(4.3)添加到RequestMappingHandlerAdapter中去
@Configuration
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class WebConfig implements WebMvcConfigurer {
@Bean
public HandlerMapping handlerMappingTest() {
HandlerMappingHandlerTest handlerTest = new HandlerMappingHandlerTest();
handlerTest.setInterceptors(new InterceptorTest());
return handlerTest;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new InterceptorTest());
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
handlers.add(new ReturnValueHandlerTest());//添加到RequestMappingHandlerAdapter中
}
}
5,结果验证
@Controller
@RequestMappingTest("/1")
@RequestMapping("/2")// 这两个注解可以同时放在这里生效
public class ControllerMappingTest {
@RequestMappingTest("/test1")
@ResponseBody
public Object test1(@ModelAttribute A a, @ModelAttribute B b) {
System.out.println(a.getId());
System.out.println(b.getId());
return CResponse.of(true);
}
@RequestMappingTest("/test2")
public Object test2(@ModelAttribute A a, @ModelAttribute B b) {
System.out.println(a.getId());
System.out.println(b.getId());
return true;//这里的返回值为true,不是CResponse的格式,需要包装
}
@RequestMapping("/test3")
@ResponseBody// 如果要使用ModleAndViewReturnValueHandler,需要加这个,不然出错。
public Object test(@ModelAttribute A a, @ModelAttribute B b) {
System.out.println(a.getId());
System.out.println(b.getId());
return CResponse.of(true);
}
}
结果:
可以看见成功被包装为通用格式了。
5,后续
demo连接,这里是和自定义路由注解揉在一块儿的,看起来可能不够整洁,但好在整体比较简单。大家可以自己动手试一试。
这一套写法还是比较符合Spring的规范的。