Spring拦截器无法设置Cookie

2021/01/07 13:57
阅读数 192

Spring提供了拦截器支持,只要我们实现 HandlerInterceptor 接口,就可以对http请求进行拦截,它提供了3个方法:

  • preHandle - 在进入Controller之前执行,如果返回false,则无法进入Controller
  • postHandle - 在Controller执行后执行
  • afterCompletion - Http请求完成时执行


HandlerInterceptor.java源码
图片

public interface HandlerInterceptor {

default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception
{

return true;
}


default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView)
throws Exception
{
}


default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex)
throws Exception
{
}

}















使用拦截器,可以使我们的很多业务逻辑解耦,如:

  • http请求需要有权限验证,此时我们就可以在preHandle中进行权限验证相关的处理,将权限验证逻辑从业务处理的Controller剥离出来,实现解耦。
  • http需要一些附加的上下文,但这些上下文信息每次请求都可能不一样,此时,我们就可以使用ThreadLocal来存储上下文信息,在preHandle设置ThreadLocal,然后在afterCompletion中移除ThreadLocal,从而避免内存泄露


处理Http请求,就不能避免的会有需要处理Cookie信息的时候。

我们举个简单的例子:

在项目管理系统中,所有的需求、任务等信息都是挂在项目下面的,所以系统中一定会有一个切换项目的功能。通常情况下,我们会使用Cookie来存储当前项目id,以方便每次请求都会带有项目属性,从而对应的业务处理都是对当前项目的。

切换项目的简单实现方式就是在Controller中直接处理Cookie:






@ResponseBody
@PostMapping("/changeProject")
public Result changeProject(HttpServletResponse response, Long projectId) {
/// 略...
   
   Cookie cookie = new Cookie("project_id", projectId);
   response.addCookie(cookie);
}







这种方式写Cookie是没有问题。

但是把设置Cookie的动作放到拦截器里缺怎么都不生效:

public class ProjectInterceptor implements HandlerInterceptor {

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception
{

       // 在此处设置Cookie是可以的
       Cookie cookie = new Cookie("project_id", projectId);
    response.addCookie(cookie);
return true;
}


public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView)
throws Exception
{
       // 在此处设置Cookie是无效的
       Cookie cookie = new Cookie("project_id", projectId);
    response.addCookie(cookie);
}


public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex)
throws Exception
{
       // 在此处设置Cookie是无效的
       Cookie cookie = new Cookie("project_id", projectId);
    response.addCookie(cookie);
}

}

























postHandle和afterCompletion中都有response,按理来说应该没有问题,但却不行。

查看Spring官方的文档发现:



Note that postHandle is less useful with @ResponseBody and ResponseEntity methods for which the response is written and committed within the HandlerAdapter and before postHandle. That means it is too late to make any changes to the response, such as adding an extra header. For such scenarios, you can implement ResponseBodyAdvice and either declare it as an Controller Advice bean or configure it directly on RequestMappingHandlerAdapter.
> 详见: Spring reference


原来postHandle方法在@ResponseBody和ResponseEntity方法的场景下是无效,此时对response做任何改变,都是晚了的。
官方也给出里解决方案:

@ControllerAdvice
public class ProjectAdvice implements ResponseBodyAdvice<Object> {
   @Override
   public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
       return true;
   }

   @Override
   public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
       Cookie cookie = new Cookie("project_id", projectId);
       ServletServerHttpResponse ***esp = (ServletServerHttpResponse)response;
       ***esp.getServletResponse().addCookie(cookie);
       return body;
   }
}














通过@ControllerAdvice的方式,就可以在Controller方法处理完成后再对response进行修改。


展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部