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