目录
1.拦截器概述
对于优秀的MVC框架,都会提供一些通用的操作,如请求数据的封装、类型转换、数据校验、解析上传的文件、防止表单的多次提交等。早期的MVC框架将这些操作都写死在核心控制器中,而这些常用的操作又不是所有的请求都需要实现的,这就导致了框架的灵活性不足,可扩展性降低,对此SpringMVC提供了Interceptor拦截器机制,类似于Servlet中的Filter过滤器,用于拦截用户的请求并做出相应的处理。比如通过拦截器来进行用户权限验证,或者用来判断用户是否已经登录。Spring MVC拦截器是可插拔式的设计,需要某一功能拦截器,只需在配置文件中应用该拦截器即可;如果不需要这个功能拦截器,只需在配置文件中取消应用该拦截器。
要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义。
1.实现HandlerInterceptor接口
2.实现WebRequestInterceptor接口
此处对于第一种实现HandlerInterceptor接口方法来详细介绍:
来看看HandlerInterceor接口的源码:
1.preHandle()方法:
调用时间:Controller方法处理之前
返回值为Boolean类型,如果返回false,表示拦截请求,不再向下执行,如果返回true,表示放行,程序继续向下执行(如果后面没有其他Interceptor,就会执行controller方法)。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化操作及对请求进行预处理。
若返回false,则中断执行,注意:不会进入afterCompletion
2.postHandle()方法:
调用前提:preHandle返回true
调用时间:Controller方法处理完之后,由于该方法会在DispatcherServlet进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图,可通过此方法对请求域中的模型和视图做进一步的修改。
备注:postHandle虽然post打头,但post、get方法都能处理
3.afterCompletion()方法:
调用前提:preHandle返回true
调用时间:DispatcherServlet进行视图的渲染之后,由于是在Controller方法执行完毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作。
2.拦截器的配置
Spring MVC 中配置拦截器(Interceptor),可以通过Java配置类的方式进行配置,也可以通过xml文件的方式进行配置。
先从Java配置类的方式来看:
@EnableWebMvc
: 表示启用Spring MVC的功能@Configuration
: 表示该类是Spring 中的配置类addPathPatterns
来表示拦截哪些请求excludePathPatterns
不拦截哪些请求?
:匹配一个字符*
: 任意一级路径**
:任意多级路径
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
/**
* SpringMVC会遍历每个Controller,来设置一些信息
* 为所有Controller添加路径前缀
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api",c->{
System.out.println(c.getClass());
return true;
});
}
/**
* 添加拦截器
* @param registry 拦截器注册器(注册拦截器:路径规则+拦截器对象)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DemoInterceptor())
//拦截所有的后端路径
.addPathPatterns("/api/**")
//排除登录和注册接口
.excludePathPatterns("/api/user/login")
.excludePathPatterns("/api/user/register");
}
}
在xml中进行配置:
<mvc:interceptors>
<bean class="com.example.web.interceptor.DemoInterceptor"/>
<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>
3.拦截器的执行流程
3.1单个拦截器的执行流程
在运行程序时,拦截器的执行是有一定顺序的,该顺序与配置文件中所定义的拦截器的顺序相关。
单个拦截器,在程序中的执行流程如下图所示:
1.程序先执行preHandle()方法,如果该方法的返回值为true,则程序会继续向下执行处理器中的方法,否则将不再向下执行。
2.在业务处理器(即控制器Controller类)处理完请求后,会执行postHandle()方法,然后会通过DispatcherServlet向客户端返回响应。
3.在DispatcherServlet处理完请求后,才会执行afterCompletion()方法。
3.2测试代码及结果展示
测试案例:通过一个测试程序来验证它的执行流程。
新建一个测试controller,代码如下:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class DemoController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello world");
return "successed";
}
}
然后,新建一个拦截器,实现HandlerInterceptor接口,并实现其中的方法。
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("DemoInterceptor......preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("DemoInterceptor......postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("DemoInterceptor......afterCompletion");
}
}
我们使用postman来测试接口:
运行项目查看结果:
3.3多个拦截器的执行流程
多个拦截器(假设有两个拦截器Interceptor1和Interceptor2,并且在配置文件中, Interceptor1拦截器配置在前),在程序中的执行流程如下图所示:
从图可以看出,当有多个拦截器同时工作时,它们的preHandle()方法会按照配置文件中拦截器的配置顺序执行,而它们的postHandle()方法和afterCompletion()方法则会按照配置顺序的反序执行。
3.4测试代码及结果展示
测试案例:新建两个拦截器:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoInterceptor implements HandlerInterceptor {
private static final Logger log= LoggerFactory.getLogger(DemoInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("DemoInterceptor......preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("DemoInterceptor......postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("DemoInterceptor......afterCompletion");
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SecondInterceptor implements HandlerInterceptor {
private static final Logger log= LoggerFactory.getLogger(DemoInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("SecondInterceptor......preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("SecondInterceptor......postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("SecondInterceptor......afterCompletion");
}
}
测试运行:
从结果可以看出,执行的顺序和图片中是一样的。
如果第一个拦截器return true; 而第二个拦截器 return false;结果如下: