缘起

有一个需求,在进入controller之前验证调用次数是否超过限制,在响应之后判断是否正常返回,对调用次数进行+1,发现带@RestController的类和带@ResponseBody的方法在被调用后response会直接写入输出流,在postHandle和afterCompletion这两个方法执行之前就已经把数据返回,导致这两个方法里面的response根本获取不到响应数据(也无法拿到头信息等)。

解决方案

先解释一下为什么不用过滤器,因为这个需求是拦截带某个特定注解的controller方法,还需要获取到这个注解里面的一些数据,因此Filter方案没法满足这样的需求。怎么解决这个问题呢?那就是使用@ControllerAdvice,这个注解标注的类需要实现ResponseBodyAdvice,这样在response返回前会调用这个类的beforeBodyWrite方法,我们就在beforeBodyWrite方法里面做文章,来进行“曲线救国”。代码:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.lang.reflect.Method; /**
**/
@ControllerAdvice
public class ApiResponseBody implements ResponseBodyAdvice<RestResult> { private static final Logger logger = LoggerFactory.getLogger(ApiResponseBody.class);
@Autowired
private RedisClientWrapper redisClientWrapper; @Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { Method method = methodParameter.getMethod();
return method.isAnnotationPresent(InvokeLimit.class);
} @Override
public RestResult beforeBodyWrite(RestResult restResult, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
logger.info("调用限制拦截器进入次数增加");
if(ResultEnums.SUCCESS.getCode().equals(restResult.getCode())){
String appId = methodParameter.getMethod().getAnnotation(InvokeLimit.class).value();
String cusNo = serverHttpRequest.getHeaders().get(BussConstant.INVOKE_HEADER_CUS_NO).get(0);
String times = redisClientWrapper.increment(appId + ":" + BussConstant.INVOKE_NUM_CURRENT_PREFIX + cusNo);
logger.info("调用限制拦截器次数增加完成,cusNo={},appId={},当前次数为:{}",cusNo,appId,times);
} return restResult;
}
}

supports方法是来给定条件判断是否该调用beforeBodyWrite,MethodParameter里面有各种数据,其中就有我想要的:调用了哪个方法,从而获得标注在上面的注解。beforeBodyWrite中就是我的逻辑,其中也包含MethodParameter,并且还有封装了的request和response,非常灵活。

关键代码:@seeorg.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverterswriteWithMessageConverters(@Nullable T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)

if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
//关键代码
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}

注:同理还有RequestBodyAdvice-> @RequestBody等

拦截器HandlerInterceptorAdapter的postHandle和afterCompletion无法获取response返回值问题的更多相关文章

  1. SpringMVC配置拦截器实现登录控制

    SpringMVC读取Cookie判断用户是否登录,对每一个action都要进行判断.之前使用jstl标签在页面上判断session如果没有登录就使用如下代码跳转到登录页面. <c:if tes ...

  2. 9.springMVC中的拦截器

    springMVC中的拦截器大概大致可以分为以下几个步骤去学习: 1.自定义一个类实现HandlerInterceptor接口,这里要了解其中几个方法的作用 2.在springMVC的配置文件中添加拦 ...

  3. Spring+SpringMVC+MyBatis深入学习及搭建(十七)——SpringMVC拦截器

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7098753.html 前面讲到:Spring+SpringMVC+MyBatis深入学习及搭建(十六)--S ...

  4. Unit05: 实战技巧 、 资费列表 、 拦截器

    Unit05: 过滤器解决表单写中文乱码.拦截器 1. 使用过滤器解决表单中文参数值乱码问题 注意: a. 表单提交方式必须为POST. b. 过滤器的编码应该与浏览器端设置的编码一致. 2. 拦截器 ...

  5. spring mvc 拦截器(已完成)

    1:ModelAndView @RequestMapping("/viewall")public ModelAndView viewAll(String name,String p ...

  6. SpringMVC拦截器及多拦截器时的执行顺序

    本文链接:https://blog.csdn.net/itcats_cn/article/details/80371639拦截器的配置步骤 springmvc.xml中配置多个拦截器配置自定义拦截器并 ...

  7. struts2拦截器的实现原理及源码剖析

    拦截器(interceptor)是Struts2最强大的特性之一,也可以说是struts2的核心,拦截器可以让你在Action和result被执行之前或之后进行一些处理.同时,拦截器也可以让你将通用的 ...

  8. 5-21 拦截器 Interceptor

    Spring MVC拦截器 什么是拦截器 拦截器是SpringMvc框架提供的功能 它可以在控制器方法运行之前或运行之后(还有其它特殊时机)对请求进行处理或加工的特定接口 常见面试题:过滤器和拦截器的 ...

  9. Spring MVC中的拦截器/过滤器HandlerInterceptorAdapter的使用

    一般情况下,对来自浏览器的请求的拦截,是利用Filter实现的 而在Spring中,基于Filter这种方式可以实现Bean预处理.后处理. 比如注入FilterRegistrationBean,然后 ...

随机推荐

  1. 教你写百分九十的shell

    本文章主要内容来自菜鸟教程 , 也添加了一些知识点 shell脚本? 在说什么是shell脚本之前,先说说什么是shell.shell是外壳的意思,就是操作系统的外壳.我们可以通过shell命令来操作 ...

  2. RabbitMq脑裂问题

    现象 部署在阿里云上的2台RabbitMQ主从,访问management页面时出现如下所示的内容: 查看其中一个mq的日志,发现如下内容: 00:06:32.423 [warning] <0.5 ...

  3. Docker:Linux离线安装docker-compose

    1)首先访问 docker-compose 的 GitHub 版本发布页面 https://github.com/docker/compose/releases 2)由于服务器是 CentOS 系统, ...

  4. 3、集成springfox-swagger 3.0.0 + 集成knife4j

    集成springfox-swagger 3.0.0 + 集成knife4j 1.基本用法 1.1.导入依赖 <!-- web接口 --> <dependency> <gr ...

  5. bugkuCTF

    这题说实话我一脸懵逼,计网还没学的我,瑟瑟发抖,赶紧去百度. 思路分析: 涉及到域名解析,也就是dns服务,看了看writeup,都是修改host文件,百度了下host文件的作用,才明白了 host文 ...

  6. PHPCMSV9版本代码审计学习

    学习代码审计,自己简单记录一下.如有错误望师傅斧正. PHPCMS预备知识 PHPCMS是采用MVC设计模式开发,基于模块和操作的方式进行访问,采用单一入口模式进行项目部署和访问,无论访问任何一个模块 ...

  7. python 列表指导式

    >>> a=[page for page in range(10)]>>> print (a)[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>& ...

  8. 用EXCEL打开CSV文件

    1.打开EXCEL 2.数据--自文本--选择对应的CSV文件 3.设置表头所在的行(例如17行为表头)则输入17 4.确定分隔符 5.单击"确定"即可

  9. java集合(5)-List集合

    序章 List集合是一个元素有序且可以重复的集合,每个元素对应一个索引,类似于数组的方式,默认是按照添加顺序,所以第一次添加元素索引为0,第二次为1.... 一 Java8改进的List接口和List ...

  10. maven解析依赖报错:Cannot resolve com.baomidou:mybatis-plus-generator:3.4.2

    不能解析依赖: <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plu ...