關於Java SPI與servlet3.0的應用,這里說的很精煉,鏈接地址如下。
https://blog.csdn.net/pingnanlee/article/details/80940993
以Tomcat8.5.31對Servlet的實現為例,簡單提一點,Tomcat獲取ServletContainerInitializer的實現類是在org.apache.catalina.startup.ContextConfig.webConfig() 中,Step 3調用processServletContainerInitializers(),
使用了自己的WebappServiceLoader,解釋為A variation of Java's JAR ServiceLoader。
順帶一提tomcat啟動時webConfig() 的調用鏈:
Tomcat.start()->各種代理的start()->org.apache.catalina.core.StandardContext.startInternal->LifecycleBase.fireLifecycleEvent->org.apache.catalina.startup.ContextConfig.lifecycleEvent->configureStart->webConfig
另:感嘆一下tomcat的源碼中,每一個方法都好長,和spring源碼的深層次相比簡直各有千秋,以及那個叫做ok的boolean 變量,可能相比用異常來表示更為直觀吧。
@HandlesTypes的實現原理:
首先這個注解最開始令我非常困惑,他的作用是將注解指定的Class對象作為參數傳遞到onStartup(ServletContainerInitializer)方法中。
然而這個注解是要留給用戶擴展的,他指定的Class對象並沒有要繼承ServletContainerInitializer,更沒有寫入META-INF/services/的文件(也不可能寫入)中,那么Tomcat是怎么掃描到指定的類的呢。
答案是Byte Code Engineering Library (BCEL),這是Apache Software Foundation 的Jakarta 項目的一部分,作用同ASM類似,是字節碼操縱框架。
webConfig() 在調用processServletContainerInitializers()時記錄下注解的類名,然后在Step 4和Step 5中都來到processAnnotationsStream這個方法,使用BCEL的ClassParser在字節碼層面讀取了/WEB-INF/classes和某些jar(應該可以在叫做fragments的概念中指定)中class文件的超類名和實現的接口名,判斷是否與記錄的注解類名相同,若相同再通過org.apache.catalina.util.Introspection類load為Class對象,最后保存起來,於Step 11中交給org.apache.catalina.core.StandardContext,也就是tomcat實際調用
ServletContainerInitializer.onStartup()的地方。
至此,謎團終於解開。
不過還有一個小疑問,StandardContext存放@HandlesTypes的對象叫做Map<ServletContainerInitializer,Set<Class<?>>> initializers,他的addServletContainerInitializer方法除了webConfig()以外,還被TomcatEmbeddedServletContainerFactory.addJasperInitializer和TomcatEmbeddedServletContainerFactory.configureContext調用,不知道運行起來是否有多余的class混入其中。也難怪spring要在SpringServletContainerInitializer.onstart的處理中這樣注釋的原因了吧:D
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
不知道其他Servlet,比如Jetty引擎,是怎么實現@HandlesTypes這個注解的呢。