tomcat 8.0.36
知识点:
- 动态监听器有七类:
- ServletContextAttributeListener
- ServletRequestListener
- ServletRequestAttributeListener
- HttpSessionIdListener
- HttpSessionAttributeListener
- HttpSessionListener
- 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的方法主要有两个:
- ServletContainerInitializer#onStartup(Set<Class<?>> c, ServletContext ctx)
- 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类型,但实际上动态添加的类型只有这七类:
- ServletContextAttributeListener
- ServletRequestListener
- ServletRequestAttributeListener
- HttpSessionIdListener
- HttpSessionAttributeListener
- HttpSessionListener
- 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 }