Spring MVC源碼——Root WebApplicationContext

Spring MVC源碼——Root WebApplicationContext

  打算開始讀一些框架的源碼,先拿 Spring MVC 練練手,歡迎點擊這里訪問我的源碼注釋, SpringMVC官方文檔一開始就給出了這樣的兩段示例:

WebApplicationInitializer示例:

public class MyWebApplicationInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletCxt) {// Load Spring web application configurationAnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();ac.register(AppConfig.class);ac.refresh();// Create and register the DispatcherServletDispatcherServlet servlet = new DispatcherServlet(ac);ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);registration.setLoadOnStartup(1);registration.addMapping("/app/*");}
}

web.xml 示例:

<web-app><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/app-context.xml</param-value></context-param><servlet><servlet-name>app</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value></param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>app</servlet-name><url-pattern>/app/*</url-pattern></servlet-mapping></web-app>

  我們按照 web.xml 中的實例來看一下 Spring MVC 初始化過程.

上下文層次結構

  Spring MVC 的上下文有如下這樣的層級:

  

  圖中的?Servlet WebApplicationContext?是與?DispatcherServlet?綁定的上下文, 其中還有 controllers、ViewResolver、HandlerMapping 等組件.

Root WebApplicationContext?不是必須的上下文, 在需要時,可以用來在多個?DispatcherServlet?間共享一些 bean.

Root WebApplicationContext 初始化和銷毀

  ContextLoaderListener

  web.xml 中配置的?ContextLoaderListener?用于啟動和終止 Spring 的 root?WebApplicationContext.

  

   ContextLoaderListener

?

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener() {}public ContextLoaderListener(WebApplicationContext context) {super(context);}/*** Initialize the root web application context.* Servlet 上下文初始化,調用父類的方法初始化 WebApplicationContext*/@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}/*** Close the root web application context.* Servlet 上下文被銷毀,調用父類的方法銷毀 WebApplicationContext*/@Overridepublic void contextDestroyed(ServletContextEvent event) {closeWebApplicationContext(event.getServletContext());// 銷毀 ServletContext 上實現了 DisposableBean 的屬性并移除他們
        ContextCleanupListener.cleanupAttributes(event.getServletContext());}
}

ContextLoaderListener?直接調用了父類?ContextLoader?的方法來初始化和銷毀上下文.

ContextLoaderListener?在創建上下文時,會嘗試讀取?contextClass?context-param 來指定上下文的類型,被指定的類需要實現?ConfigurableWebApplicationContext?接口. 如果沒有獲取到,默認會使用?WebApplicationContext.

初始化上下文時,會嘗試讀取?contextConfigLocation?context-param, 作為 xml 文件的路徑.

  初始化上下文

initWebApplicationContext()?方法如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {// 檢查有沒有綁定上下文if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");}servletContext.log("Initializing Spring root WebApplicationContext");Log logger = LogFactory.getLog(ContextLoader.class);if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}// 初始化開始時間long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.// 保存上下文到本地實例變量中,保證上細紋能在 ServletContext 關閉時訪問到if (this.context == null) {// 創建上下文this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {// 如果上下文實現了 ConfigurableWebApplicationContextConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// 上下文還沒有刷新,設置 父上下文(如果能找到),并且刷新// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}// 設置和刷新上下文
                configureAndRefreshWebApplicationContext(cwac, servletContext);}}// 將上下文綁定到 servletContext 的屬性上servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);// 獲取當前線程的上下文類加載器ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {// 如果有線程上下文類加載器,而且不是 ContextLoader 本身的類加載器,放入到 currentContextPerThread 中。這是一個 static 的域currentContextPerThread.put(ccl, this.context);}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException | Error ex) {// 發生異常, 把異常綁定到上下文對應的屬性上,之后不會再進行初始化logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}
}

initWebApplicationContext()?方法調用了?createWebApplicationContext()?方法來創建上下文;調用了?configureAndRefreshWebApplicationContext()?來對實現了?ConfigurableWebApplicationContext?接口的上下文做初始化.

createWebApplicationContext()?會調用?determineContextClass()?來獲取上下文類型的 Class 對象.

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {// 獲取上下文類型Class<?> contextClass = determineContextClass(sc);// 檢查是否實現了 ConfigurableWebApplicationContextif (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");}// 實例化return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}protected Class<?> determineContextClass(ServletContext servletContext) {// 獲取 serveltContext 的 'contextClass' 初始化參數。String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);if (contextClassName != null) {// 指定過上下文類型,加載類try {return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);}}else {// 去默認策略里獲取默認的上下文類型名稱contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());try {// 加載類return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);}}
}

configureAndRefreshWebApplicationContext()?方法.

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {// 用可以獲取到的信息,獲取一個更有意義的上下文if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available information// 獲取 ServletContext 的 'contextId' 初始化參數。String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam != null) {wac.setId(idParam);}else {// Generate default id...// 生成默認 idwac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));}}// 設置 servletContext 屬性
    wac.setServletContext(sc);// 設置配置文件路徑String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refresh// 初始化屬性源, 確保 servlet 屬性源到位并能夠在任何 refresh 之前的后期處理和初始化中使用ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}// 在設置了配置文件之后上下文刷新之前,自定義上下文
    customizeContext(sc, wac);wac.refresh();
}protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {// 根據 ServletContext 的 'contextInitializerClasses' 和 'globalInitializerClasses' 初始化參數 加載 ApplicationContextInitializer 的 classList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =determineContextInitializerClasses(sc);for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {// 獲取范型參數類型Class<?> initializerContextClass =GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);// 檢查 Initializer 是否適用于當前上下文對象if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {throw new ApplicationContextException(String.format("Could not apply context initializer [%s] since its generic parameter [%s] " +"is not assignable from the type of application context used by this " +"context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),wac.getClass().getName()));}// 創建 Initializer 實例,并添加到 contextInitializersthis.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));}// 根據 org.springframework.core.Ordered 和 org.springframework.core.annotation.Order 排序,如果沒有實現或注解,會被排到最后AnnotationAwareOrderComparator.sort(this.contextInitializers);// 執行每個 initializer 的 initialize() 方法for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {initializer.initialize(wac);}
}

  銷毀上下文

closeWebApplicationContext()?方法如下:

public void closeWebApplicationContext(ServletContext servletContext) {servletContext.log("Closing Spring root WebApplicationContext");try {// 如果 context 是 ConfigurableWebApplicationContext 調用 close() 方法if (this.context instanceof ConfigurableWebApplicationContext) {((ConfigurableWebApplicationContext) this.context).close();}}finally {ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = null;}else if (ccl != null) {currentContextPerThread.remove(ccl);}// 移除 servletContext 中的 context 屬性
        servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);}
}

Servlet 3.0+ 中初始化

Servlet 3.0+ 中可以通過?ServletContext?的?addlistener()?方法來添加監聽器.因此可以先把 Spring 容器先創建好,再傳給?ContextLoaderListener?的構造器.這里就不自己寫例子了,選了單元測試中的?ContextLoaderTests.testContextLoaderListenerWithDefaultContext()?方法:

public void testContextLoaderListenerWithDefaultContext() {MockServletContext sc = new MockServletContext("");sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,"/org/springframework/web/context/WEB-INF/applicationContext.xml " +"/org/springframework/web/context/WEB-INF/context-addition.xml");ServletContextListener listener = new ContextLoaderListener();ServletContextEvent event = new ServletContextEvent(sc);listener.contextInitialized(event);String contextAttr = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;WebApplicationContext context = (WebApplicationContext) sc.getAttribute(contextAttr);assertTrue("Correct WebApplicationContext exposed in ServletContext", context instanceof XmlWebApplicationContext);assertTrue(WebApplicationContextUtils.getRequiredWebApplicationContext(sc) instanceof XmlWebApplicationContext);LifecycleBean lb = (LifecycleBean) context.getBean("lifecycle");assertTrue("Has father", context.containsBean("father"));assertTrue("Has rod", context.containsBean("rod"));assertTrue("Has kerry", context.containsBean("kerry"));assertTrue("Not destroyed", !lb.isDestroyed());assertFalse(context.containsBean("beans1.bean1"));assertFalse(context.containsBean("beans1.bean2"));listener.contextDestroyed(event);assertTrue("Destroyed", lb.isDestroyed());assertNull(sc.getAttribute(contextAttr));assertNull(WebApplicationContextUtils.getWebApplicationContext(sc));
}

?

?轉自:https://www.cnblogs.com/FJH1994/p/10798028.html

轉載于:https://www.cnblogs.com/hujunzheng/p/10819473.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/531196.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/531196.shtml
英文地址,請注明出處:http://en.pswp.cn/news/531196.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring MVC源碼——Servlet WebApplicationContext

上一篇筆記(Spring MVC源碼——Root WebApplicationContext)中記錄了下 Root WebApplicationContext 的初始化代碼.這一篇來看 Servlet WebApplicationContext 的初始化代碼 DispatcherServlet 是另一個需要在 web.xml 中配置的類, Servlet WebApplicationContext 就由它來創建…

Springboot源碼——應用程序上下文分析

前兩篇(Spring MVC源碼——Root WebApplicationContext 和 Spring MVC源碼——Servlet WebApplicationContext)講述了springmvc項目創建上下文的過程&#xff0c;這一篇帶大家了解一下springboot項目創建上下文的過程。 SpringApplication引導類 SpringApplication類用于啟動或…

基于zookeeper實現分布式配置中心(一)

最近在學習zookeeper&#xff0c;發現zk真的是一個優秀的中間件。在分布式環境下&#xff0c;可以高效解決數據管理問題。在學習的過程中&#xff0c;要深入zk的工作原理&#xff0c;并根據其特性做一些簡單的分布式環境下數據管理工具。本文首先對zk的工作原理和相關概念做一下…

基于zookeeper實現分布式配置中心(二)

上一篇&#xff08;基于zookeeper實現分布式配置中心&#xff08;一&#xff09;&#xff09;講述了zookeeper相關概念和工作原理。接下來根據zookeeper的特性&#xff0c;簡單實現一個分布式配置中心。 配置中心的優勢 1、各環境配置集中管理。 2、配置更改&#xff0c;實時推…

Redis分布式鎖實戰

背景 目前開發過程中&#xff0c;按照公司規范&#xff0c;需要依賴框架中的緩存組件。不得不說&#xff0c;做組件的大牛對CRUD操作的封裝&#xff0c;連接池、緩存路由、緩存安全性的管控都處理的無可挑剔。但是有一個小問題&#xff0c;該組件沒有對分布式鎖做實現&#xff…

基于RobotFramework實現自動化測試

Java robotframework seleniumlibrary 使用Robot Framework Maven Plugin&#xff08;http://robotframework.org/MavenPlugin/&#xff09;執行自動化測試chromedriver下載&#xff1a; http://chromedriver.storage.googleapis.com/index.htmlchromedriver和chrome版本對應…

Springboot國際化信息(i18n)解析

國際化信息理解 國際化信息也稱為本地化信息 。 Java 通過 java.util.Locale 類來表示本地化對象&#xff0c;它通過 “語言類型” 和 “國家/地區” 來創建一個確定的本地化對象 。舉個例子吧&#xff0c;比如在發送一個具體的請求的時候&#xff0c;在header中設置一個鍵值對…

看了就知道為什么別人C語言學習效率那么高了

談及C語言&#xff0c;我想C語言功能強大都應該知道、應用廣泛&#xff0c;一旦掌握了后&#xff0c;你就可以理直氣壯地對他人說“我是電腦高手&#xff01;”&#xff0c;而且以后若是再自學其他語言就顯得輕而易舉了。憂慮的是&#xff0c;C語言般博大精深&#xff0c;太難學…

C語言一看就能上手的干貨!你確定你不來看嗎?

本地環境設置 如果您想要設置 C 語言環境&#xff0c;您需要確保電腦上有以下兩款可用的軟件&#xff0c;文本編輯器和 C 編譯器。 文本編輯器 這將用于輸入您的程序。文本編輯器包括 Windows Notepad、OS Edit command、Brief、Epsilon、EMACS 和 vim/vi。文本編輯器的名稱…

C語言爆炸干貨,小白你還不來看看嘛!

①&#xff1a;數據類型 int(整型)&#xff0c;short int(短整型)&#xff0c;long int(長整型)&#xff0c; char(字符型)&#xff0c;float&#xff08;單精度浮點型&#xff09; double&#xff08;雙精度浮點型&#xff09; C語言編程交流群815393895 ②&#xff1a;邏…

10萬碼農五年的C語言筆記!你現在知道別人為什么這么優秀了嗎?

c語言對許多同學來說確實是一門比較難學的課程&#xff0c;不僅抽象&#xff0c;而且繁瑣&#xff0c;但這又是一門不得不學的課程。前兩節可能還有興致聽一聽&#xff0c;然而&#xff0c;再過幾節課就是一臉蒙比。憑空要想出一道題的算法和程序&#xff0c;根本無從下手。 所…

C語言從來都沒有過時,你大爺終究是你大爺

直到今天&#xff0c;有人在喊C語言過時的語言&#xff0c;還有什么值得學習的&#xff0c;現在看Python&#xff0c;PHP等語言現在都很容易用&#xff0c;誰還在學習老C語言&#xff0c;其實這是真的嗎&#xff1f;作者下載了兩種語言的源代碼作為下載器。由于空間的限制&…

C語言超級瑪麗菜單模塊源碼

C語言是面向過程的&#xff0c;而C&#xff0b;&#xff0b;是面向對象的 C和C的區別&#xff1a; C是一個結構化語言&#xff0c;它的重點在于算法和…

C語言使用函數必須知道的3點注意事項!

C語言是面向過程的&#xff0c;而C&#xff0b;&#xff0b;是面向對象的 C和C的區別&#xff1a; C是一個結構化語言&#xff0c;它的重點在于算法和數據結構。C程序的設計首要考慮的是如何通過一個過程&#xff0c;對輸入&#xff08;或環境條件&#xff09;進行運算處理得…

C語言/C++編程學習:C語言環境設置!

C語言是面向過程的&#xff0c;而C&#xff0b;&#xff0b;是面向對象的 C和C的區別&#xff1a; C是一個結構化語言&#xff0c;它的重點在于算法和數據結構。C程序的設計首要考慮的是如何通過一個過程&#xff0c;對輸入&#xff08;或環境條件&#xff09;進行運算處理得…

C語言指針原來也可以這么的通俗易懂!

C語言是面向過程的&#xff0c;而C&#xff0b;&#xff0b;是面向對象的 C和C的區別&#xff1a; C是一個結構化語言&#xff0c;它的重點在于算法和數據結構。C程序的設計首要考慮的是如何通過一個過程&#xff0c;對輸入&#xff08;或環境條件&#xff09;進行運算處理得…

C語言過時了?你在做夢?

為什么要使用C語言&#xff1f; 在過去的四十年里&#xff0c;C語言已經成為世界上最流行、最重要的一種編程語言。 C是一種融合了控制特性的現代語言&#xff0c;而我們已發現在計算機科學的理論和實踐中&#xff0c;控制特性是很重要的。其設計使得用戶可以自然地采用自頂向…

C/C++的轉義字符詳解

所有的ASCII碼都可以用“\”加數字&#xff08;一般是8進制數字&#xff09;來表示。而C中定義了一些字母前加"\"來表示常見的那些不能顯示的ASCII字符&#xff0c;如\0,\t,\n等&#xff0c;就稱為轉義字符&#xff0c;因為后面的字符&#xff0c;都不是它本來的ASCI…

C語言深入理解!助你向大佬邁進!

Dennis Ritchie 過世了&#xff0c;他發明了C語言&#xff0c;一個影響深遠并徹底改變世界的計算機語言。一門經歷40多年的到今天還長盛不衰的語言&#xff0c;今天很多語言都受到C的影響&#xff0c;C&#xff0c;Java&#xff0c;C#&#xff0c;Perl&#xff0c; PHP&#xf…

【初涉C語言】程序員歡迎來到C語言的世界!

計算機發展史 機器語言所有的代碼里面只有0和1優點&#xff1a;直接對硬件產生作用&#xff0c;程序的執行效率非常高缺點&#xff1a;指令又多又難記、可讀性差、無可移植性匯編語言符號化的機器語言&#xff0c;用一個符號&#xff08;英文單詞、數字&#xff09;來代表一條…