被@ResponseBoby注释的方法在拦截器的posthandle方法中设置cookie失效的问题
文章标题可能有点绕口。先来解释下遇到的问题。
我写了一个拦截器,希望能够实现保存特定方法的请求参数到cookie中。
public class SaveParamInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// if(((HandlerMethod)handler).hasMethodAnnotation(SaveParam.class)){
// saveParam(request, response);
// }
return super.preHandle(request, response, handler);
} @Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if(((HandlerMethod)handler).hasMethodAnnotation(SaveParam.class)){
saveParam(request, response);
}
super.postHandle(request, response, handler, modelAndView);
} private void saveParam(HttpServletRequest request, HttpServletResponse response){
Enumeration<String> enumeration = request.getParameterNames();
while(enumeration.hasMoreElements()){
String name = enumeration.nextElement();
//过滤dataTables参数
if(name.startsWith("columns") || name.startsWith("search") || name.startsWith("order")){
continue;
}
String value = request.getParameter(name);
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(3600);
cookie.setPath("/");
response.addCookie(cookie);
System.out.println("name:" + name + " value:" + value);
}
}
}
一开始我将saveParam方法放在postHandle中。发现虽然请求能被正常拦截,但是页面上取不到保存过的cookie。
然后我又试了下将saveParam移到preHandle中,结果就正常了。
而且这种情况只有在被@ResponseBody注释的方法上才会发生。
由于给response添加cookie的本质应该就是在reponse的header里写入一些信息。所以应该是某个流程后,再往response里写信息就无效了(之前看servlet的API里也有类似的情况,当response被提交过后,再对其进行一些操作会抛出异常)。
于是我猜想,这跟SpringMVC处理请求的流程有关。想起前些天Spring绑定请求参数的流程中,handler被invoke之后,有一个设置response的status的动作。
先随便找一个控制器试试:
@RequestMapping("test")
@ResponseBody
@SaveParam
public JSONObject test(HttpServletResponse res) {
res.addCookie(new Cookie("befroe", "1"));
res.setStatus(200);
res.addCookie(new Cookie("after", "1"));
JSONObject object = new JSONObject;
return object;
}
从浏览器中查看结果:
发现两个cookie都是正常的。看来真想并没有这么简单。
于是只好从Spring的流程在查一遍:
直接从ServletInvocableHandlerMethod的invokeAndHandle找起。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest); if (returnValue == null) {
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(this.responseReason)) {
mavContainer.setRequestHandled(true);
return;
} mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
}
throw ex;
}
}
进到handleReturnValue这个方法里:
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
这是选择对应的处理器来处理返回值,继续往下:
因为是被@ResponseBoby注释的方法,所以我们进到了RequestResponseBodyMethodProcessor的实现里:
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
前两步几部是设置了状态,并将原生的request和response封装一下在返回。我们看writeWithMessageConverters里做了啥,
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object outputValue;
Class<?> valueType;
Type declaredType; if (value instanceof CharSequence) {
outputValue = value.toString();
valueType = String.class;
declaredType = String.class;
}
else {
outputValue = value;
valueType = getReturnValueType(outputValue, returnType);
declaredType = getGenericType(returnType);
} HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType); if (outputValue != null && producibleMediaTypes.isEmpty()) {
throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
} Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType requestedType : requestedMediaTypes) {
for (MediaType producibleType : producibleMediaTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
if (compatibleMediaTypes.isEmpty()) {
if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
return;
} List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
MediaType.sortBySpecificityAndQuality(mediaTypes); MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
} if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((GenericHttpMessageConverter) messageConverter).write(
outputValue, declaredType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
else if (messageConverter.canWrite(valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
} if (outputValue != null) {
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
虽然写了一大段,但是我们看到对outputMessage进行操作的只有在下面这个for循环里,我们就重点关注下这里操作了什么:
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
if (((GenericHttpMessageConverter) messageConverter).canWrite(
declaredType, valueType, selectedMediaType)) {
outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
inputMessage, outputMessage);
if (outputValue != null) {
addContentDispositionHeader(inputMessage, outputMessage);
((GenericHttpMessageConverter) messageConverter).write(
outputValue, declaredType, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
"\" using [" + messageConverter + "]");
}
}
return;
}
}
重点应该是在write这个方法里,这里是Converter对内容进行转化。
由于我们用的conver是FastJsonHttpMessageConverter。
来看看具体实现:
public void write(Object t, //
Type type, //
MediaType contentType, //
HttpOutputMessage outputMessage //
) throws IOException, HttpMessageNotWritableException { HttpHeaders headers = outputMessage.getHeaders();
if (headers.getContentType() == null) {
if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
contentType = getDefaultContentType(t);
}
if (contentType != null) {
headers.setContentType(contentType);
}
}
if (headers.getContentLength() == -1) {
Long contentLength = getContentLength(t, headers.getContentType());
if (contentLength != null) {
headers.setContentLength(contentLength);
}
}
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
看看是不是flush动作导致了response状态改为已经被提交,所以导致设置cookie失效呢,再来试一试:
@RequestMapping("queryAuditList")
@ResponseBody
@SaveParam
public JSONObject queryAuditList( HttpServletResponse res) {
res.addCookie(new Cookie("befroe", "1"));
try {
res.getOutputStream().flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
res.addCookie(new Cookie("after", "1"));
return new JSONObject();
}
看看结果:
果然是这样!
再看下servlet文档里的说法:
isCommitted
public boolean isCommitted()
- Returns a boolean indicating if the response has been committed. A committed response has already had its status code and headers written.
划重点:A committed response has already had its status and headers written.
所以flush操作是会导致response的commited状态被修改的,也就是说这时response的头信息已经被确定了!
被@ResponseBoby注释的方法在拦截器的posthandle方法中设置cookie失效的问题的更多相关文章
- struts2中的方法过滤拦截器
方法过滤拦截器是只过滤指定的方法,如果使用针对action 的普通的过滤器则会过滤该action内部 所有方法.如果在一个action中同时有多个作为业务逻辑控制的方法存在 的话则会过滤所有的业务逻辑 ...
- Struts2拦截指定方法的拦截器
作者:禅楼望月 默认情况下,我们为一个Action配置一个拦截器,该拦截器会拦截该Action中的所有方法,但是有时候我们只想拦截指定的方法.为此,需要使用struts2拦截器的方法过滤特性. 要使用 ...
- 拦截器(Interceptor)中的invocation.invoke()是什么意思?
拦截器(Interceptor)中的invocation.invoke()是什么意思? 最佳答案: invocation.invoke() 就是通知struts2接着干下面的事情 比如 调用下一个拦截 ...
- struts2拦截器配置;拦截器栈;配置默认拦截器;拦截方法的拦截器MethodFilterInterceptor;完成登录验证
struts2.xml 内容 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts ...
- 关于springmvc 方法注解拦截器的解决方案,多用于方法的鉴权
最近在用SpringMvc写项目的时候,遇到一个问题,就是方法的鉴权问题,这个问题弄了一天了终于解决了,下面看下解决方法 项目需求:需要鉴权的地方,我只需要打个标签即可,比如只有用户登录才可以进行的操 ...
- Spring MVC 方法注解拦截器
应用场景,在方法级别对本次调用进行鉴权,如api接口中有个用户唯一标示accessToken,对于有accessToken的每次请求可以在方法加一个拦截器,获得本次请求的用户,存放到request或者 ...
- 在struts2.3.4.1中使用注解、反射、拦截器实现基于方法的权限控制
权限控制是每一个系统都应该有的一个功能,有些只需要简单控制一下就可以了,然而有些却需要进行更加深入和细致的权限控制,尤其是对于一些MIS类系统,基于方法的权限控制就更加重要了. 用反射和自定义注解来实 ...
- CXF - 拦截器获取调用方法
没想到要弄这么一个东西. 起初只是想用interceptor记录一下webservice调用日志,后来却被要求在页面展示. 展示容易,但只是展示webservice的地址无法让用户从中明白什么. 那么 ...
- 基于Dynamic Proxy技术的方法AOP拦截器开发
在面向对象编程中,会用到大量的类,并且会多次调用类中的方法.有时可能需要对这些方法的调用进行一些控制.如在权限管理中,一些用户没有执行某些方法的权限.又如在日志系统中,在某个方法执行完后,将其执行的结 ...
随机推荐
- 1017 Queueing at Bank (25 分)
Suppose a bank has K windows open for service. There is a yellow line in front of the windows which ...
- 什么是ansible
什么是ansible l Ansible是2013年推出的一款IT自劢化和De ...
- django->基本操作和新建项目常用配置
一.安装django pip install django==2.1.5 -U #安装django/升级最新版本 二.创建.启动django项目 django-admin startproject m ...
- Mac PyCharm之.gitignore 安装设置
1. 首先安装.ignore 点击 PyCharm >>> Preferences 点击Plugins >>> 在搜索框输入.ignore >>> ...
- HBase Shell 十大花式玩儿法
前言:工欲善其事必先利其器,今天给大家介绍一下HBase Shell十大花式利器,在日常运维工作中,可以试着用起来. 1. 交互模式 也就是我们最常用到的Shell命令行的方式. $ hbase sh ...
- 关于《Python自动化测试实战》
作者有话说 笔者写这本书的初心是想通过自身经验分享一些在自动化测试领域中的实用技术,能够帮助那些正在从事自动化测试相关工作或者准备转型自动化测试的测试人员.任何一门技术涵盖的知识点都是非常广泛的,可能 ...
- python填写问卷星,疫情上报
#!!!注意:修改main里的url为真实的url,按需修改 50行 set_data中的submitdata # 61行 ip 修改为 真ip # submittype可能有错误,在151行 # 提 ...
- Vue + d3.js实现在地图上选点
需求:用户在地图上单击选点,页面获取到具体坐标并返回. 首先比较重要的是Vue中的$nextTick,因为vue是异步更新的,如果是想打开Dialog或者是其他操作dom后才加载地图,使用nextTi ...
- Git敏捷开发--reset和clean
reset 丢弃本地所有修改,强行和上游分支保持一致 git reset --hard HEAD 若仅丢弃某个文件的改动,利用checkout git checkout your_file clean ...
- 非oracle用户sysdba登陆出TNS-12547错误
这个问题mark下,测试机器oracle从12c升级到19c后,非oracle用户 sysdba登陆不上.sqlnet.log里错误是: sqplus uasa/uasa error Fa ...