JAVA-过滤器和拦截器2

1、实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于 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() 方法,以此循环执行实现函数回调。

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    filterChain.doFilter(servletRequest, servletResponse);
}

2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,导致它只能在 web 程序中使用。

而拦截器 (Interceptor) 它是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application、Swing 等程序中。

3、触发时机不同
过滤器 和 拦截器的触发时机也不同
过滤器 Filter 是在请求进入容器后,但在进入 servlet 之前进行预处理,请求结束是在 servlet 处理完以后。

拦截器 Interceptor 是在请求进入 servlet 后,在进入 Controller 之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

4、拦截的请求范围不同
在上边我们已经同时配置了过滤器和拦截器,再建一个 Controller 接收请求测试一下。

@Controller
@RequestMapping()
public class Test {

@RequestMapping("/test1")
@ResponseBody
public String test1(String a) {
    System.out.println("我是controller");
    return null;
}

}
5、注入 Bean 情况不同
在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些 service 服务。

下边我们分别在过滤器和拦截器中都注入 service,看看有什么不同?

@Component
public class TestServiceImpl implements TestService {

@Override
public void a() {
    System.out.println("我是方法A");
}

}
过滤器中注入 service,发起请求测试一下 ,日志正常打印出 “我是方法A”。

@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,发起请求测试一下 ,竟然 TM 的报错了,debug 跟一下发现注入的 service 怎么是 Null 啊?

这是因为加载顺序导致的问题,拦截器加载的时间点在 springcontext 之前,而 Bean 又是由 spring 进行管理。

拦截器:老子今天要进洞房;
Spring:兄弟别闹,你媳妇我还没生出来呢!

解决方案也很简单,我们在注册拦截器之前,先将 Interceptor 手动进行注入。注意:在 registry.addInterceptor() 注册的是 getMyInterceptor() 实例。

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

@Bean
public MyInterceptor getMyInterceptor(){
    System.out.println("注入了MyInterceptor");
    return new MyInterceptor();
}

@Override
public void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
}

}
6、控制执行顺序不同
实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

过滤器用 @Order 注解控制执行顺序,通过 @Order 控制过滤器的级别,值越小级别越高越先执行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
拦截器默认的执行顺序,就是它的注册顺序,也可以通过 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() 方法反而会后执行。

postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后
那为什么会这样呢? 得到答案就只能看源码了,我们要知道 controller 中所有的请求都要经过核心组件 DispatcherServlet 路由,都会执行它的 doDispatch() 方法,而拦截器 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);
        }
    }
    ...........
}

看看两个方法 applyPreHandle()、applyPostHandle()具体是如何被调用的,就明白为什么 postHandle()、preHandle() 执行顺序是相反的了。

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);
}
}
}
发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致 postHandle()、preHandle() 方法执行的顺序相反

你可能感兴趣的:(JAVA,过滤器,spring,java,filter,spring,boot)