詳解spring注解配置啟動(dòng)過程
最近看起spring源碼,突然想知道沒有web.xml的配置,spring是怎么通過一個(gè)繼承于AbstractAnnotationConfigDispatcherServletInitializer的類來啟動(dòng)自己的。鑒于能力有限以及第一次看源碼和發(fā)博客,不到之處請望諒~
我用的IDE是IntelliJ IDEA,這個(gè)比myEclipse看源碼方便一點(diǎn),而且黑色背景挺喜歡。然后項(xiàng)目是在maven下的tomcat7插件運(yùn)行。spring版本是4.3.2.RELEASE。
如果寫過純注解配置的spring web,應(yīng)該知道需要繼承一個(gè)初始化類來裝載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è)類很簡單,只有四個(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è)方法大概意思是獲取用戶(程序員)傳過來的RootClasses,然后注冊里面的bean,這些都不是我們關(guān)注的,不過這個(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; } }
注意的是這里我們跳過了AbstractDispatcherServletInitializer抽象類(看uml圖),這個(gè)類主要配置DispatcherServlet,這里就是spring mvc等功能的實(shí)現(xiàn)了。
那誰來加載AbstractContextLoaderInitializer?WebApplicationInitializer已經(jīng)是接口,不會(huì)再有一個(gè)抽象類來調(diào)用了,于是我嘗試性地搜WebApplicationInitializer接口,因?yàn)閟pring這種大項(xiàng)目肯定是面向接口的,所以調(diào)用的地方一般是寫接口,然后我們找到了SpringServletContainerInitializer類,它實(shí)現(xiàn)了ServletContainerInitializer接口,這個(gè)類大概是說把所有WebApplicationInitializer都startUp一遍,可以說這個(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)一遍。那么問題來了,誰來啟動(dòng)SpringServletContainerInitializer,spring肯定不能自己就能啟動(dòng)的,在
web環(huán)境下,就只有web容器了。我們可以在上面某一個(gè)地方打個(gè)斷點(diǎn),然后Debug一下(事實(shí)上,完全可以全程Debug = =,這樣準(zhǔn)確又快捷,不過這樣少了點(diǎn)尋找的意味,沿路風(fēng)景還是挺不錯(cuò)的)
可以看到包org.apache.catalina.core下的StandardContext類的startInternal方法,這個(gè)已經(jīng)是tomcat的范圍了,所以我們的目標(biāo)算是達(dá)到了。注意的是ServletContainerInitializer接口并不是spring包下的,而是javax.servlet
我猜測,tomcat通過javax.servlet的ServletContainerInitializer接口來找容器下實(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ī)制。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot熱部署實(shí)現(xiàn)原理及實(shí)例詳解
這篇文章主要介紹了Springboot熱部署實(shí)現(xiàn)原理及實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05spring aop底層原理及如何實(shí)現(xiàn)
這篇文章主要介紹了spring aop底層原理及如何實(shí)現(xiàn),幫助大家更好的理解和學(xué)習(xí)使用spring aop,感興趣的朋友可以了解下2021-04-04淺談SpringBoot中properties、yml、yaml的優(yōu)先級(jí)
優(yōu)先級(jí)低的配置會(huì)被先加載,所以優(yōu)先級(jí)高的配置會(huì)覆蓋優(yōu)先級(jí)低的配置,本文就來介紹一下SpringBoot中properties、yml、yaml的優(yōu)先級(jí),感興趣的可以了解一下2023-08-08SpringBoot?MongoCustomConversions自定義轉(zhuǎn)換方式
這篇文章主要介紹了SpringBoot?MongoCustomConversions自定義轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08SpringBoot整合Mybatis無法掃描xml文件的解決
這篇文章主要介紹了SpringBoot整合Mybatis無法掃描xml文件的解決操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Java基于Session登錄驗(yàn)證的實(shí)現(xiàn)示例
基于Session的登錄驗(yàn)證方式是最簡單的一種登錄校驗(yàn)方式,本文主要介紹了Java基于Session登錄驗(yàn)證的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02設(shè)計(jì)模式系列之組合模式及其在JDK和MyBatis源碼中的運(yùn)用詳解
這篇文章主要介紹了組合模式及其在JDK和MyBatis源碼中的運(yùn)用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09