最近看起spring源碼,突然想知道沒有web.xml的配置,spring是怎麼通過一個繼承於AbstractAnnotationConfigDispatcherServletInitializer的類來啟動自己的。鑒於能力有限以及第一次看源碼和發博客,不到之處請望諒~
我用的IDE是IntelliJ IDEA,這個比myEclipse看源碼方便一點,而且黑色背景挺喜歡。然後項目是在maven下的tomcat7插件運行。spring版本是4.3.2.RELEASE。
如果寫過純注解配置的spring web,應該知道需要繼承一個初始化類來裝載bean,然後從這個類開始就會加載我們自定義的功能和bean了,下面是我的一個WebInitializer
1 @Order(1) 2 public class WebMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer { 3 protected Class<?>[] getRootConfigClasses() { 4 return new Class[]{RootConfig.class,WebSecurityConfig.class}; 5 } 6 7 protected Class<?>[] getServletConfigClasses() { 8 return new Class[]{WebConfig.class}; 9 } 10 11 protected String[] getServletMappings() { 12 return new String[]{"/"}; 13 } 14 15 @Override 16 protected Filter[] getServletFilters() { 17 return new Filter[]{new HiddenHttpMethodFilter()}; 18 } 19 20 }
首先看下AbstractAnnotationConfigDispatcherServletInitializer類的結構,這個也是IDEA的一個uml功能,在類那裡右鍵Diagrams->show Diagrams就有啦
然後我們直接點進AbstractAnnotationConfigDispatcherServletInitializer,可以看到這個類很簡單,只有四個方法,然後我們關注下createRootApplicationContext()
1 @Override 2 protected WebApplicationContext createRootApplicationContext() { 3 Class<?>[] configClasses = getRootConfigClasses(); 4 if (!ObjectUtils.isEmpty(configClasses)) { 5 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); 6 rootAppContext.register(configClasses); 7 return rootAppContext; 8 } 9 else { 10 return null; 11 } 12 }
這個方法大概意思是獲取用戶(程序員)傳過來的RootClasses,然後注冊裡面的bean,這些都不是我們關注的,不過這個方法應該是要在啟動後執行的,所以我們可以從這個方法往上找
IDEA下Ctrl+G可以找調用某個方法或類,然後設置尋找范圍為project and library
我們找到,AbstractContextLoaderInitializer下registerContextLoaderListener(ServletContext servletContext)方法調用子類的createRootApplicationContext()獲取WebApplicationContext,繼續找registerContextLoaderListener(ServletContext servletContext)方法的調用者,結果發現就是該類下的onStartup(ServletContext servletContext),下面貼下AbstractContextLoaderInitializer類
1 public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { 2 3 /** Logger available to subclasses */ 4 protected final Log logger = LogFactory.getLog(getClass()); 5 6 7 @Override 8 public void onStartup(ServletContext servletContext) throws ServletException { 9 registerContextLoaderListener(servletContext); 10 } 11 12 /** 13 * Register a {@link ContextLoaderListener} against the given servlet context. The 14 * {@code ContextLoaderListener} is initialized with the application context returned 15 * from the {@link #createRootApplicationContext()} template method. 16 * @param servletContext the servlet context to register the listener against 17 */ 18 protected void registerContextLoaderListener(ServletContext servletContext) { 19 WebApplicationContext rootAppContext = createRootApplicationContext(); 20 if (rootAppContext != null) { 21 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); 22 listener.setContextInitializers(getRootApplicationContextInitializers()); 23 servletContext.addListener(listener); 24 } 25 else { 26 logger.debug("No ContextLoaderListener registered, as " + 27 "createRootApplicationContext() did not return an application context"); 28 } 29 } 30 31 /** 32 * Create the "<strong>root</strong>" application context to be provided to the 33 * {@code ContextLoaderListener}. 34 * <p>The returned context is delegated to 35 * {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will 36 * be established as the parent context for any {@code DispatcherServlet} application 37 * contexts. As such, it typically contains middle-tier services, data sources, etc. 38 * @return the root application context, or {@code null} if a root context is not 39 * desired 40 * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer 41 */ 42 protected abstract WebApplicationContext createRootApplicationContext(); 43 44 /** 45 * Specify application context initializers to be applied to the root application 46 * context that the {@code ContextLoaderListener} is being created with. 47 * @since 4.2 48 * @see #createRootApplicationContext() 49 * @see ContextLoaderListener#setContextInitializers 50 */ 51 protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() { 52 return null; 53 } 54 55 }
注意的是這裡我們跳過了AbstractDispatcherServletInitializer抽象類(看uml圖),這個類主要配置DispatcherServlet,這裡就是spring mvc等功能的實現了。
那誰來加載AbstractContextLoaderInitializer?WebApplicationInitializer已經是接口,不會再有一個抽象類來調用了,於是我嘗試性地搜WebApplicationInitializer接口,因為spring這種大項目肯定是面向接口的,所以調用的地方一般是寫接口,然後我們找到了SpringServletContainerInitializer類,它實現了ServletContainerInitializer接口,這個類大概是說把所有WebApplicationInitializer都startUp一遍,可以說這個類很接近我們的目標了。下面貼下SpringServletContainerInitializer
1 @HandlesTypes(WebApplicationInitializer.class) 2 public class SpringServletContainerInitializer implements ServletContainerInitializer { 3 @Override 4 public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) 5 throws ServletException { 6 7 List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); 8 9 if (webAppInitializerClasses != null) { 10 for (Class<?> waiClass : webAppInitializerClasses) { 11 // Be defensive: Some servlet containers provide us with invalid classes, 12 // no matter what @HandlesTypes says... 13 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && 14 WebApplicationInitializer.class.isAssignableFrom(waiClass)) { 15 try { 16 initializers.add((WebApplicationInitializer) waiClass.newInstance()); 17 } 18 catch (Throwable ex) { 19 throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); 20 } 21 } 22 } 23 } 24 25 if (initializers.isEmpty()) { 26 servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); 27 return; 28 } 29 30 servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); 31 AnnotationAwareOrderComparator.sort(initializers); 32 for (WebApplicationInitializer initializer : initializers) { 33 initializer.onStartup(servletContext); 34 } 35 } 36 37 }
在最後的foreach把所有的WebApplicationInitializer都啟動一遍。那麼問題來了,誰來啟動SpringServletContainerInitializer,spring肯定不能自己就能啟動的,在
web環境下,就只有web容器了。我們可以在上面某一個地方打個斷點,然後Debug一下(事實上,完全可以全程Debug = =,這樣准確又快捷,不過這樣少了點尋找的意味,沿路風景還是挺不錯的)
可以看到包org.apache.catalina.core下的StandardContext類的startInternal方法,這個已經是tomcat的范圍了,所以我們的目標算是達到了。注意的是ServletContainerInitializer接口並不是spring包下的,而是javax.servlet
我猜測,tomcat通過javax.servlet的ServletContainerInitializer接口來找容器下實現這個接口的類,然後調用它們的OnStartUp,然後spring的SpringServletContainerInitializer就可以把所有WebApplicationInitializer都啟動一遍,其中就有我們自己寫的WebInitializer,另外spring security用注解配置也是實現WebApplicationInitializer啟動的,所以這樣spring的擴展性很強。這幾天再看下tomcat源碼,了解下tomcat的機制。