tomcat实现ServletContext的addListener方法的源码解说(原创)

tomcat 8.0.36

知识点:

  • 动态监听器有七类: 
  1. ServletContextAttributeListener
  2. ServletRequestListener
  3. ServletRequestAttributeListener
  4. HttpSessionIdListener
  5. HttpSessionAttributeListener
  6. HttpSessionListener
  7. ServletContextListener
  • 动态监听器必须在启动前完成,即使用ServletContainerInitializer或ServletContextListener进行动态添加。
  • 如果要动态添加ServletContextListener,只能使用ServletContainerInitializer进行。

 

ServletContext的addListener方法,是一个通过动态添加监听器的方法。

传入的参数必须是EventListener类型。

public <T extends EventListener> void addListener(T t)

添加的时机受到限制,必须在指定时机STARTING_PREP下才能添加。

if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
    throw new IllegalStateException(
    sm.getString("applicationContext.addListener.ise",
    getContextPath())); }

STARTING_PREP的意思是启动前,tomcat在启动的时候有两个状态,一个是启动前,一个是正式启动,也就说在状态变为正式启动的时候,要把该添加的添加,不然就没机会了。

tomcat在维持启动前这个状态下,调用servlet api的方法主要有两个:

  1. ServletContainerInitializer#onStartup(Set<Class<?>> c, ServletContext ctx)
  2. ServletContextListener#contextInitialized(ServletContextEvent sce)

其实这两个方法的作用都一样,都能够监听ServletContext初始化事件,但用法上面有些不一样,例如前者要比后者早触发,前者的实现类需要放在一个模块中,关于模块和其它一些区别的地方不再叙述。

tomcat使用addApplicationEventListener和addApplicationLifecycleListener这两个方法,区分存储在两个列表里。

if (t instanceof ServletContextAttributeListener
        || t instanceof ServletRequestListener
        || t instanceof ServletRequestAttributeListener
        || t instanceof HttpSessionIdListener
        || t instanceof HttpSessionAttributeListener) {
    context.addApplicationEventListener(t);
    match = true;
}

if (t instanceof HttpSessionListener || 
    (t instanceof ServletContextListener
      && newServletContextListenerAllowed)) { context.addApplicationLifecycleListener(t); match = true; }

 虽然传入的参数类型限制在EventListener类型,但实际上动态添加的类型只有这七类:

  1. ServletContextAttributeListener
  2. ServletRequestListener
  3. ServletRequestAttributeListener
  4. HttpSessionIdListener
  5. HttpSessionAttributeListener
  6. HttpSessionListener
  7. ServletContextListener

其中ServletContextListener,是受到newServletContextListenerAllowed变量的限制。 

这个变量默认为true,在调用ServletContainerInitializer#onStartup方法时,仍然是true。

但在调用ServletContextListener#contextInitialized方法之前,这变量就会变成false,调用完后也一直保持着false。

也就是说,如果想通过动态添加监听器ServletContextListener的方式,只能在ServletContainerInitializer#onStartup的方法中添加。

其实简单的总结就是,ServletContextListener作为一个监听ServletContext的初始化,不能在这时候再添加这样的监听器,有什么事没做完的可以现在完成,非要搞什么推迟一下完成,是不是有点傻。

看到了么,最后那些未添加的监听器,都会接受末日审判。 

if (match)
    return;

if (t instanceof ServletContextListener) {
    throw new IllegalArgumentException(
    sm.getString("applicationContext.addListener.iae.sclNotAllowed",
    t.getClass().getName()));
} else {
    throw new IllegalArgumentException(
    sm.getString("applicationContext.addListener.iae.wrongType",
    t.getClass().getName()));
}

完整源码:

 1 public <T extends EventListener> void addListener(T t) {
 2     if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
 3         throw new IllegalStateException(sm.getString("applicationContext.addListener.ise", getContextPath()));
 4     }
 5 
 6     boolean match = false;
 7     
 8     if (t instanceof ServletContextAttributeListener
 9             || t instanceof ServletRequestListener
10             || t instanceof ServletRequestAttributeListener
11             || t instanceof HttpSessionIdListener
12             || t instanceof HttpSessionAttributeListener) {
13         context.addApplicationEventListener(t);
14         match = true;
15     }
16 
17     if (t instanceof HttpSessionListener || (t instanceof ServletContextListener && newServletContextListenerAllowed)) {
18         context.addApplicationLifecycleListener(t);
19         match = true;
20     }
21 
22     if (match)
23         return;
24 
25     if (t instanceof ServletContextListener) {
26         throw new IllegalArgumentException(sm.getString("applicationContext.addListener.iae.sclNotAllowed", t.getClass().getName()));
27     } else {
28         throw new IllegalArgumentException(sm.getString("applicationContext.addListener.iae.wrongType", t.getClass().getName()));
29     }
30 }
原文地址:https://www.cnblogs.com/hvicen/p/6004187.html