文章标题可能有点绕口。先来解释下遇到的问题。

我写了一个拦截器,希望能够实现保存特定方法的请求参数到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失效的问题的更多相关文章

  1. struts2中的方法过滤拦截器

    方法过滤拦截器是只过滤指定的方法,如果使用针对action 的普通的过滤器则会过滤该action内部 所有方法.如果在一个action中同时有多个作为业务逻辑控制的方法存在 的话则会过滤所有的业务逻辑 ...

  2. Struts2拦截指定方法的拦截器

    作者:禅楼望月 默认情况下,我们为一个Action配置一个拦截器,该拦截器会拦截该Action中的所有方法,但是有时候我们只想拦截指定的方法.为此,需要使用struts2拦截器的方法过滤特性. 要使用 ...

  3. 拦截器(Interceptor)中的invocation.invoke()是什么意思?

    拦截器(Interceptor)中的invocation.invoke()是什么意思? 最佳答案: invocation.invoke() 就是通知struts2接着干下面的事情 比如 调用下一个拦截 ...

  4. struts2拦截器配置;拦截器栈;配置默认拦截器;拦截方法的拦截器MethodFilterInterceptor;完成登录验证

    struts2.xml 内容 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts ...

  5. 关于springmvc 方法注解拦截器的解决方案,多用于方法的鉴权

    最近在用SpringMvc写项目的时候,遇到一个问题,就是方法的鉴权问题,这个问题弄了一天了终于解决了,下面看下解决方法 项目需求:需要鉴权的地方,我只需要打个标签即可,比如只有用户登录才可以进行的操 ...

  6. Spring MVC 方法注解拦截器

    应用场景,在方法级别对本次调用进行鉴权,如api接口中有个用户唯一标示accessToken,对于有accessToken的每次请求可以在方法加一个拦截器,获得本次请求的用户,存放到request或者 ...

  7. 在struts2.3.4.1中使用注解、反射、拦截器实现基于方法的权限控制

    权限控制是每一个系统都应该有的一个功能,有些只需要简单控制一下就可以了,然而有些却需要进行更加深入和细致的权限控制,尤其是对于一些MIS类系统,基于方法的权限控制就更加重要了. 用反射和自定义注解来实 ...

  8. CXF - 拦截器获取调用方法

    没想到要弄这么一个东西. 起初只是想用interceptor记录一下webservice调用日志,后来却被要求在页面展示. 展示容易,但只是展示webservice的地址无法让用户从中明白什么. 那么 ...

  9. 基于Dynamic Proxy技术的方法AOP拦截器开发

    在面向对象编程中,会用到大量的类,并且会多次调用类中的方法.有时可能需要对这些方法的调用进行一些控制.如在权限管理中,一些用户没有执行某些方法的权限.又如在日志系统中,在某个方法执行完后,将其执行的结 ...

随机推荐

  1. 数据科学 R语言速成

    文章更新于:2020-03-07 按照惯例,需要的文件附上链接放在文首: 文件名:R-3.6.2-win.exe 文件大小:82.4M 下载链接:https://www.lanzous.com/i9c ...

  2. 【numpy】新版本中numpy(numpy>1.17.0)中的random模块

    numpy是Python中经常要使用的一个库,而其中的random模块经常用来生成一些数组,本文接下来将介绍numpy中random模块的一些使用方法. 首先查看numpy的版本: import nu ...

  3. MTK Android ROM与RAM的区别

    ROM与RAM 简单的说,一个完整的计算机系统是由软件和硬件组成的.其中,硬件部分由中央处理单元CPU(包括运算器和控制器).存储器和输入/输出设备构成.目前个人电脑上使用的主板一般只能支持到1GB的 ...

  4. 让 .NET 轻松构建中间件模式代码

    让 .NET 轻松构建中间件模式代码 Intro 在 asp.net core 中中间件的设计令人叹为观止,如此高大上的设计何不集成到自己的代码里呢. 于是就有了封装了一个简单通用的中间件模板的想法, ...

  5. TCP协议的安全性分析

    有算法就有破解法,因为它们都遵循了一定的数据结构和数学知识.所以网络安全是一个相对的概念,不可能出现绝对的安全!作为当今最流行的网络协议--TCP也是如此.那么TCP的安全问题究竟是哪些因素引起的呢? ...

  6. 最长公共子窜和最长公共子序列(LCS)

    他们都是用dp做;复杂度都是O(N方) 有一个大佬的博客写的很详细,是关于最长公共子序列的:https://blog.csdn.net/hrn1216/article/details/51534607 ...

  7. 【从零单排HBase 03】深入HBase读写

    在了解HBase架构的基础上,我们需要进一步学习HBase的读写过程,一方面是了解各个组件在整个读写过程中充当的角色,另一方面只有了解HBase的真实请求过程,才能为后续的正确使用打下初步基础,毕竟, ...

  8. java 第七周课后作业0417

    定义一个矩形类Rectangle:(知识点:对象的创建和使用)1 定义三个方法:getArea()求面积.getPer()求周长,showAll()分别在控制台输出长.宽.面积.周长.2 有2个属性: ...

  9. Web三维编程入门总结之一:WebGL与Threejs入门知识

    /*在这里对这段时间学习的3D编程知识做个总结,以备再次出发.计划分成“webgl与three.js基础介绍”.“面向对象的基础3D场景框架编写”.“模型导入与简单3D游戏编写”三个部分,其他零散知识 ...

  10. Ant概念

    Ant是基于Java的.可以跨平台的项目编译和生成工具.