Spring MVC拦截器David发表在天码营
Spring WebMvc框架中的Interceptor,与Servlet API中的Filter十分类似,用于对Web请求进行预处理/后处理。通常情况下这些预处理/后处理逻辑是通用的,可以被应用于所有或多个Web请求,例如:
- 记录Web请求相关日志,可以用于做一些信息监控、统计、分析
- 检查Web请求访问权限,例如发现用户没有登录后,重定向到登录页面
- 打开/关闭数据库连接——预处理时打开,后处理关闭,可以避免在所有业务方法中都编写类似代码,也不会忘记关闭数据库连接
Spring MVC请求处理流程
上图是Spring MVC框架处理Web请求的基本流程,请求会经过DispatcherServlet
的分发后,会按顺序经过一系列的Interceptor
并执行其中的预处理方法,在请求返回时同样会执行其中的后处理方法。
HandlerInterceptor接口
Spring MVC中拦截器是实现了HandlerInterceptor
接口的Bean:
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception;
void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception;
}
preHandle()
:预处理回调方法,若方法返回值为true,请求继续(调用下一个拦截器或处理器方法);若方法返回值为false,请求处理流程中断,不会继续调用其他的拦截器或处理器方法,此时需要通过response产生响应;postHandle()
:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时可以通过modelAndView对模型数据进行处理或对视图进行处理afterCompletion()
:整个请求处理完毕回调方法,即在视图渲染完毕时调用
HandlerInterceptor
有三个方法需要实现,但大部分时候可能只需要实现其中的一个方法,HandlerInterceptorAdapter
是一个实现了HandlerInterceptor
的抽象类,它的三个实现方法都为空实现(或者返回true
),继承该抽象类后可以仅仅实现其中的一个方法:
public class Interceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("This is interceptor.");
return true;
}
}
配置Interceptor
定义HandlerInterceptor
后,需要在MVC配置中将它们应用于特定的URL中,下面是一个配置的例子:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
}
对应的XML配置是:
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="org.example.SecurityInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
实例:用户登录检查
根据前面几个小节的学习,现在需要实现用户登录检查——如果发现用户没有登录,跳转到登录页面,登录成功后跳转回之前访问的页面。
Interceptor实现检查逻辑
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (request.getSession().getAttribute(Constants.USER_SESSION_ATTR) != null) {
return true;
}
response.sendRedirect("/login?next=".concat(request.getRequestURI()));
return false;
}
}
为了实现跳转登录页面登录成功后能够返回当前页面,在Interceptor
中将当前URL作为/login
的参数next
LoginController
@Controller
@RequestMapping("/login")
public class LoginController {
public static final Logger logger = LoggerFactory.getLogger(LoginController.class);
@RequestMapping(method = RequestMethod.GET)
public String loginPage(@RequestParam("next") Optional<String> next) {
logger.info("next = {}", next);
return "login";
}
@RequestMapping(method = RequestMethod.POST)
public String login(@RequestParam("next") Optional<String> next, HttpSession session) {
logger.info("next = {}", next);
session.setAttribute(Constants.USER_SESSION_ATTR, "username");
return "redirect:".concat(next.orElse("/"));
}
}
在登录的POST方法中,除了将Session中放入user对象外,跳转到next
以便回到登录前的页面。
配置Interceptor
需要注意的是,登录页面本身(包括POST请求)不能应用Interceptor来拦截,否则会陷入无限循环中:
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}