Interception

所有 HandlerMapping 的实现都支持处理程序拦截器,当你想对某些请求应用特定的功能时,这些拦截器是非常有用的 — 例如,检查一个委托人。拦截器必须实现 org.springframework.web.servlet包中的 HandlerInterceptor,它有三个方法,应该可以提供足够的灵活性来进行各种预处理和后处理:

  • preHandle(..):在实际处理程序运行之前

  • postHandle(..):处理程序运行后

  • afterCompletion(..):在完整的请求完成后运行,也就是说,只要 preHandle 执行了,无论 postHandler 是否正常完成(抛出了异常),afterCompletion 也会执行

preHandle(..)方法返回一个布尔值。你可以使用这个方法来 中断或继续执行链 的处理。当这个方法返回 true 时,处理程序执行链继续进行。当它返回 false 时,DispatcherServlet 认为拦截器本身已经处理了请求(例如,渲染了一个适当的视图),并且不继续执行其他拦截器和执行链中的实际处理程序。

关于如何配置拦截器的例子,请参见 MVC 配置部分的 拦截器。你也可以通过使用个别 HandlerMapping 实现的设置器来直接注册它们。

请注意,postHandle 对于 @ResponseBodyResponseEntity方法来说用处不大,因为这些方法的响应是在 HandlerAdapter 中和postHandle 之前写入并提交的。这意味着对响应进行任何修改都太晚了,比如添加一个额外的头。对于这种情况,你可以实现ResponseBodyAdvice,并把它声明为一个 Controller Advice Bean,或者直接在 RequestMappingHandlerAdapter上配置它。

下面有一个例子

实现一个拦截器

  1. package cn.mrcode.study.springdocsread.web;
  2. import org.springframework.web.servlet.HandlerInterceptor;
  3. import org.springframework.web.servlet.ModelAndView;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. /**
  7. * @author mrcode
  8. */
  9. public class MyHandlerInterceptor implements HandlerInterceptor {
  10. @Override
  11. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  12. System.out.println("preHandle");
  13. return true;
  14. }
  15. @Override
  16. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  17. // 这里尝试修改响应值或则添加响应头也不会生效
  18. response.addHeader("xxxxxxxxx", "xxxxxx");
  19. if(true) throw new RuntimeException();
  20. System.out.println("postHandle");
  21. }
  22. @Override
  23. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  24. System.out.println("afterCompletion");
  25. }
  26. }

注册拦截器(这里的知识后面才学,可以先不用管这个)

  1. package cn.mrcode.study;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  5. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  7. import cn.mrcode.study.springdocsread.web.MyHandlerInterceptor;
  8. /**
  9. * @author mrcode
  10. */
  11. @EnableWebMvc
  12. @ComponentScan("cn.mrcode.study.springdocsread")
  13. @Configuration
  14. public class AppConfig implements WebMvcConfigurer {
  15. @Override
  16. public void addInterceptors(InterceptorRegistry registry) {
  17. registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/hello/*");
  18. }
  19. }

controller

  1. package cn.mrcode.study.springdocsread.web;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. /**
  6. * @author mrcode
  7. */
  8. @Controller
  9. @RequestMapping("/hello")
  10. public class DemoController {
  11. public DemoController() {
  12. System.out.println("x");
  13. }
  14. @RequestMapping("/list")
  15. @ResponseBody
  16. public String test() {
  17. return "success";
  18. }
  19. }

访问 [http://localhost:8080/hello/list](http://localhost:8080/hello/list) 响应结果是 success,但是控制台却如下所示

  1. preHandle
  2. afterCompletion
  3. 三月 29, 2022 3:10:26 下午 org.apache.catalina.core.StandardWrapperValve invoke
  4. 严重: Servlet.service() for servlet [app] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException] with root cause
  5. java.lang.RuntimeException
  6. at cn.mrcode.study.springdocsread.web.MyHandlerInterceptor.postHandle(MyHandlerInterceptor.java:22)
  7. at org.springframework.web.servlet.HandlerExecutionChain.applyPostHandle(HandlerExecutionChain.java:165)
  8. at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1074)
  9. at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
  10. at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
  11. at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)

在 postHandle 中异常,不影响响应结果。

ResponseBodyAdvice 改写值的例子

  1. package cn.mrcode.study.springdocsread.web;
  2. import org.springframework.core.MethodParameter;
  3. import org.springframework.http.MediaType;
  4. import org.springframework.http.server.ServerHttpRequest;
  5. import org.springframework.http.server.ServerHttpResponse;
  6. import org.springframework.web.bind.annotation.ControllerAdvice;
  7. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  8. /**
  9. * @author mrcode
  10. */
  11. // 这里指定对那些 controller 生效
  12. @ControllerAdvice(assignableTypes = {DemoController.class})
  13. public class MyResponseBodyAdvice implements ResponseBodyAdvice {
  14. /**
  15. *
  16. * @param returnType 控制器的参数类型
  17. * @param converterType 返回值选定的转换器
  18. * @return
  19. */
  20. @Override
  21. public boolean supports(MethodParameter returnType, Class converterType) {
  22. // 关于这里要如何判定是否支持 这个 控制器返回的值,我们是否能改写,我暂时没有更好的例子
  23. System.out.println(String.format("returnType=%s , converterType=%s", returnType, converterType));
  24. return true;
  25. }
  26. /**
  27. * 在写入 body 前,调用
  28. * @param body 控制器返回的值
  29. * @param returnType
  30. * @param selectedContentType 被选定响应的 contentType , 比如 text/html
  31. * @param selectedConverterType 被选定的转换器
  32. * @param request
  33. * @param response
  34. * @return
  35. */
  36. @Override
  37. public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  38. System.out.println(String.format("body=%s , returnType=%s , selectedContentType=%s , selectedConverterType=%s",
  39. body, returnType, selectedContentType, selectedConverterType));
  40. // 这里添加一个响应头
  41. response.getHeaders().add("mrcode", "mrcode.cn");
  42. // 这里改写值
  43. return "update:" + body;
  44. }
  45. }

:::tips 使用 @ControllerAdvice 标注的话,起地方不用对这个 MyResponseBodyAdvice 进行额外的初始化之类的管理了 :::

实现的效果就是,返回值被改写了,还新增了一个响应头
image.png