SpringBoot2实践系列(二十七):Listener,Filter,Interceptor

star2017 1年前 ⋅ 424 阅读

  Listener 监听器,Filter 过滤器,Interceptor 拦截器是 Java Web领域非常重要的三大神器(组件),会经常使用到。

  关注这三个的知识点本篇不做描述,主要记录在Spring Boot框架中这三大组件的使用。

  前面文章也有提到:Spring事件监听,SpringMVC之HandlerInterceptor拦截器, Spring Boot实践系列(二十二):Web相关配置详解

  官方文档:Spring Boot -> Application Events and Listeners,Spring Boot -> Servlets, Filters, Listeners, Spring Boot -> Add a Servlet, Filter, or Listener to an Application

Spring Boot

在 Spring Boot 框架中,将自定义的 Servlet, Filter, Listener 注册为 Spring Bean有两种方式:

  1. Spring Boot框架中在自定义的 Servlet, Filter, Listener 三大组件类上添加@Component 或 @Configuration注解将监听器注册为 Spring Bean, 可调用 IoC 容器的产资源(Bean)。
  2. 在自定义的 Servlet, Filter, Listener 类上添加@WebServlet, @WebFilter, @WebListener注解,会自动将自定义三大给件类注册为 Servlet 组件并启动。

注意:如果使用的是 Spring Boot 内嵌的 Servlet 容器,还需要在使用了@Configuration注解的类上添加@ServletComponentScan注解; 如果部署在外部独立的Servlet 容器,则不需要@ServletComponentScan注解,由独立容器的内部发现机制将自定义的三大组件类注册为 Servlet组件。

@ServletComponentScan注解,是在使用 Spring Boot 内嵌的 Servlet 容器时需要添加,用于是启用扫描使用了@WebServlet, @WebFilter, @WebListener注解的类并将之注册为 Servlet 组件, 可通过valuebasePackages属性指定自定义的 Servlet, Filter, Listener 所在的包路径, 就会自动将该包下使用了 @WebServlet, @WebFilter, @WebListener 注解的自定义类注册 Servlet 组件; 默认情况下会从 @ServletComponentScan注解所在类的包开始扫描,所以很多时候直接把这个注解放在启动入口类上。

Listener

Listener是 Servlet 规范,由 Servlet 容器提供支持,随Servlet容器启动就启动监听, 监听到事件时,在监听器里面相应的方法里执行处理。
Servlet 提供的3个基本监听器接口:ServletContextListener,ServletRequestListener,HttpSessionListener,使用时实现用到的接口,重写里面的方法 。

  1. 根据创建的Session数统计在线人数
    根据 Session 统计 Web 站点的计在线人数并不十分准确, 若用户离线了但 Session 仍在有效期内则认为这个用户仍在线。

    /**
    * @Name: CountOnlineUserListener
    * @Desc: 统计在线用户数
    **/
    @WebListener
    public class CountOnlineUserListener implements HttpSessionListener {
    
       private static final Logger logger = LogManager.getLogger(CountOnlineUserListener.class);
    
       private static final String TOTAL_ONLINE_USER="totalOnlineUser";
    
       private RedisTemplate<String, Integer> redisTemplate;
    
       @Override
       public void sessionCreated(HttpSessionEvent event) {
    
           HttpSession session = event.getSession();
           //session默认有效时间是30分钟,可不设置
           session.setMaxInactiveInterval(30 * 60);
           ServletContext servletContext = session.getServletContext();
           WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
           redisTemplate = (RedisTemplate<String, Integer>) webApplicationContext.getBean("redisTemplate");
           Integer integer = redisTemplate.opsForValue().get(TOTAL_ONLINE_USER);
           integer = (integer == null ? 0 : integer);
           //创建Session数
           redisTemplate.opsForValue().set(TOTAL_ONLINE_USER,integer + 1);
       }
    
       @Override
       public void sessionDestroyed(HttpSessionEvent se) {
           Integer integer = redisTemplate.opsForValue().get(TOTAL_ONLINE_USER);
           redisTemplate.opsForValue().set(TOTAL_ONLINE_USER,integer - 1);
       }
    }
    
  2. 容器启动监听器,执行初始化操作
    因上面的统计在线用户数是存在 Redis 中, 如果系统宕机或崩溃是不会清空引在线用户数的值的,会存在较大误差。所在在系统起动时重置下。如果是单机系统,可以将计算的用户数放到ServletContext域中。

    /**
    * @Desc: 重写ServletContext
    * @Date: 2018/8/14 11:29
    **/
    @WebListener
    public class DydContextLoaderListener implements ServletContextListener {
    
       private RedisTemplate<String, Integer> redisTemplate;
    
       /**
       * @Desc: 容器启动增加初始化操作
       * @Param: [sce]
       * @Return: void
       **/
       @Override
       public void contextInitialized(ServletContextEvent sce) {
           ServletContext servletContext = sce.getServletContext();
           WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
           redisTemplate = (RedisTemplate<String, Integer>) webApplicationContext.getBean("redisTemplate");
           //重置在线人数
           redisTemplate.opsForValue().set("totalOnlineUser",0);
       }
    
       @Override
       public void contextDestroyed(ServletContextEvent sce) {
    
       }
    }
    

Filter

Filter过滤器是 Servlet 规范,由 Servlet 容器提供支持,在 Servlet 之前和之后起作用,无法调用 Spring IOC 中的资源。

基础知识,参考 Servlet 编写过滤器

Interceptor

Interceptor 是 Spring 提从的组件,由 Spring 框架提供支持,由 Spring 管理,可在拦截器中注入 IoC 中的资源。

  1. 登录拦截器

    /**
     * @ClassName LoginInterceptor
     * @Description 登录验证拦截器
     **/
    @Component
    public class LoginInterceptor implements HandlerInterceptor {
    
        private static final Logger log = LoggerFactory.getLogger(LoginInterceptor.class);
    
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
            HttpSession session = httpServletRequest.getSession();
            // 判断是否登录
            if (null != session.getAttribute("SESSION_USER_OBJ")) {
                return true;
            } else {
                httpServletResponse.sendRedirect("/user/toLogin");
                return false;
            }
        }
    
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    
        }
    }
    
  2. 配置拦截器和放行路径
    注:新的 Spring 5.0 是实现 WebMvcConfigurer 接口,WebMvcConfigurerAdapter 已标注为过期。

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(loginInterceptor())
                    .addPathPatterns("/**")
                    .excludePathPatterns("/login");
        }
    
    /*
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //注册自定义拦截器,添加拦截路径和排除拦截路径
            registry.addInterceptor(new LoginInterceptor())
                    .addPathPatterns("/**")
                    .excludePathPatterns("/index")
                    .excludePathPatterns("/user/register")
                    .excludePathPatterns("/user/login")
                    .excludePathPatterns("/error");
        }
    */
    
        @Bean
        public LoginInterceptor loginInterceptor() {
            return new LoginInterceptor();
        }
    }
    

三大神器的执行顺序:监听电话 -> 过滤垃圾信号 -> 拦截恐怖事件,任务处理完则原路返回。

注意

在注册 监听器、过滤器或拦截器时,最好是先通过 @Bean 注解将其声明为 Bean,注册时调用申请为 Bean 的方法 来使用。

因为这三大组件在注册时先于 Bean 注册,如果在这三大给件里注入了其它 Bean,在引用时就会报空指针。例如,在拦截器里注入了 RedisTemplate,在 WebMvcConfigurer 添加拦截器时,使用 new 的方式,则

参考资料

  1. Java三大器之监听器
更多内容请访问:IT源点

全部评论: 0

    我有话说: