过滤器Filter 和 拦截器Interceptor 有什么区别

参考:https://mp.weixin.qq.com/s/PzQlCjLLM1fjPc0y4poQOw

「一看就会一说就废」!这是典型基础不扎实的表现。

一、过滤器

过滤器的配置比较简单,直接实现Filter接口(implements Filter)即可。也可通过@WebFilter注解实现对特定URL拦截。可以看到Filter接口中定义了三个方法:

  • init():该方法在容器启动初始化过滤器时被调用,它在Filter的整个生命周期中只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
  • doFilter():容器中的每一次请求都会调用该方法,FilterChain用来调用下一个过滤器Filter。
  • destroy():当容器销毁过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器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中进行配置:

<web-app>
  ...
    <!-- 过滤器  -->
    <filter>
		<filter-name>CharacterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>	
		<init-param>
			<param-name>encoding</param-name>
			<param-value>utf-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>CharacterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 过滤器  -->
	<filter>
		<filter-name>MyFilter</filter-name>
		<filter-class>com.ly.myFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>MyFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
  ...
</web-app>

二、拦截器 Interceptor 

拦截器是链式调用,一个应用中可以同时存在多个拦截器Interceptor,一个请求也可以触发多个拦截器,每个拦截器的调用会根据它的声明顺序依次执行。

首先编写一个简单的拦截器处理类,请求拦截的实现类是HandlerInterceptor。HandlerInterceptor接口也定义了三个方法:

  • preHandle():这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false,意味着当前请求结束。return true;  //放行    return false;  //拦截
  • postHandle():只有在preHandle()方法返回true时才会执行。会在Controller中的方法调用之后,DispatcherServlet返回渲染视图之前被调用。有意思的是:postHandle()的调用顺序与preHandle()相反,先声明的拦截器preHandle()先执行,而先声明的postHandle()方法反而后执行。
  • afterCompletion():只有在preHandle()方法返回true时才会执行。在整个请求结束之后,DispatcherServlet渲染了对应的视图之后执行。
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);
		}
	}
}

四、总结

Filter和Interceptor的区别

  • Filter是基于函数回调的,而Interceptor则是基于Java反射的。
  • Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器。
  • Filter对几乎所有的请求起作用,而Interceptor只能对action(默认是.action结尾的url)请求起作用。
  • Interceptor可以访问Action的上下文,值栈里的对象,而Filter不能。
  • 在action的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。

  •  

猜你喜欢

转载自blog.csdn.net/qq_37358143/article/details/106664343