詳解spring注解配置啟動(dòng)過(guò)程
最近看起spring源碼,突然想知道沒(méi)有web.xml的配置,spring是怎么通過(guò)一個(gè)繼承于AbstractAnnotationConfigDispatcherServletInitializer的類來(lái)啟動(dòng)自己的。鑒于能力有限以及第一次看源碼和發(fā)博客,不到之處請(qǐng)望諒~
我用的IDE是IntelliJ IDEA,這個(gè)比myEclipse看源碼方便一點(diǎn),而且黑色背景挺喜歡。然后項(xiàng)目是在maven下的tomcat7插件運(yùn)行。spring版本是4.3.2.RELEASE。
如果寫過(guò)純注解配置的spring web,應(yīng)該知道需要繼承一個(gè)初始化類來(lái)裝載bean,然后從這個(gè)類開始就會(huì)加載我們自定義的功能和bean了,下面是我的一個(gè)WebInitializer
@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類的結(jié)構(gòu),這個(gè)也是IDEA的一個(gè)uml功能,在類那里右鍵Diagrams->show Diagrams就有啦

然后我們直接點(diǎn)進(jìn)AbstractAnnotationConfigDispatcherServletInitializer,可以看到這個(gè)類很簡(jiǎn)單,只有四個(gè)方法,然后我們關(guān)注下createRootApplicationContext()
@Override
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
這個(gè)方法大概意思是獲取用戶(程序員)傳過(guò)來(lái)的RootClasses,然后注冊(cè)里面的bean,這些都不是我們關(guān)注的,不過(guò)這個(gè)方法應(yīng)該是要在啟動(dòng)后執(zhí)行的,所以我們可以從這個(gè)方法往上找
IDEA下Ctrl+G可以找調(diào)用某個(gè)方法或類,然后設(shè)置尋找范圍為project and library
我們找到,AbstractContextLoaderInitializer下registerContextLoaderListener(ServletContext servletContext)方法調(diào)用子類的createRootApplicationContext()獲取WebApplicationContext,繼續(xù)找registerContextLoaderListener(ServletContext servletContext)方法的調(diào)用者,結(jié)果發(fā)現(xiàn)就是該類下的onStartup(ServletContext servletContext),下面貼下AbstractContextLoaderInitializer類
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;
}
}
注意的是這里我們跳過(guò)了AbstractDispatcherServletInitializer抽象類(看uml圖),這個(gè)類主要配置DispatcherServlet,這里就是spring mvc等功能的實(shí)現(xiàn)了。
那誰(shuí)來(lái)加載AbstractContextLoaderInitializer?WebApplicationInitializer已經(jīng)是接口,不會(huì)再有一個(gè)抽象類來(lái)調(diào)用了,于是我嘗試性地搜WebApplicationInitializer接口,因?yàn)閟pring這種大項(xiàng)目肯定是面向接口的,所以調(diào)用的地方一般是寫接口,然后我們找到了SpringServletContainerInitializer類,它實(shí)現(xiàn)了ServletContainerInitializer接口,這個(gè)類大概是說(shuō)把所有WebApplicationInitializer都startUp一遍,可以說(shuō)這個(gè)類很接近我們的目標(biāo)了。下面貼下SpringServletContainerInitializer
@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都啟動(dòng)一遍。那么問(wèn)題來(lái)了,誰(shuí)來(lái)啟動(dòng)SpringServletContainerInitializer,spring肯定不能自己就能啟動(dòng)的,在
web環(huán)境下,就只有web容器了。我們可以在上面某一個(gè)地方打個(gè)斷點(diǎn),然后Debug一下(事實(shí)上,完全可以全程Debug = =,這樣準(zhǔn)確又快捷,不過(guò)這樣少了點(diǎn)尋找的意味,沿路風(fēng)景還是挺不錯(cuò)的)

可以看到包org.apache.catalina.core下的StandardContext類的startInternal方法,這個(gè)已經(jīng)是tomcat的范圍了,所以我們的目標(biāo)算是達(dá)到了。注意的是ServletContainerInitializer接口并不是spring包下的,而是javax.servlet
我猜測(cè),tomcat通過(guò)javax.servlet的ServletContainerInitializer接口來(lái)找容器下實(shí)現(xiàn)這個(gè)接口的類,然后調(diào)用它們的OnStartUp,然后spring的SpringServletContainerInitializer就可以把所有WebApplicationInitializer都啟動(dòng)一遍,其中就有我們自己寫的WebInitializer,另外spring security用注解配置也是實(shí)現(xiàn)WebApplicationInitializer啟動(dòng)的,所以這樣spring的擴(kuò)展性很強(qiáng)。這幾天再看下tomcat源碼,了解下tomcat的機(jī)制。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis之自查詢使用遞歸實(shí)現(xiàn) N級(jí)聯(lián)動(dòng)效果(兩種實(shí)現(xiàn)方式)
這篇文章主要介紹了MyBatis之自查詢使用遞歸實(shí)現(xiàn) N級(jí)聯(lián)動(dòng)效果,本文給大家分享兩種實(shí)現(xiàn)方式,需要的的朋友參考下吧2017-07-07
Spring如何通過(guò)@Lazy注解解決構(gòu)造方法循環(huán)依賴問(wèn)題
循環(huán)依賴其實(shí)就是循環(huán)引用,也就是兩個(gè)或則兩個(gè)以上的bean互相持有對(duì)方,最終形成閉環(huán),這篇文章主要給大家介紹了關(guān)于Spring如何通過(guò)@Lazy注解解決構(gòu)造方法循環(huán)依賴問(wèn)題的相關(guān)資料,需要的朋友可以參考下2023-03-03
Mybatis中TypeAliasRegistry的作用及使用方法
Mybatis中的TypeAliasRegistry是一個(gè)類型別名注冊(cè)表,它的作用是為Java類型建立別名,使得在Mybatis配置文件中可以使用別名來(lái)代替完整的Java類型名。使用TypeAliasRegistry可以簡(jiǎn)化Mybatis配置文件的編寫,提高配置文件的可讀性和可維護(hù)性2023-05-05

