被@ResponseBoby註釋的方法在攔截器的posthandle方法中設置cookie失效的問題

文章標題可能有點繞口。先來解釋下遇到的問題。web

我寫了一個攔截器,但願可以實現保存特定方法的請求參數到cookie中。瀏覽器

 1 public class SaveParamInterceptor extends HandlerInterceptorAdapter{
 2     @Override
 3     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
 4           throws Exception {
 5 //         if(((HandlerMethod)handler).hasMethodAnnotation(SaveParam.class)){
 6 //                saveParam(request, response);
 7 //            }
 8         return super.preHandle(request, response, handler);
 9     }
10 
11      @Override
12      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
13           ModelAndView modelAndView) throws Exception {
14          if(((HandlerMethod)handler).hasMethodAnnotation(SaveParam.class)){
15                 saveParam(request, response);
16             }
17         super.postHandle(request, response, handler, modelAndView);
18     }
19      
20      private void saveParam(HttpServletRequest request, HttpServletResponse response){
21             Enumeration<String> enumeration = request.getParameterNames();
22             while(enumeration.hasMoreElements()){
23                 String name = enumeration.nextElement();
24                 //過濾dataTables參數
25                 if(name.startsWith("columns") || name.startsWith("search") || name.startsWith("order")){
26                     continue;
27                 }
28                 String value = request.getParameter(name);
29                 Cookie cookie = new Cookie(name, value);
30                 cookie.setMaxAge(3600);  
31                 cookie.setPath("/"); 
32                 response.addCookie(cookie);
33                 System.out.println("name:" + name + " value:" + value);
34             }
35         }
36 }

一開始我將saveParam方法放在postHandle中。發現雖然請求能被正常攔截,可是頁面上取不到保存過的cookie。cookie

而後我又試了下將saveParam移到preHandle中,結果就正常了。app

並且這種狀況只有在被@ResponseBody註釋的方法上纔會發生。ide

因爲給response添加cookie的本質應該就是在reponse的header裏寫入一些信息。因此應該是某個流程後,再往response裏寫信息就無效了(以前看servlet的API裏也有相似的狀況,當response被提交事後,再對其進行一些操做會拋出異常)。post

 

因而我猜測,這跟SpringMVC處理請求的流程有關。想起前些天Spring綁定請求參數的流程中,handler被invoke以後,有一個設置response的status的動做。this

先隨便找一個控制器試試:spa

 1 @RequestMapping("test")
 2      @ResponseBody
 3      @SaveParam
 4      public JSONObject test(HttpServletResponse res) {
 5          res.addCookie(new Cookie("befroe", "1"));
 6          res.setStatus(200);
 7          res.addCookie(new Cookie("after", "1"));
 8       9          JSONObject object =  new JSONObject;
10          return object;
11      }

從瀏覽器中查看結果:debug

發現兩個cookie都是正常的。看來真想並無這麼簡單。code

因而只好從Spring的流程在查一遍:

直接從ServletInvocableHandlerMethod的invokeAndHandle找起。

 1     public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
 2             Object... providedArgs) throws Exception {
 3 
 4         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
 5         setResponseStatus(webRequest);
 6 
 7         if (returnValue == null) {
 8             if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
 9                 mavContainer.setRequestHandled(true);
10                 return;
11             }
12         }
13         else if (StringUtils.hasText(this.responseReason)) {
14             mavContainer.setRequestHandled(true);
15             return;
16         }
17 
18         mavContainer.setRequestHandled(false);
19         try {
20             this.returnValueHandlers.handleReturnValue(
21                     returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
22         }
23         catch (Exception ex) {
24             if (logger.isTraceEnabled()) {
25                 logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
26             }
27             throw ex;
28         }
29     }

進到handleReturnValue這個方法裏:

1 public void handleReturnValue(Object returnValue, MethodParameter returnType,
2             ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
3 
4         HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
5         if (handler == null) {
6             throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
7         }
8         handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
9     }

這是選擇對應的處理器來處理返回值,繼續往下:

由於是被@ResponseBoby註釋的方法,因此咱們進到了RequestResponseBodyMethodProcessor的實現裏:

 1 @Override
 2     public void handleReturnValue(Object returnValue, MethodParameter returnType,
 3             ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
 4             throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
 5 
 6         mavContainer.setRequestHandled(true);
 7         ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
 8         ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
 9 
10         // Try even with null return value. ResponseBodyAdvice could get involved.
11         writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
12     }

前兩步幾部是設置了狀態,並將原生的request和response封裝一下在返回。咱們看writeWithMessageConverters裏作了啥,

 1 protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
 2             ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
 3             throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
 4 
 5         Object outputValue;
 6         Class<?> valueType;
 7         Type declaredType;
 8 
 9         if (value instanceof CharSequence) {
10             outputValue = value.toString();
11             valueType = String.class;
12             declaredType = String.class;
13         }
14         else {
15             outputValue = value;
16             valueType = getReturnValueType(outputValue, returnType);
17             declaredType = getGenericType(returnType);
18         }
19 
20         HttpServletRequest request = inputMessage.getServletRequest();
21         List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
22         List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
23 
24         if (outputValue != null && producibleMediaTypes.isEmpty()) {
25             throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
26         }
27 
28         Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
29         for (MediaType requestedType : requestedMediaTypes) {
30             for (MediaType producibleType : producibleMediaTypes) {
31                 if (requestedType.isCompatibleWith(producibleType)) {
32                     compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
33                 }
34             }
35         }
36         if (compatibleMediaTypes.isEmpty()) {
37             if (outputValue != null) {
38                 throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
39             }
40             return;
41         }
42 
43         List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
44         MediaType.sortBySpecificityAndQuality(mediaTypes);
45 
46         MediaType selectedMediaType = null;
47         for (MediaType mediaType : mediaTypes) {
48             if (mediaType.isConcrete()) {
49                 selectedMediaType = mediaType;
50                 break;
51             }
52             else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
53                 selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
54                 break;
55             }
56         }
57 
58         if (selectedMediaType != null) {
59             selectedMediaType = selectedMediaType.removeQualityValue();
60             for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
61                 if (messageConverter instanceof GenericHttpMessageConverter) {
62                     if (((GenericHttpMessageConverter) messageConverter).canWrite(
63                             declaredType, valueType, selectedMediaType)) {
64                         outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
65                                 (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
66                                 inputMessage, outputMessage);
67                         if (outputValue != null) {
68                             addContentDispositionHeader(inputMessage, outputMessage);
69                             ((GenericHttpMessageConverter) messageConverter).write(
70                                     outputValue, declaredType, selectedMediaType, outputMessage);
71                             if (logger.isDebugEnabled()) {
72                                 logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
73                                         "\" using [" + messageConverter + "]");
74                             }
75                         }
76                         return;
77                     }
78                 }
79                 else if (messageConverter.canWrite(valueType, selectedMediaType)) {
80                     outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
81                             (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
82                             inputMessage, outputMessage);
83                     if (outputValue != null) {
84                         addContentDispositionHeader(inputMessage, outputMessage);
85                         ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage);
86                         if (logger.isDebugEnabled()) {
87                             logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
88                                     "\" using [" + messageConverter + "]");
89                         }
90                     }
91                     return;
92                 }
93             }
94         }
95 
96         if (outputValue != null) {
97             throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
98         }
99     }

雖然寫了一大段,可是咱們看到對outputMessage進行操做的只有在下面這個for循環裏,咱們就重點關注下這裏操做了什麼:

 1 for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
 2                 if (messageConverter instanceof GenericHttpMessageConverter) {
 3                     if (((GenericHttpMessageConverter) messageConverter).canWrite(
 4                             declaredType, valueType, selectedMediaType)) {
 5                         outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
 6                                 (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
 7                                 inputMessage, outputMessage);
 8                         if (outputValue != null) {
 9                             addContentDispositionHeader(inputMessage, outputMessage);
10                             ((GenericHttpMessageConverter) messageConverter).write(
11                                     outputValue, declaredType, selectedMediaType, outputMessage);
12                             if (logger.isDebugEnabled()) {
13                                 logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
14                                         "\" using [" + messageConverter + "]");
15                             }
16                         }
17                         return;
18                     }
19                 }

重點應該是在write這個方法裏,這裏是Converter對內容進行轉化。

因爲咱們用的conver是FastJsonHttpMessageConverter。

來看看具體實現:

 1 public void write(Object t, //
 2                       Type type, //
 3                       MediaType contentType, //
 4                       HttpOutputMessage outputMessage //
 5     ) throws IOException, HttpMessageNotWritableException {
 6 
 7         HttpHeaders headers = outputMessage.getHeaders();
 8         if (headers.getContentType() == null) {
 9             if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) {
10                 contentType = getDefaultContentType(t);
11             }
12             if (contentType != null) {
13                 headers.setContentType(contentType);
14             }
15         }
16         if (headers.getContentLength() == -1) {
17             Long contentLength = getContentLength(t, headers.getContentType());
18             if (contentLength != null) {
19                 headers.setContentLength(contentLength);
20             }
21         }
22         writeInternal(t, outputMessage);
23         outputMessage.getBody().flush();
24     }

看看是否是flush動做致使了response狀態改成已經被提交,因此致使設置cookie失效呢,再來試一試:

 1 @RequestMapping("queryAuditList")
 2      @ResponseBody
 3      @SaveParam
 4      public JSONObject queryAuditList( HttpServletResponse res) {
 5          res.addCookie(new Cookie("befroe", "1"));
 6          try {
 7             res.getOutputStream().flush();
 8         } catch (IOException e) {
 9             // TODO Auto-generated catch block
10             e.printStackTrace();
11         }
12          res.addCookie(new Cookie("after", "1"));
13          return new JSONObject();
14      }

看看結果:

果真是這樣!

再看下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的頭信息已經被肯定了!

相關文章
相關標籤/搜索