所有 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 {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle");
return true;
}
@Override
public 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");
}
@Override
public 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")
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public 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")
@ResponseBody
public String test() {
return "success";
}
}
访问 [http://localhost:8080/hello/list](http://localhost:8080/hello/list)
响应结果是 success,但是控制台却如下所示
preHandle
afterCompletion
三月 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 cause
java.lang.RuntimeException
at 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
*/
@Override
public 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
*/
@Override
public 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 进行额外的初始化之类的管理了 :::
实现的效果就是,返回值被改写了,还新增了一个响应头