1. AOP
利用环绕通知,对包含@RequestMapping注解的方法统一处理
优点:配置简单、可捕获功能方法内部的异常
缺点:aop不能修改返回结果的类型,因此功能方法的返回值须统一为Object类型
2. 过滤器
在过滤器层统一处理
优点:配置简单
缺点:无法识别异常结果,须对返回结果进行额外的反序列化
3. 拦截器
获取返回值不方便,且无法获取到String类型的返回值,无法实现该功能
4. HandlerMethodReturnValueHandler
无上述各方法的缺点,且能复用@ResponseBody等注解
SpringMVC流程
在SpringMVC流程中,在调用 HandlerAdapter 的 处理方法的时候 会跳转调用到 RequestMappingHandlerAdapter 的 handleInternal 方法。这里面会将 原本的处理器 HandlerMethod 封装成 ServletInvocableHandlerMethod,然后会调用这个类中的 invokeAndHandle 方法,这个方法中主要进行了相应方法处理器的方法的调用,在调用完成后,会处理返回值。
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
在handleReturnValue方法中,会查找合适的处理器Processor,并通过supportsReturnType方法判断是否支持。
若使用了@ResponseBody注解的接口,都会被RequestResponseBodyMethodProcessor处理。所以我们可以在该处理器处理之前,将返回值修改成统一的JSON格式。
具体实现
在初始化ResponseBodyWrapBean时,获取所有的SpringMVC的内部HandlerMethodReturnValueHandler实现,并将RequestResponseBodyMethodProcessor替换为自定义实现ApiReturnValueHandler,并在ApiReturnValueHandler保存RequestResponseBodyMethodProcessor的实例。
@Component
public class ResponseBodyWrapBean implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter adapter;
@Value("${server.servlet.context-path:/}")
private String contextPath;
@Override
public void afterPropertiesSet() throws Exception {
List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers);
// 在handlers中将RequestResponseBodyMethodProcessor替换为ApiReturnValueWrapHandler,
// 并在ApiReturnValueWrapHandler保存RequestResponseBodyMethodProcessor
for (HandlerMethodReturnValueHandler handler : handlers) {
if (handler instanceof RequestResponseBodyMethodProcessor) {
ApiReturnValueHandler apiReturnValueHandler = new ApiReturnValueHandler(handler,contextPath);
int index = handlers.indexOf(handler);
handlers.set(index, apiReturnValueHandler);
break;
}
}
adapter.setReturnValueHandlers(handlers);
}
}
ApiReturnValueHandler实现HandlerMethodReturnValueHandler接口,并在类里面保存RequestResponseBodyMethodProcessor的实例,使用RequestResponseBodyMethodProcessor的supportsReturnType原实现,即判断类或方法是否有@ResponseBody注解。如果有,就会执行该handleReturnValue方法。
在handleReturnValue方法中将返回值包装后,就继续按RequestResponseBodyMethodProcessor的原实现继续执行。
public class ApiReturnValueHandler implements HandlerMethodReturnValueHandler {
// 因为RequestResponseBodyMethodProcessor被替换,所以将其保存下来,处理后在调用原逻辑
private final HandlerMethodReturnValueHandler handler;
private final String contextPath;
public ApiReturnValueHandler(HandlerMethodReturnValueHandler handler, String contextPath) {
this.handler = handler;
this.contextPath = contextPath;
}
/**
* 判断是否支持值处理引擎
* 一定要正确,如果不正确,就永远不会被执行
* @param returnType
* @return
*/
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return handler.supportsReturnType(returnType);
}
/**
* 对值进行处理,并确定是否继续进行下一个处理引擎执行
* @param returnValue
* @param returnType
* @param mavContainer
* @param webRequest
* @throws Exception
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
String uri = ((HttpServletRequest) webRequest.getNativeRequest()).getRequestURI();
String apiUrl = contextPath + "/api";
if (uri.startsWith(apiUrl)) {
// 将api开头的url都包装成统一返回
Map<String, Object> result = new HashMap<>(3);
result.put("code", 0);
result.put("message", "ok");
result.put("data", returnValue);
handler.handleReturnValue(result, returnType, mavContainer, webRequest);
} else {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
}
测试:
5. ResponseBodyAdvice
无上述各方法的缺点,且能复用@ResponseBody等注解。
ResponseBodyAdvice方法是在执行完HandlerMethodReturnValueHandler之后执行,全局捕获的异常返回,也会经过该方法。
借助@RestControllerAdvice
和ResponseBodyAdvice<T>
来对项目的每一个@RestController
标记的控制类的响应体进行后置切面通知处理。
@RestControllerAdvice
public class ResponseBodyHandler implements ResponseBodyAdvice<Object> {
@Value("${server.servlet.context-path:}")
private String contextPath;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
} else {
String apiUrl;
if (contextPath != null && contextPath != "") {
apiUrl = contextPath + "/api";
} else {
apiUrl = "/api";
}
HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest();
String url = httpServletRequest.getRequestURI();
// api开头的url才进行包装
if (url.startsWith(apiUrl)) {
// String类型特别处理,防止发生类型转换异常
if (body instanceof String) {
return JSON.toJSONString(Result.okData(body));
} else {
return Result.okData(body);
}
} else {
return body;
}
}
}
}
统一返回类
public class Result<T> {
private Integer code = 200;
private String message = "ok";
private T data;
public static Result<?> ok() {
return new Result<>();
}
public static <T> Result<T> okData(T data) {
Result<T> result = new Result<>();
result.setData(data);
return result;
}
public static <T> Result<T> okData(T data, String message) {
Result<T> result = new Result<>();
result.setMessage(message);
result.setData(data);
return result;
}
public static <T> Result<T> build(Integer code, T data, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
public static Result<?> failure(Integer code, String message) {
Result<?> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
// getter and setter
参考:
自定义统一api返回json格式(app后台框架搭建三)
Spring MVC 使用介绍(十二)控制器返回结果统一处理
SpringMVC 流程
从零搭建Spring Boot脚手架(2):增加通用的功能
Springboot使用了ResponseBodyAdvice处理返回值异常?