一般来说,提供给终端调用的API接口在Controller都会直接使用@ResponseBody来进行注解。此时如果我们想要在其他地方来获取方法的返回值,并做一些操作,此处以保存日志为例。每一个接口返回的都是Result类型,大概如下:
@RequestMapping(value="a")
@ResponseBody
public Result a(){
...
return result;
}
@RequestMapping(value="b")
@ResponseBody
public Result b(){
...
return result;
}
Result类含有返回码code,消息msg,和一个保存数据的Map
public class Result implements Serializable{
private static final long serialVersionUID = 1L;
//返回码
private int code;
//消息提示
private String msg;
//数据
private transient Map<String, Object> data = new HashMap<>();
private Result(){
}
...
}
我们需要获取每个方法的返回码,异常信息(如有)等信息保存到数据库,直接使用拦截器是无法获取到ResponseBody的返回值的,因为实现 HandlerInterceptor的三个方法preHandle、postHandle以及afterCompletion都无法获取到我们想要的返回值。当然这个日志操作我们可以在Controller的每个方法最后进行调用,比如这样:
@RequestMapping(value="a")
@ResponseBody
public Result a(){
...
saveLog(result.getCode(),result.getMsg());
return result;
}
@RequestMapping(value="b")
@ResponseBody
public Result b(){
...
saveLog(result.getCode(),result.getMsg());
return result;
}
private void saveLog(int code,String msg){
...
}
然而这种方式在接口数量较小的情况下还勉强可以接受,如果接口一多,每个地方都需要调用一次saveLog方法,万一忘记调用了呢?万一保存方法修改需要增加额外的参数了呢?因此很容易出乱子。
由于上一种方案的各种不可靠性,因此这里提供一种通过实现HandlerInterceptor和ResponseBodyAdvice接口相结合的方式来实现我们保存日志的小需求。
思路:
首先我们创建一个接收需要保存的各种参数的实体类,实现HandlerInterceptor接口并得到参数的值,设置开始时间,并把对象放入线程局部变量中。
然后去掉我们在Controller为每个接口方法写的saveLog。
最后我们实现ResponseBodyAdvice接口,通过beforeBodyWrite方法获取返回值,并调用saveLog方法。
简单的以上三步,即可完成我们的小需求,不仅不需要再Controller给每个接口方法显示的调用saveLog,而且即便将来需要保存更多信息,都不需要牵一发而动全身,此时的幸福指数应当是指数上升状态。
以下是实现代码:
接收保存参数的实体类
public class RequestModel {
private static final ThreadLocal<RequestModel> REQUEST_MODEL = new ThreadLocal<>();
/**平台**/
private String platform;
/**版本**/
private String version;
/**产品**/
private String product;
/**开始时间**/
private Long startMillis;
/**当前请求用户sessionId**/
private String sessionId;
...其他一些需要的属性...
public static RequestModel getRequestModel() {
return REQUEST_MODEL.get();
}
public static void setRequestModel(RequestModel request){
REQUEST_MODEL.set(request);
}
public static void removeRequestModel(){
REQUEST_MODEL.remove();
}
}
拦截器实现类
public class ApiInterceptor implements HandlerInterceptor{
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
throws Exception {
//Do Nothing.
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
//Do Nothing.
}
/**
* 获取请求的参数,设置处理请求的毫秒数,并放入线程局部变量中。
* 当执行完毕后,在MyResponseBodyAdvice中获取ResponseBody返回值和线程局部变量的值
* 做操作日志记录处理,此方法省去在每一个Controller方法中显示的调用保存操作日志
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequestModel requestModel = new RequestModel();
requestModel.setPlatform(request.getParameter("platform"));
requestModel.setProduct(request.getParameter("product"));
requestModel.setVersion(request.getParameter("version"));
requestModel.setSessionId(request.getParameter("sessionId"));
requestModel.setStartMillis(System.currentTimeMillis());
RequestModel.setRequestModel(requestModel);
}
return true;
}
}
获取ResponseBody返回值的实现类
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Result>{
@Resource
private OperateLogService operateLogService;
@Override
public Result beforeBodyWrite(Result result, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//获取在ApiInterceptor对指定请求参数放如到线程局部变量的对象
RequestModel model = RequestModel.getRequestModel();
//移除线程局部变量,释放内存
RequestModel.removeRequestModel();
//保存日志
operateLogService.saveLog(model, result);
return result;
}
@Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) {
//Do Nothing.
return true;
}
}
OVER。