一、拦截器与过滤器的区别

在这里插入图片描述

  1. 过滤器 (Filter)
    过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。
  • init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
  • doFilter() :容器中的每一次请求都会调用该方法FilterChain 用来调用下一个过滤器 Filter
  • destroy(): 当容器销毁过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
  1. 拦截器 (Interceptor)
    拦截器它是链式调用,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。首先编写一个简单的拦截器处理类,请求的拦截是通过HandlerInterceptor 来实现,看到HandlerInterceptor 接口中也定义了三个方法。
  • preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
  • postHandle():只有在 preHandle()方法返回值为true 时才会执行会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。有意思的是:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
  • afterCompletion():只有在 preHandle()方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。
  1. 过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
    如下图:
    在这里插入图片描述
  2. 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。

过滤器拦截器运行先后步骤:
在这里插入图片描述
其中第2步,SpringMVC的机制是由DispaterServlet来分发请求给不同的Controller,其实这一步是在Servlet的service()方法中执行的
在这里插入图片描述

  1. 过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射,代理分静态代理和动态代理,动态代理是拦截器的简单实现。
  2. 拦截器加载的时间点在springcontext之前。
  3. 过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用
  4. 过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能

二、何时使用拦截器?何时使用过滤器?何时使用监听器

  1. 如果是非spring项目,那么拦截器不能用,只能使用过滤器。
  2. 如果是处理controller前后,既可以使用拦截器也可以使用过滤器。
  3. 如果是处理dispaterServlet前后,只能使用过滤器。
  4. 监听 Servlet 上下文用来初始化一些数据、监听 HTTP Session 用来获取当前在线的人数、监听客户端请求的 ServletRequest 对象来获取用户的访问信息等等,使用监听器。
  • 具体使用场景
  1. 过滤器:可以对请求的URL进行过滤, 对敏感词过滤,
  2. 拦截器:性能分析, 权限检查, 日志记录

灵活性上说拦截器功能更强大些,Filter能做的事情,他都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。

三、spring boot如何使用过滤器、拦截器

  1. Spring boot过滤器的使用(两种方式)
  1. 使用spring boot提供的FilterRegistrationBean注册Filter
  2. 使用原生servlet注解定义Filter

两种方式的本质都是一样的,都是去FilterRegistrationBean注册自定义Filter

  • 方式一:

①、先定义Filter:

import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // do something 处理request 或response
        System.out.println("filter1");
        // 调用filter链中的下一个filter
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
    }
}

②、注册自定义Filter

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean registrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }
}
  • 方式二:
// 注入spring容器
@Component
// 定义filterName 和过滤的url
@WebFilter(filterName = "my2Filter" ,urlPatterns = "/*")
public class My2Filter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filter2");
    }
    @Override
    public void destroy() {
    }
}
  1. Spring boot拦截器的使用:

①、定义拦截器:

public class MyInterceptor implements HandlerInterceptor {
    @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, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

②、配置拦截器:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor());
    }
}

③Controller演示:

@RestController
public class UController {
    @GetMapping("/home")
    public String home(){
        System.out.println("home");
        return "myhome";
    }
}

四、过滤器拦截器完整例子

在这里插入图片描述

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor());
        registry.addInterceptor(new MyInterceptor2());
    }
}
@Order(1)
@Component
@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter1-----过滤器的init()方法,随着容器的启动进行了初始化");
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // do something 处理request 或response
        System.out.println("filter1");
        // 调用filter链中的下一个filter
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
        System.out.println("MyFilter1--------过滤器的destroy()方法,随着容器的关闭而进行");
    }
}
@Order(2)
@Component
@WebFilter(filterName = "myFilter2",urlPatterns = "/*")
public class MyFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter2------过滤器的init()方法,随着容器的启动进行了初始化");
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // do something 处理request 或response
        System.out.println("filter2");
        // 调用filter链中的下一个filter
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
        System.out.println("MyFilter2-----过滤器的destroy()方法,随着容器的关闭而进行");
    }
}
@Order(3)
@Component
@WebFilter(filterName = "myFilter3",urlPatterns = "/*")
public class MyFilter3  implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFilter3------过滤器的init()方法,随着容器的启动进行了初始化");
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // do something 处理request 或response
        System.out.println("filter3");
        // 调用filter链中的下一个filter
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
        System.out.println("MyFilter3-------过滤器的destroy()方法,随着容器的关闭而进行");
    }
}
public class MyInterceptor implements HandlerInterceptor {
    @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, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}
@Controller
public class MyController {
    @ResponseBody
    @RequestMapping("myTest")
    public String test(){
        System.out.println("controller.method invoked");
        return "ok";
    }
}

order定义filter的优先级,值越小优先级越高,因此运行顺序为

filter3—doFilter—>filter2—doFilter—>filter1—doFilter—>控制器方法—>返回到filter1—>返回到filter2—>返回到filter3

注意:@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响
在这里插入图片描述

可以看到只有执行的时候,才会按filter1,filter2,filter3的顺序执行。同时我们注意到:先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。也即是:postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的。如果实际开发中严格要求执行顺序,那就需要特别注意这一点。那为什么会这样呢? 得到答案就只能看源码了,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()、preHandle()方法便是在其中调用的。

  • doDispatch方法
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 Web开发中的监听器(listener)就是由application、session、request三个对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件,如下所示:

  • ServletContextListener:对Servlet上下文的创建和销毁进行监听。
  • ServletContextAttributeListener:监听Servlet上下文属性的添加、删除和修改。
  • HttpSessionListener:对Session的创建和销毁进行监听。
  • HttpSessionAttributeListener:对Session对象中属性的添加、删除和修改进行监听。
  • HttpSessionBindingListener:监听Http会话中对象的绑定信息。
  • HttpSessionActivationListener:监听器监听Http会话的情况。
  • ServletRequestListener:对请求对象的初始化和销毁进行监听。
  • ServletRequestAttributeListener:对请求对象属性的添加、删除和修改进行监听。

在这里插入图片描述

  1. 统计网站最多在线人数监听器的例子
/**
 * 上下文监听器,在服务器启动时初始化onLineCount和maxOnLineCount两个变量,
 * 并将其置于服务器上下文(ServletContext)中,其初始值都是0。
 */
@WebListener
public class InitListener implements ServletContextListener {
    public void contextDestroyed(ServletContextEvent evt) {
    }
    public void contextInitialized(ServletContextEvent evt) {
        evt.getServletContext().setAttribute("onLineCount", 0);
        evt.getServletContext().setAttribute("maxOnLineCount", 0);
    }
}
/**
 * 会话监听器,在用户会话创建和销毁的时候根据情况修改onLineCount和maxOnLineCount的值。
 */
@WebListener
public class MaxCountListener implements HttpSessionListener {
    public void sessionCreated(HttpSessionEvent event) {
        ServletContext ctx = event.getSession().getServletContext();
        int count = Integer.parseInt(ctx.getAttribute("onLineCount").toString());
        count++;
        ctx.setAttribute("onLineCount", count);
        int maxOnLineCount = Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());
        if (count > maxOnLineCount) {
            ctx.setAttribute("maxOnLineCount", count);
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            ctx.setAttribute("date", df.format(new Date()));
        }
    }
    public void sessionDestroyed(HttpSessionEvent event) {
        ServletContext app = event.getSession().getServletContext();
        int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
        count--;
        app.setAttribute("onLineCount", count);
    }
}
  • 新建一个servlet处理
@WebServlet(name = "SessionServlet",value = "/sessionCount")
public class SessionServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        //获取上下文对象
        ServletContext servletContext = this.getServletContext();
        Integer onLineCount = (Integer) servletContext.getAttribute("onLineCount");
        System.out.println("invoke doGet");
        PrintWriter out = resp.getWriter();
        out.println("<html><body>");
        out.println("<h1>" + onLineCount + "</h1>");
        out.println("</body></html>");
    }
}
  1. springboot监听器的使用(以实现异步Event监听为例子)
  • 场景

很多时候当我们完成某些业务后需要给用户推送相关消息提醒。对于这种非核心业务功能我们可以拿出来,创建一个事件去异步执行,从而实现核心业务和子业务的解耦。

  • 实现

定义事件类 Event

创建一个类,继承ApplicationEvent,并重写构造函数。ApplicationEvent是Spring提供的所有应用程序事件扩展类。

public class Event extends ApplicationEvent {
    private static final long serialVersionUID = 1L;
    private String msg ;
    private static final Logger logger=LoggerFactory.getLogger(Event.class);
    public Event(String msg) {
        super(msg);
        this.msg = msg;
        logger.info("add event success! message: {}", msg);
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}

创建一个用于监听指定事件的类,需要实现ApplicationListener接口,说明它是一个应用程序事件的监听类。注意这里需要加上@Component注解,将其注入Spring容器中。

@Component
public class MyListener implements ApplicationListener<Event>{
    private static final Logger logger= LoggerFactory.getLogger(MyListener.class);
    @Override
    public void onApplicationEvent(Event event) {
        logger.info("listener get event,sleep 2 second...");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("event msg is:{}",event.getMsg());
    }
}
  • 事件发布
    事件发布很简单,只需要使用Spring 提供的ApplicationEventPublisher来发布自定义事件
 @RequestMapping("/notice/{msg}")
    @ResponseBody
    public void notice(@PathVariable String msg){
        logger.info("begin>>>>>");
        applicationEventPublisher.publishEvent(new Event(msg));
        logger.info("end<<<<<<<");
    }
}
  • 测试
    在这里插入图片描述
  • 异步执行
    默认是没有开启异步的,我们需要手动配置开启异步功能,很简单,只需要在配置类上加上@EnableAsync注解就行了,该注解用于声明启用Spring的异步方法执行功能,需要和@Configuration注解一起使用,我们可以直接加在启动类上。然后在监听方法上加上@Async注解,说明当前方法使用异步去执行。
@Component
public class MyListener implements ApplicationListener<Event>{
    private static final Logger logger= LoggerFactory.getLogger(MyListener.class);
    @Override
    @Async
    public void onApplicationEvent(Event event) {
        logger.info("listener get event,sleep 2 second...");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("event msg is:{}",event.getMsg());
    }
}

在这里插入图片描述
可以发现已经实现了异步功能,主线程为nio-8080-exec-1,监听线程为 task-1。从浏览器反应可以看出,接口直接返回了,并没有等监听线程执行完后才返回。

  • 自定义异步线程池

用默认的Spring线程池会有啥问题呢?SimpleAsyncTaskExecutor不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。很有可能导致OOM。创建配置类(加上@Configuration),实现AsyncConfigurer接口,重写Executor方法。这里我们可以将原先配置在启动类上的@EnableAsync注解放到这个类上而不再直接加在主启动类上。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class);
    /**
     * 自定义异步线程池
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //核心线程数
        taskExecutor.setCorePoolSize(2);
        //最大线程数
        taskExecutor.setMaxPoolSize(10);
        //队列大小
        taskExecutor.setQueueCapacity(15);
        //线程名的前缀
        taskExecutor.setThreadNamePrefix("async-thread-");
        taskExecutor.initialize();
        return taskExecutor;
    }
    /**
     * 捕捉IllegalArgumentException异常
     * @return
     */
   public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
       return new MyAsyncExceptionHandler();
    }

    static class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler{
        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
            logger.info("TASK Exception message - " + throwable.getMessage());
            logger.info("Method name - " + method.getName());
            for (Object param : objects) {
                logger.info("Parameter value - " + param);
            }
        }
    }
}

六、 过滤器、拦截器、监听器对比

在这里插入图片描述

参考文章
参考文章
参考文章
参考文章
参考文章

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐