spring boot通过Interceptor和HandlerMethodReturnValueHandler实现统一处理为controller返回对象统计处理时间
思路:实现思路都是基于Aop实现,方式上可以通过spring aop和spring mvc的aop机制都能实现。
通过Interceptor的可以实现为controller插入开始时间和执行结束时间,并将数据放入response中,但是这里希望将数据直接放入ResponseBody Controller返回的统一对象中,所以用Interceptor有点麻烦,可以使用spring mvc的HandlerMethodReturnValueHandler对结果对象进行处理,然后在交给RequestResponseBodyMethodProcessor处理。
1、通过Interceptor在进入controller前将时间存入request中;
2、在HandlerMethodReturnValueHandler拦截并统计时间,将耗时放入对象中。
采坑记录:
1、在WebMvcConfigurerAdapter重写addReturnValueHandlers方法,发现supportsReturnType不会被执行,原因:@ResponseBody或@RestController的实现也是由HandlerMethodReturnValueHandler实现,因为系统级别高于用户自定义级别,会先执行RequestResponseBodyMethodProcessor的supportsReturnType,当返回true后,后续的久不会再执行,只会有1个HandlerMethodReturnValueHandler会被执行,解决思路是在WebMvcConfigurerAdapter中改变自定义的顺序,插入到系统之前(List)。
2、自定义的ReturnHandler执行后系统的将不会执行,将导致需要自己实现MessageConvert,即如果不自己实现response将没有任何返回数据,解决思路是继续使用@ResponseBody或@RestController前提下在自定义的ReturnHandler中调用RequestResponseBodyMethodProcessor的实现进行处理,RequestResponseBodyMethodProcessor需要一个构造参数:List<HttpMessageConverter<?>> converters,可以注入RequestMappingHandlerAdapter进行获取。
代码如下:
1、WebFileConfigurer
@Configuration
public class WebFileConfigurer extends WebMvcConfigurerAdapter { @Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter; /**
* 注册SpringMVC Interceptor
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
// 为每个请求统计处理耗时
InterceptorRegistration ir = registry.addInterceptor(new ProcessInterceptor());
} @Bean
public ReturnHandler getReturnHandler(){
List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
return new ReturnHandler(messageConverters);//初始化过滤器
} /**
* 解决ReturnHandler不生效问题
*/
@PostConstruct
public void init() {
final List<HandlerMethodReturnValueHandler> originalHandlers = new ArrayList<>(
requestMappingHandlerAdapter.getReturnValueHandlers());
// final int deferredPos = obtainValueHandlerPosition(originalHandlers, DeferredResultMethodReturnValueHandler.class);
// originalHandlers.add(deferredPos + 1, customedReturnValueHandler()); // Add customed handler BEFORE the HttpEntityMethodProcessor to enable JsonReturnHandler !!
final int deferredPos = obtainValueHandlerPosition(originalHandlers, HttpEntityMethodProcessor.class);
originalHandlers.add(deferredPos - 1, getReturnHandler());
requestMappingHandlerAdapter.setReturnValueHandlers(originalHandlers);
return;
} private int obtainValueHandlerPosition(final List<HandlerMethodReturnValueHandler> originalHandlers, Class<?> handlerClass) {
for (int i = 0; i < originalHandlers.size(); i++) {
final HandlerMethodReturnValueHandler valueHandler = originalHandlers.get(i);
if (handlerClass.isAssignableFrom(valueHandler.getClass())) {
return i;
}
}
return -1;
} }
2、Interceptor
public class ProcessInterceptor extends HandlerInterceptorAdapter {
private final static Logger logger = getLogger(ProcessInterceptor.class);
public final static String requestName="requestStartTime";
/**
*预处理回调方法,实现处理器的预处理(如登录检查)。
*第三个参数为响应的处理器,即controller。
*返回true,表示继续流程,调用下一个拦截器或者处理器。
*返回false,表示流程中断,通过response产生响应。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
//记录开始时间
Long requestStartTime = System.currentTimeMillis();
request.setAttribute(requestName,requestStartTime);
return true;
}
}
3、ReturnHandler
@Component
public class ReturnHandler extends RequestResponseBodyMethodProcessor { private final static Logger logger = getLogger(ReturnHandler.class); public ReturnHandler(List<HttpMessageConverter<?>> converters) {
super(converters);
} /**
* 该处理程序是否支持给定的方法返回类型(只有返回true才回去调用handleReturnValue)
*/
@Override
public boolean supportsReturnType(MethodParameter methodParameter) {
boolean support = super.supportsReturnType(methodParameter);
support=support || methodParameter.getParameterType() == WebResponse.class;
return support;
} /**
* 处理给定的返回值
* 通过向 ModelAndViewContainer 添加属性和设置视图或者
* 通过调用 ModelAndViewContainer.setRequestHandled(true) 来标识响应已经被直接处理(不再调用视图解析器)
*/
@Override
public void handleReturnValue(Object o, MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest) throws IOException, HttpMediaTypeNotAcceptableException { if ( o instanceof WebResponse){
WebResponse res=((WebResponse)o);
/**
* 标识请求是否已经在该方法内完成处理
*/
modelAndViewContainer.setRequestHandled(true);
HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
//获取开始时间
Object startTimerObj = request.getAttribute(ProcessInterceptor.requestName);
if (startTimerObj==null || !(startTimerObj instanceof Long) || res==null){
//没有开始时间,不做处理的请求
return;
}
Long startTimer=(Long) startTimerObj;
Long millis=System.currentTimeMillis()-startTimer;
//记录处理时间
res.setT(millis); logger.debug(String.format("请求url:%s 耗时:%s",request.getRequestURL().toString(),millis.toString())); }
super.handleReturnValue(o,methodParameter,modelAndViewContainer,nativeWebRequest);
} }
spring boot通过Interceptor和HandlerMethodReturnValueHandler实现统一处理为controller返回对象统计处理时间的更多相关文章
- Spring Boot 构建电商基础秒杀项目 (三) 通用的返回对象 & 异常处理
SpringBoot构建电商基础秒杀项目 学习笔记 定义通用的返回对象 public class CommonReturnType { // success, fail private String ...
- Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志
其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...
- Spring Boot 2.0 教程 | AOP 切面统一打印请求日志
欢迎关注微信公众号: 小哈学Java 文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request 本节中,您 ...
- Spring Boot中Restful Api的异常统一处理
我们在用Spring Boot去向前端提供Restful Api接口时,经常会遇到接口处理异常的情况,产生异常的可能原因是参数错误,空指针异常,SQL执行错误等等. 当发生这些异常时,Spring B ...
- spring boot: filter/interceptor/aop在获取request/method参数上的区别(spring boot 2.3.1)
一,filter/interceptor/aop在获取参数上有什么区别? 1,filter可以修改HttpServletRequest的参数(doFilter方法的功能), interceptor/a ...
- 使用Spring Boot接受HTTP GET/POST请求的一个SQL并返回结果
这里说的意思是:我向我的Web服务器发送一个请求(因为GET请求的一些限制,所以最好使用POST请求,不过这里作为测试还是使用GET请求),请求中带一个sql参数,它对应查询的数据.然后我的Sprin ...
- Spring Boot 表单验证、AOP统一处理请求日志、单元测试
一.使用@Valid表单验证 于实体类中添加@Min等注解 @Entity public class Girl { @Id @GeneratedValue private Integer id; pr ...
- Spring Boot 系列 @ControllerAdvice 拦截异常并统一处理
ControllerAdvice用法解析 简介 通过@ControllerAdvice注解可以将对于控制器的全局配置放在同一个位置. 注解了@Controller的类的方法可以使用@Exception ...
- Spring Boot启动过程(五):Springboot内嵌Tomcat对象的start
标题和上一篇很像,所以特别强调一下,这个是Tomcat对象的. 从TomcatEmbeddedServletContainer的this.tomcat.start()开始,主要是利用Lifecycle ...
随机推荐
- webpack.optimize.UglifyJsPlugin配置说明
https://segmentfault.com/a/1190000008995453?utm_source=tuicool&utm_medium=referral
- day048 BOM和DOM
BOM Browser Object Model(文档对象模型) > window对象 1.alert 弹出带有提示和一个确认按钮的警示框 2.confirm 弹出带有一 ...
- python __init__.py 的作用
__init__.py的主要作用是: . Python中package的标识,不能删除 . 定义__all__用来模糊导入 . 编写Python代码(不建议在__init__中写python模块,可以 ...
- 菜鸟脱壳之脱壳的基础知识(二) ——DUMP的原理
菜鸟脱壳之脱壳的基础知识(二)——DUMP的原理当外壳的执行完毕后,会跳到原来的程序的入口点,即Entry Point,也可以称作OEP!当一般加密强度不是很大的壳,会在壳的末尾有一个大的跨段,跳向O ...
- AngularJS +Kendo UI Grid template
var dataSource = new kendo.data.DataSource({ transport: { dataType: "json", read: inputUri ...
- IDA Pro安装教程
1.下载 http://tools.pediy.com/windows/disassemblers.htm 我们这里选择6.8版本下载 2.安装 打开下载的安装包,解压出来进入IDAPro68文件夹, ...
- NAT资料
第1章 NAT 1.1 NAT概述 1990年代中期,NAT是作为一种解决IPv4地址短缺以避免保留IP地址困难的方案而流行起来的.网络地址转换在很多国家都有很广泛的使用.所以NAT就成了家庭和小型办 ...
- Android : Android Studio 更新至gradle 4.10.1后Variants API变化
同步警告: WARNING: API 'variantOutput.getPackageApplication()' is obsolete and has been replaced with 'v ...
- python面向对象封装案例(附:is和==的区别)
面向对象封装案例 目标 封装 小明爱跑步 存放家具 01. 封装 封装 是面向对象编程的一大特点 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中 外界 使用 类 创建 对 ...
- linux系统转换root权限
有时候我们用普通用户的权限没办法完成有关权限,这时候我们就需要拿到root权限才可以,拿到root权限有两种方式 方式一: su - 或者su 此时就会提示你输入密码,输入密码成功以后就能以root权 ...