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 }