首先说明一下二者的区别:
1. 拦截器基于java的反射机制,而过滤器是基于函数回调
2. 拦截器不依赖于servlet容器,过滤器依赖servlet容器
3. 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用
4. 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
5. 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器注入一个service,可以调用业务逻辑
过滤器和拦截器之间的关系如下图,Filter包裹Servlet,Servlet包裹Interceptor
过滤器的触发时机是容器后,servlet之前,所以过滤器的 doFilter ( ServletRequest request, ServletResponse response, FilterChain chain ) 的入参是ServletRequest,而不是HttpServletRequest,因为过滤器在HttpServlet之前
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("before..."); chain.doFilter(request, response); System.out.println("after..."); }
这个chain.doFilter(request, response) 作用是将请求转发给过滤器链上下一个对象,下一个对象是指filter,如果没有filter那就是你请求的资源
拦截器是被包裹在过滤器之中
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); }
preHandle 这个方法是在过滤器的 chain.doFilter( request, response ) 方法的前一步执行,且在调用Controller之前调用,当返回false后,会跳过之后的拦截器,并且不会执行所有的拦截器 postHandle ,并且调用返回true的拦截器的 afterCompletion 方法
postHandle 是调用 Controller 之后被调用,但是在渲染 View 页面之前
afterCompletion 是调用完 Controller 接口,渲染 View 页面最后调用,返回true的拦截器都会调用该拦截器的 afterCompletion 方法,顺序相反。这个方法也是在过滤器返回给前端前一步执行
多个过滤器和拦截器是如何配置的呢,下面结合Spring Boot写个demo,地址是 https://gitee.com/colin220/intercept
首先写一个Controller,内容如下:
@Controller public class InterceptController { @RequestMapping("/index") public ResponseEntity<?> index() { System.out.println("Controller Handler"); return ResponseEntity.ok("index"); } }
这个Controller比较简单,浏览器访问 http://localhost:8080/index 会返回一个字符串 "index"。
写三个过滤器,需要实现javax.servlet.Filter接口,实现其中的三个方法。这三个过滤器分别命名为 FirstFilter SecondFilter ThirdFilter,以FirstFilter为例,其他的两个类内容和其基本一致,其代码内容如下:
import javax.servlet.*; import java.io.IOException; public class FirstFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("FirstFilter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("FirstFilter doFilter pre"); filterChain.doFilter(servletRequest, servletResponse); System.out.println("FirstFilter doFilter aft"); } @Override public void destroy() { System.out.println("FirstFilter destroy"); } }
然后写一个配置类,来配置这三个过滤器,其代码内容如下:
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean filterRegistrationBeanFirst() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new FirstFilter()); filterRegistrationBean.addUrlPatterns("/index/*"); filterRegistrationBean.setName("FirstFilter"); filterRegistrationBean.setOrder(1); return filterRegistrationBean; } @Bean public FilterRegistrationBean filterRegistrationBeanSecond() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new SecondFilter()); filterRegistrationBean.addUrlPatterns("/index/*"); filterRegistrationBean.setName("SecondFilter"); filterRegistrationBean.setOrder(2); return filterRegistrationBean; } @Bean public FilterRegistrationBean filterRegistrationBeanThird() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new ThirdFilter()); filterRegistrationBean.addUrlPatterns("/index/*"); filterRegistrationBean.setName("ThirdFilter"); filterRegistrationBean.setOrder(3); return filterRegistrationBean; } }
因为有三个Filter,因此需要三个org.springframework.boot.web.servlet.FilterRegistrationBean,每个FilterRegisterationBean中需要设置属性 filterRegistrationBean.setFilter(new FirstFilter( )) ,并设置这个Filter拦截的url,顺序order等属性。
接下来写三个拦截器,需要实现 org.springframework.web.servlet.HandlerInterceptor 接口,重写其中的三个方法,这三个拦截器分别命名为FirstInterceptor SecondInterceptor ThirdInterceptor,以FirstInterceptor为例,其他两个类内容基本一致,其代码内容如下:
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FirstInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 在请求处理之前进行调用(Controller方法调用之前) System.out.println("FirstInterceptor preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后 ) System.out.println("FirstInterceptor postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作) System.out.println("FirstInterceptor afterCompletion"); } }
然后写一个配置类,来配置这三个拦截器。这个配置类要实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer。因为WebMvcConfigurerAdapter 在Spring5.0已被废弃,所以采用WebMvcConfigurer,其代码内容如下:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/index/**"); registry.addInterceptor(new SecondInterceptor()).addPathPatterns("/index/**"); registry.addInterceptor(new ThirdInterceptor()).addPathPatterns("/index/**"); } }
其中InterceptorRegistry内部有个List,这个addInterceptor( )方法是用来存放添加进去的interceptor,按照添加的先后顺序,依次拦截。
这样就完成了多个过滤器和多个拦截器的配置,浏览器访问 http://localhost:8080/index 可以看到控制台打印内容如下:
END