使用Interceptor
上图虚线框就是Filter2的拦截范围,Filter组件实际上并不知道后续内部处理是通过Spring MVC提供的还是其他Servlet组件,因为Filter是Servlet规范定义的标准组件,它可以应用在任何基于Servlet的程序中。
如果只基于Spring MVC开发应用程序,还可以使用Spring MVC提供的一种功能类似Filter的拦截器:Interceptor。和Filter相比,Interceptor拦截范围不是后续整个处理流程,而是仅针对Controller拦截:
│ ▲
▼ │
┌───────┐
│Filter1│
└───────┘
│ ▲
▼ │
┌───────┐
│Filter2│
└───────┘
│ ▲
▼ │
┌─────────────────┐
│DispatcherServlet│<───┐
└─────────────────┘ │
│ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┼ ─ ─ ─ ┐
│ │
│ │ ┌────────────┐ │
│ │ Render │
│ │ └────────────┘ │
│ ▲
│ │ │ │
│ │ │ModelAndView│ │
│ └────────────┘
│ │ ▲ │
│ ┌───────────┐ │
├─┼─>│Controller1│────┤ │
│ └───────────┘ │
│ │ │ │
│ ┌───────────┐ │
└─┼─>│Controller2│────┘ │
└───────────┘
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
上图虚线框就是Interceptor的拦截范围,注意到Controller的处理方法一般都类似这样:
所以,Interceptor的拦截范围其实就是Controller方法,它实际上就相当于基于AOP的方法拦截。因为Interceptor只拦截Controller方法,所以要注意,返回ModelAndView
并渲染后,后续处理就脱离了Interceptor的拦截范围。
使用Interceptor的好处是Interceptor本身是Spring管理的Bean,因此注入任意Bean都非常简单。此外,可以应用多个Interceptor,并通过简单的@Order
指定顺序。我们先写一个:
@Order(1)
@Component
public class LoggerInterceptor implements HandlerInterceptor {
final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("preHandle {}...", request.getRequestURI());
if (request.getParameter("debug") != null) {
PrintWriter pw = response.getWriter();
pw.write("<p>DEBUG MODE</p>");
pw.flush();
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("postHandle {}.", request.getRequestURI());
if (modelAndView != null) {
modelAndView.addObject("__time__", LocalDateTime.now());
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("afterCompletion {}: exception = {}", request.getRequestURI(), ex);
}
}
一个Interceptor必须实现HandlerInterceptor
接口,可以选择实现preHandle()
、postHandle()
和afterCompletion()
方法。preHandle()
是Controller方法调用前执行,是Controller方法正常返回后执行,而afterCompletion()
无论Controller方法是否抛异常都会执行,参数ex
就是Controller方法抛出的异常(未抛出异常是null
)。
我们再继续添加一个AuthInterceptor
,用于替代上一节使用AuthFilter
进行Basic认证的功能:
这个AuthInterceptor
是由Spring容器直接管理的,因此注入UserService
非常方便。
最后,要让拦截器生效,我们在WebMvcConfigurer
中注册所有的Interceptor:
@Bean
WebMvcConfigurer createWebMvcConfigurer(@Autowired HandlerInterceptor[] interceptors) {
return new WebMvcConfigurer() {
public void addInterceptors(InterceptorRegistry registry) {
for (var interceptor : interceptors) {
registry.addInterceptor(interceptor);
}
}
...
};
}
如果拦截器没有生效,请检查是否忘了在WebMvcConfigurer中注册。
在Controller中,Spring MVC还允许定义基于@ExceptionHandler
注解的异常处理方法。我们来看具体的示例代码:
异常处理方法没有固定的方法签名,可以传入Exception
、HttpServletRequest
等,返回值可以是void
,也可以是ModelAndView
,上述代码通过@ExceptionHandler(RuntimeException.class)
表示当发生RuntimeException
的时候,就自动调用此方法处理。
可以编写多个错误处理方法,每个方法针对特定的异常。例如,处理LoginException
使得页面可以自动跳转到登录页。
使用ExceptionHandler
时,要注意它仅作用于当前的Controller,即ControllerA中定义的一个ExceptionHandler
方法对ControllerB不起作用。如果我们有很多Controller,每个Controller都需要处理一些通用的异常,例如LoginException
,思考一下应该怎么避免重复代码?
从下载练习: (推荐使用IDE练习插件快速下载)
Spring MVC提供了Interceptor组件来拦截Controller方法,使用时要注意Interceptor的作用范围。