Servlet监听器和Spring监听器

1.Servlet监听器

  • ServletContext、HttpSession、ServletRequest的监听都是通过javax.servlet 包下定义的,他们都是servlet的特性,换句话说,他们不依赖于Spring,而依赖于web容器(tomcat容器),他们的加载优先于Spring容器
  • Servlet监听器有用处,但是没有Spring监听器用的多,用的方便;并且通常可以用HttpServletRequest和HttpServletResponse实现相同的需求
  • Servlet监听器提供了很多接口,在这些接口的实现类中需要重写接口方法并且要制定对应的监听事件,监听事件也会直接影响监听目标
    参考链接1
    参考链接2
    参考链接3统计在线人数
    其实统计人数用httpServletRequest().getServletContext().setAttribute();来直接获取Servlet上下文实现

1.1Servlet上下文监听

  • 补充:在Spring项目中,启动Spring会先启动Servlet(tomcat),Spring的init()紧跟其后
    而关闭Spring项目会之后会立马destroyed()Servlet容器,因此这个ServletContext监听器可以完全被ApplicationListener监听器代替,ApplicationListener是Spring3.0沿用至今最流行的全局监听器

可以监听ServletContext对象的创建和删除以及属性的添加、删除和修改等操作。该监听器需要使用到如下两个接口类:

ServletContextAttributeListener:监听对ServletContext属性的操作,如增加、删除、修改操作。

ServletContextListener:监听ServletContext。

当创建ServletContext时,激发contextInitialized(ServletContextEvent sce)方法;

当销毁ServletContext时,激发contextDestroyed(ServletContext- Event sce)方法。

1.2Http会话监听

  • 在Spring项目中对Session域进行监控仍然用的是HttpSessionListener,其来自于Servlet2.3
  • 用来监听 Web 应用种的 Session 对象,通常用于统计在线情况。

可以监听Http会话活动情况、Http会话中属性设置情况,也可以监听Http会话的active、pasivate情况等。

该监听器需要使用到如下多个接口类:

HttpSessionListener:监听HttpSession的操作。

当创建一个Session时,激发sessionCreated (SessionEvent se)方法;

当销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se) 方法。

HttpSessionActivationListener:用于监听Http会话active、passivate情况。

HttpSessionAttributeListener:监听HttpSession中属性的操作。

当在Session增加一个属性时,激发attributeAdded(HttpSessionBindingEvent se) 方法;

当在Session删除一个属性时,激发attributeRemoved(HttpSessionBindingEvent se)方法;

在Session属性被修改时,激发attributeReplaced(HttpSessionBindingEvent se) 方法。

1.3客户端Request请求监听

  • 用来监听 Request 对象的属性操作。
  • ServletRequestListener监听器来自于Servlet2.4

ServletRequestListener接口类。

ServletRequestAttrubuteListener接口类。

其实现类实现了ServletContextAttributeListener和ServletContextListener两个接口类中的5个方法

● contextInitialized(ServletContextEvent s)方法用来初始化ServletContext对象。

● contextDestroyed(ServletContextEvent s)方法在上下文中删除某个属性时调用。

● attributeAdded(ServletContextAttributeEvent sa)方法在上下文中添加一个新的属性时调用。

● attributeReplaced(ServletContextAttributeEvent sa)方法在更新属性时调用。

● attributeRemoved(ServletContextAttributeEvent sa)方法在上下文中删除某个属性时会被调用。

2.Servlet监听事件

在实现了Servlet监听器接口的方法中,需要重写其接口抽象方法,而其抽象方法的形参,则对应了一个监听器对象

2.1不带Attribute监听事件的创建和销毁

@WebListener
public class MyServletListener implements ServletContextListener {

  @Override
  public void contextInitialized(ServletContextEvent sce) {
    ServletContextListener.super.contextInitialized(sce);
  }

  @Override
  public void contextDestroyed(ServletContextEvent sce) {
    ServletContextListener.super.contextDestroyed(sce);
  }}

————————————————————————————————————————————————————————————————————————
@WebListener
public class MyServletListener implements HttpSessionListener {

  @Override
  public void sessionCreated(HttpSessionEvent se) {
    HttpSessionListener.super.sessionCreated(se);
  }

  @Override
  public void sessionDestroyed(HttpSessionEvent se) {
    HttpSessionListener.super.sessionDestroyed(se);
  }}

—————————————————————————————————————————————————————————————————————————
	@WebListener
public class MyServletListener implements ServletRequestListener {

  @Override
  public void requestDestroyed(ServletRequestEvent sre) {
    ServletRequestListener.super.requestDestroyed(sre);
  }

  @Override
  public void requestInitialized(ServletRequestEvent sre) {
    ServletRequestListener.super.requestInitialized(sre);
  }}

2.2带Attribute监听事件的增、删、改

@WebListener
public class MyServletListener implements ServletContextAttributeListener {

  @Override
  public void attributeAdded(ServletContextAttributeEvent scae) {
    ServletContextAttributeListener.super.attributeAdded(scae);
  }

  @Override
  public void attributeRemoved(ServletContextAttributeEvent scae) {
    ServletContextAttributeListener.super.attributeRemoved(scae);
  }

  @Override
  public void attributeReplaced(ServletContextAttributeEvent scae) {
    ServletContextAttributeListener.super.attributeReplaced(scae);
  }}

其他两个关于Attribute监听的都是有三个抽象方法:added removed repalced

3.为什么大量出现ed过去式?

众所周知英语中ed结尾的动词都是过去完成式
我们发现 sessionCreated contextDestroyed Replaced等等这些抽象方法都是ed结尾的过去式,那么可以 从中推断出:监听中的方法是在事件发生之后进行的。
因此我们可以解决一个疑惑:“我想在监听器方法中使用容器中的其他对象方法,会不会报错?”
答案是不会:“因为对context监听的ed方法一定触发在整个容器加载结束之后”

4. Spring监听器

两种方式:参考资料

4.1ApplicationListener接口实现监听

一般来说,在Spring项目中监听器只选择ApplicationListener,而对于监听事件spring提供了多种选择

ApplicationStartedEvent:spring boot启动监听类
ApplicationEnvironmentPreparedEvent:环境事先准备
ApplicationPreparedEvent:上下文context准备时触发
ApplicationReadyEvent:上下文已经准备完毕的时候触发
ApplicationFailedEvent:该事件为spring boot启动失败时的操作
  • 他们都是ApplicationEvent的抽象子类,触发的时机不同
  • 例如用ApplicationListener监听,那么以上5种子类事件都可以被监听到
  • 你可以自定义一个事件,继承于上面的子事件,来实现一些数据的绑定,不过一般不会用到

4.2@EventListener注解实现监听

@Component//需要注入IOC,无需实现任何接口
public class MySprigListenerAnno {
@EventListener//配置一个监听器,省去了写ApplicationListener,但必须明确指出形参中的监听事件
  public void myEvent(ApplicationEvent event){
  System.out.println("用注解的方式实现Spring监听器,当前:"+event.getClass().getName());
}

}

4.3重要的泛型

查看源码可知泛型决定了监听事件是谁,如果不指定,那么就是默认监听根父类ApplicationEvent

源码

5.Servlet和Spring的初始化顺序

我们用一个案例直接看

5.1Serlvet:监听context

@WebListener
public class MyServletListener implements ServletContextListener {

  @Override
  public void contextInitialized(ServletContextEvent sce) {
    /**
     * ServletContextEvent的父类EventListener是没有contextInitialized(sce)方法的。
     * 所以对于此处来说,调不调super.contextInitialized都无所谓
     */
    ServletContextListener.super.contextInitialized(sce);
    System.out.println("当前是Servlet初始化");
  }

  @Override
  public void contextDestroyed(ServletContextEvent sce) {
    ServletContextListener.super.contextDestroyed(sce);
    System.out.println("当前是Servlet销毁");
  }}

5.2Spring:监听context

这里监听ApplicationEvent,相当于监听其所有子类事件的,他的子类很多,但是Spring启动的时候只有几个事件会被监听

@Component
@Order(1)
public class MySpringListener implements ApplicationListener<ApplicationEvent> {

  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    System.out.println("当前是Spring的:"+event.getClass().getName()+"监听事件");
  }}

5.3启动Spring

执行顺序大概是: tomcat初始化——Servlet启动——Servlet引擎——Servlet初始化完成——tomcat启动完成——Spring初始化

6.监听器的应用:Spring启动时就调用业务方法

  • 通过监听ApplicationReadyEventApplicationStartedEvent事件,就可以调用;

  • 注意started是过去式,即此时Spring的IOC容器已经启动完成,可以在此时调用IOC容器中的bean

  • 而Ready更是在Started之后,更可以直接@Autowired使用IOC容器中的bean

      @Service
      public class TheFuckList {
      
        @Autowired
        RedisTemplate redisTemplate;
      
        public List<String> getFuckList(){
      //    List fuckList = redisTemplate.boundListOps("list1").range(0, 10);
          List fuckList = redisTemplate.opsForList().range("list1",0,10);
          Iterator iterator = fuckList.iterator();
          while(iterator.hasNext()){
            System.out.println(iterator.next());
          }
          return fuckList;
        }
      }
    

@Component
@Order(0)
public class MySpringListener2 implements ApplicationListener<ApplicationStartedEvent> {

  @Autowired
  TheFuckList theFuckList;

  @Autowired
  FuckListBean fuckListBean;

  @Override
  public void onApplicationEvent(ApplicationStartedEvent event) {
    fuckListBean.fuckList = theFuckList.getFuckList();
    System.out.println(fuckListBean.fuckList.size());
  }
}

7.多个Spring监听器执行顺序

监听事件为基准,监听器之间的顺序以@Order决定

@Component
@Order(-1)
public class MySpringListener implements ApplicationListener<ApplicationEvent> {//ApplicationStartedEvent
  
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    System.out.println("当前是Srp:"+event.getClass().getName()+"监听事件");
  }


@Component
@Order(0)
public class MySpringListener2 implements ApplicationListener<ApplicationEvent> {
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    System.out.println("第二个监听器");
  }
}

8.过滤器、拦截器、监听器执行顺序

  • 注意,过滤器、拦截器是针对某个包下的controller接口被调用时而进行处理的
  • 监听器则可以深入到Servlet、Spring容器的创建之时执行。
  • Filter过滤器的本质是:回调
  • Interceptor拦截器的本质是:反射

参考资料

那么如果是过滤器,拦截器,监听器同时生效呢?


8.1Filter的过滤流程

  • 在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。
  • 回调的顺序由@Order来进行配置
  • 过滤器Filter是在请求进入web容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

8.2Interceptor拦截器流程

  • 对于Spring拦截器,一般直接实现HandlerInterceptor接口,并重写三个抽象方法。
  • 拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

参考资料1

8.3Controller请求调用顺序

controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()、preHandle()方法便是在其中调用的。

9.DispatcherServlet类:路由,中央调度器

源码

  • HTTP请求处理程序/控制器的中央调度器

    源码

9.1doDispatch中央调度方法

	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);
        }
    }
    ...........
}

9.2为何拦截器的pre和post执行顺序是栈顺序

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

正是因为DispathcherServlet类中的doDispach()方法进行调度的时候,实际上是调用了applyPreHandler()和applyPostHandler(),而这两个方法中的for循环遍历interceptorList数组时一个是i++一个是i--,所以表面上就成了入栈出栈的执行顺序

10.过滤器监听器几个注解及配置

@WebFilter

标注在 xxxxximplements Filter的类上,即把Servlet过滤器注入Servlet容器中,这是Servlet3.0的注解规范,被Spring5.0引入

@WebListener

标注在 xxxxx implements ServletContextListener的类上,即把Servlet监听器注入Servlet容器中,这是Servlet3.0的注解规范,被Spring5.0引入

@EventListener

@Component//需要注入IOC,无需实现任何接口
public class MySprigListenerAnno {
@EventListener//配置一个监听器,省去了写ApplicationListener,但必须明确指出形参中的监听事件
  public void myEvent(ApplicationEvent event){
  System.out.println("用注解的方式实现Spring监听器,当前:"+event.getClass().getName());
}

————————————如果不用注解就需要实现接口

@Component
@Order(0)
public class MySpringListener2 implements ApplicationListener<ApplicationEvent> {
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    System.out.println("第二个监听器");
  }
}

WebMvcConfigurer配置

为Spring容器注册HandlerInterceptor的实现类

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/login/web")
        .excludePathPatterns("/login/no");
  }
}

————————拦截器本身内容跟注册配置缺一不可

public class MyHandlerInterceptor  implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    long start = System.currentTimeMillis();
    request.setAttribute("startTime", start);
    return true;//直接放行
//    return HandlerInterceptor.super.preHandle(request, response, handler);
  }
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    long start = (long) request.getAttribute("startTime");
    long end     = System.currentTimeMillis();
    request.setAttribute("handleTime",end-start);
//    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  log.info(
      String.format("本次请求%s的%s接口耗时%s毫秒",
          request.getRequestURL(),
          request.getMethod(),
          (long)request.getAttribute("handleTime")));
//    HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
  }
}

小结:

除了@EventListener可以不写实现类,其他都必须写

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值