参考:https://mp.weixin.qq.com/s/PzQlCjLLM1fjPc0y4poQOw
「一看就会一说就废」!这是典型基础不扎实的表现。
过滤器的配置比较简单,直接实现Filter接口(implements Filter)即可。也可通过@WebFilter注解实现对特定URL拦截。可以看到Filter接口中定义了三个方法:
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 前置");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 处理中");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("Filter 后置");
}
}
在web.xml中进行配置:
...
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
utf-8
CharacterEncodingFilter
/*
MyFilter
com.ly.myFilter
MyFilter
/*
...
拦截器是链式调用,一个应用中可以同时存在多个拦截器Interceptor,一个请求也可以触发多个拦截器,每个拦截器的调用会根据它的声明顺序依次执行。
首先编写一个简单的拦截器处理类,请求拦截的实现类是HandlerInterceptor。HandlerInterceptor接口也定义了三个方法:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class MyHandlerInterceptor1 implements HandlerInterceptor {
/**
* controller执行前调用此方法
* 返回true表示继续执行,返回false中止执行
* 这里可以加入登录校验、权限拦截等
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyHandlerInterceptor1----拦截器Interceptor 前置。。。");
return true;//return true;//放行 return false;//拦截,程序终止
}
/**
* 只有preHandle()方法返回true后才能执行此方法
* controller执行后但未返回视图前调用此方法
* 这里可在返回用户前对模型数据进行加工处理,比如这里加入公用信息以便页面显示
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyHandlerInterceptor1----Interceptor 处理中。。。");
}
/**
* 只有preHandle()方法返回true后才能执行此方法
* controller执行后且视图返回后调用此方法
* 这里可得到执行controller时的异常信息,比如 可记录操作日志,资源清理等
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyHandlerInterceptor1----Interceptor 后置。。。");
}
}
过滤器 和 拦截器 均体现了AOP
的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。
1、实现原理不同
底层实现方式大不相同。过滤器Filter是基于函数回调的;拦截器Interceptor则是基于Java的反射机制(动态代理)实现的。
这里重点说下过滤器。
在我们自定义的过滤器中都会实现一个doFilter()方法,这个方法有一个FilterChain参数,二实际上它是一个回调接口。ApplicationFilterChain是它的实现类,这个类内部也有一个doFilter()方法就是回调方法。
public interface FilterChain {
void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
ApplicationFilterChain 里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()里调用各个自定义xxxFilter过滤器,并执行doFilter()方法。
public final class ApplicationFilterChain implements FilterChain {
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
...//省略
internalDoFilter(request,response);
}
private void internalDoFilter(ServletRequest request, ServletResponse response){
if (pos < n) {
//获取第pos个filter
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
...
filter.doFilter(request, response, this);
}
}
}
而每个xxxFilter会先执行自身的doFilter()过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse); 也就是回调ApplicationFilterChain 的doFilter()方法,以此循环执行实现函数回调。
2、使用范围不同
我们可以看到过滤器实现的是import javax.servlet.Filter; 接口,这个接口是在Servlet规范中定义的,也就是说过滤器Filter的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
而拦截器实现的是import org.springframework.web.servlet.HandlerInterceptor;接口,是一个Spring组件,并由Spring管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
3、触发时机不同
过滤器Filter是在请求进入容器后,但在进入Servlet之前进行预处理,请求结束是在Servlet处理完以后。
拦截器Interceptor是在请求进入Servlet后,在进入Controller之前进行预处理的,Controller中渲染了对应的视图之后请求结束。
4、拦截的请求范围不同
在上边我们已经同时配置了过滤器和拦截器,再建一个Controller
接收请求测试一下。
@Controller
@RequestMapping("/user/")
public class UserController {
@RequestMapping("/toLogin")
@ResponseBody
public String showList(){
System.out.println("我是controller");
return "login";
}
}
项目启动过程中发现,过滤器的init()
方法,随着容器的启动进行了初始化。
看到控制台的打印日志如下:
执行顺序 :Filter 处理中
-> Interceptor 前置
-> 我是controller
-> Interceptor 处理中
-> Interceptor 处理后
5、注入Bean情况不同
在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。
@Component
public class TestServiceImpl implements TestService {
@Override
public void a() {
System.out.println("我是方法A");
}
}
①在过滤器中注入service
@Component
public class MyFilter implements Filter {
@Autowired
private TestService testService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 处理中");
testService.a();
filterChain.doFilter(servletRequest, servletResponse);
}
}
看到控制台的打印日志如下:
执行顺序 :Filter 处理中 -> 我是方法A -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后
②在拦截器中注入service
@Component
public class MyHandlerInterceptor1 implements HandlerInterceptor {
@Autowired
private TestService testService; // testService:null
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
testService.a(); // testService:null
System.out.println("MyHandlerInterceptor1----Interceptor 处理中。。。");
}
}
结果发现报错了。debug发现注入的service是null???
这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理的。
❝拦截器:老子今天要进洞房;Spring:兄弟别闹,你媳妇我还没生出来呢!
解决方案...
6、控制执行顺序不同
过滤器用@Order
注解控制执行顺序,通过@Order
控制过滤器的级别,值越小级别越高越先执行。
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order
手动设置控制,值越小越先执行。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
}
执行顺序:
结果发现,preHandle()方法按顺序执行,而postHandle()方法和preHandle()被调用的顺序居然是相反的!
如果实际开发中严格要求执行顺序,那就需要特别注意这一点。
「那为什么会这样呢?」 得到答案就只能看源码了,我们知道controller中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的doDispatch()方法,而拦截器postHandle()和preHandle()方法就是在其中被调用的。
doDispatch()方法中分别调用了applyPreHandle()
、applyPostHandle()方法,分别查看applyPreHandle()
、applyPostHandle()方法方法,就会发现端倪,两个方法在调用拦截器数组HandlerInterceptor[],循环顺序竟然是相反的,导致postHandle()
、preHandle()
方法执行的顺序相反。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 获取可以执行当前Handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 执行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if(!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if(!ObjectUtils.isEmpty(interceptors)) {
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}