所有 HandlerMapping 的实现都支持处理程序拦截器,当你想对某些请求应用特定的功能时,这些拦截器是非常有用的 — 例如,检查一个委托人。拦截器必须实现 org.springframework.web.servlet包中的 HandlerInterceptor,它有三个方法,应该可以提供足够的灵活性来进行各种预处理和后处理:
preHandle(..):在实际处理程序运行之前postHandle(..):处理程序运行后afterCompletion(..):在完整的请求完成后运行,也就是说,只要 preHandle 执行了,无论 postHandler 是否正常完成(抛出了异常),afterCompletion 也会执行
preHandle(..)方法返回一个布尔值。你可以使用这个方法来 中断或继续执行链 的处理。当这个方法返回 true 时,处理程序执行链继续进行。当它返回 false 时,DispatcherServlet 认为拦截器本身已经处理了请求(例如,渲染了一个适当的视图),并且不继续执行其他拦截器和执行链中的实际处理程序。
关于如何配置拦截器的例子,请参见 MVC 配置部分的 拦截器。你也可以通过使用个别 HandlerMapping 实现的设置器来直接注册它们。
请注意,postHandle 对于 @ResponseBody和 ResponseEntity方法来说用处不大,因为这些方法的响应是在 HandlerAdapter 中和postHandle 之前写入并提交的。这意味着对响应进行任何修改都太晚了,比如添加一个额外的头。对于这种情况,你可以实现ResponseBodyAdvice,并把它声明为一个 Controller Advice Bean,或者直接在 RequestMappingHandlerAdapter上配置它。
下面有一个例子
实现一个拦截器
package cn.mrcode.study.springdocsread.web;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/*** @author mrcode*/public class MyHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {// 这里尝试修改响应值或则添加响应头也不会生效response.addHeader("xxxxxxxxx", "xxxxxx");if(true) throw new RuntimeException();System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion");}}
注册拦截器(这里的知识后面才学,可以先不用管这个)
package cn.mrcode.study;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import cn.mrcode.study.springdocsread.web.MyHandlerInterceptor;/*** @author mrcode*/@EnableWebMvc@ComponentScan("cn.mrcode.study.springdocsread")@Configurationpublic class AppConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/hello/*");}}
controller
package cn.mrcode.study.springdocsread.web;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;/*** @author mrcode*/@Controller@RequestMapping("/hello")public class DemoController {public DemoController() {System.out.println("x");}@RequestMapping("/list")@ResponseBodypublic String test() {return "success";}}
访问 [http://localhost:8080/hello/list](http://localhost:8080/hello/list) 响应结果是 success,但是控制台却如下所示
preHandleafterCompletion三月 29, 2022 3:10:26 下午 org.apache.catalina.core.StandardWrapperValve invoke严重: Servlet.service() for servlet [app] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException] with root causejava.lang.RuntimeExceptionat cn.mrcode.study.springdocsread.web.MyHandlerInterceptor.postHandle(MyHandlerInterceptor.java:22)at org.springframework.web.servlet.HandlerExecutionChain.applyPostHandle(HandlerExecutionChain.java:165)at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1074)at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
在 postHandle 中异常,不影响响应结果。
ResponseBodyAdvice 改写值的例子
package cn.mrcode.study.springdocsread.web;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;/*** @author mrcode*/// 这里指定对那些 controller 生效@ControllerAdvice(assignableTypes = {DemoController.class})public class MyResponseBodyAdvice implements ResponseBodyAdvice {/**** @param returnType 控制器的参数类型* @param converterType 返回值选定的转换器* @return*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {// 关于这里要如何判定是否支持 这个 控制器返回的值,我们是否能改写,我暂时没有更好的例子System.out.println(String.format("returnType=%s , converterType=%s", returnType, converterType));return true;}/*** 在写入 body 前,调用* @param body 控制器返回的值* @param returnType* @param selectedContentType 被选定响应的 contentType , 比如 text/html* @param selectedConverterType 被选定的转换器* @param request* @param response* @return*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {System.out.println(String.format("body=%s , returnType=%s , selectedContentType=%s , selectedConverterType=%s",body, returnType, selectedContentType, selectedConverterType));// 这里添加一个响应头response.getHeaders().add("mrcode", "mrcode.cn");// 这里改写值return "update:" + body;}}
:::tips 使用 @ControllerAdvice 标注的话,起地方不用对这个 MyResponseBodyAdvice 进行额外的初始化之类的管理了 :::
实现的效果就是,返回值被改写了,还新增了一个响应头
