日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring MVC 原理探秘 - 容器的创建过程

發(fā)布時(shí)間:2025/3/21 javascript 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring MVC 原理探秘 - 容器的创建过程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?1.簡(jiǎn)介

在上一篇文章中,我向大家介紹了 Spring MVC 是如何處理 HTTP 請(qǐng)求的。Spring MVC 可對(duì)外提供服務(wù)時(shí),說明其已經(jīng)處于了就緒狀態(tài)。再次之前,Spring MVC 需要進(jìn)行一系列的初始化操作。正所謂兵馬未動(dòng),糧草先行。這些操作包括創(chuàng)建容器,加載 DispatcherServlet 中用到的各種組件等。本篇文章就來和大家討論一下這些初始化操作中的容器創(chuàng)建操作,容器的創(chuàng)建是其他一些初始化過程的基礎(chǔ)。那其他的就不多說了,我們直入主題吧。

?2.容器的創(chuàng)建過程

一般情況下,我們會(huì)在一個(gè) Web 應(yīng)用中配置兩個(gè)容器。一個(gè)容器用于加載 Web 層的類,比如我們的接口 Controller、HandlerMapping、ViewResolver 等。在本文中,我們把這個(gè)容器叫做 web 容器。另一個(gè)容器用于加載業(yè)務(wù)邏輯相關(guān)的類,比如 service、dao 層的一些類。在本文中,我們把這個(gè)容器叫做業(yè)務(wù)容器。在容器初始化的過程中,業(yè)務(wù)容器會(huì)先于 web 容器進(jìn)行初始化。web 容器初始化時(shí),會(huì)將業(yè)務(wù)容器作為父容器。這樣做的原因是,web 容器中的一些 bean 會(huì)依賴于業(yè)務(wù)容器中的 bean。比如我們的 controller 層接口通常會(huì)依賴 service 層的業(yè)務(wù)邏輯類。下面舉個(gè)例子進(jìn)行說明:

如上,我們將 dao 層的類配置在 application-dao.xml 文件中,將 service 層的類配置在 application-service.xml 文件中。然后我們將這兩個(gè)配置文件通過標(biāo)簽導(dǎo)入到 application.xml 文件中。此時(shí),我們可以讓業(yè)務(wù)容器去加載 application.xml 配置文件即可。另一方面,我們將 Web 相關(guān)的配置放在 application-web.xml 文件中,并將該文件交給 Web 容器去加載。

這里我們把配置文件進(jìn)行分層,結(jié)構(gòu)上看起來清晰了很多,也便于維護(hù)。這個(gè)其實(shí)和代碼分層是一個(gè)道理,如果我們把所有的代碼都放在同一個(gè)包下,那看起來會(huì)多難受啊。同理,我們用業(yè)務(wù)容器和 Web 容器去加載不同的類也是一種分層的體現(xiàn)吧。當(dāng)然,如果應(yīng)用比較簡(jiǎn)單,僅用 Web 容器去加載所有的類也不是不可以。

?2.1 業(yè)務(wù)容器的創(chuàng)建過程

前面說了一些背景知識(shí)作為鋪墊,那下面我們開始分析容器的創(chuàng)建過程吧。按照創(chuàng)建順序,我們先來分析業(yè)務(wù)容器的創(chuàng)建過程。業(yè)務(wù)容器的創(chuàng)建入口是 ContextLoaderListener 的 contextInitialized 方法。顧名思義,ContextLoaderListener 是用來監(jiān)聽 ServletContext 加載事件的。當(dāng) ServletContext 被加載后,監(jiān)聽器的 contextInitialized 方法就會(huì)被 Servlet 容器調(diào)用。ContextLoaderListener Spring 框架提供的,它的配置方法如下:

1 2 3 4 5 6 7 8 9 10 11 12 <web-app><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:application.xml</param-value></context-param><!-- 省略其他配置 --> </web-app>

如上,ContextLoaderListener 可通過 ServletContext 獲取到 contextConfigLocation 配置。這樣,業(yè)務(wù)容器就可以加載 application.xml 配置文件了。那下面我們來分析一下 ContextLoaderListener 的源碼吧。

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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {// 省略部分代碼@Overridepublic void contextInitialized(ServletContextEvent event) {// 初始化 WebApplicationContextinitWebApplicationContext(event.getServletContext());} }public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {/** 如果 ServletContext 中 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 屬性值* 不為空時(shí),表明有其他監(jiān)聽器設(shè)置了這個(gè)屬性。Spring 認(rèn)為不能替換掉別的監(jiān)聽器設(shè)置* 的屬性值,所以這里拋出異常。*/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!");}Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {...}long startTime = System.currentTimeMillis();try {if (this.context == null) {// 創(chuàng)建 WebApplicationContextthis.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {if (cwac.getParent() == null) {/** 加載父 ApplicationContext,一般情況下,業(yè)務(wù)容器不會(huì)有父容器,* 除非進(jìn)行配置*/ ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}// 配置并刷新 WebApplicationContextconfigureAndRefreshWebApplicationContext(cwac, servletContext);}}// 設(shè)置 ApplicationContext 到 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) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {...}if (logger.isInfoEnabled()) {...}return this.context;}catch (RuntimeException ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error("Context initialization failed", err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;} }

如上,我們看一下上面的創(chuàng)建過程。首先 Spring 會(huì)檢測(cè) ServletContext 中 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 屬性有沒有被設(shè)置,若被設(shè)置過,則拋出異常。若未設(shè)置,則調(diào)用 createWebApplicationContext 方法創(chuàng)建容器。創(chuàng)建好后,再調(diào)用 configureAndRefreshWebApplicationContext 方法配置并刷新容器。最后,調(diào)用 setAttribute 方法將容器設(shè)置到 ServletContext 中。經(jīng)過以上幾步,整個(gè)創(chuàng)建流程就結(jié)束了。流程并不復(fù)雜,可簡(jiǎn)單總結(jié)為創(chuàng)建容器 → 配置并刷新容器 → 設(shè)置容器到 ServletContext 中。這三步流程中,最后一步就不進(jìn)行分析,接下來分析一下第一步和第二步流程對(duì)應(yīng)的源碼。如下:

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 protected WebApplicationContext createWebApplicationContext(ServletContext sc) {// 判斷創(chuàng)建什么類型的容器,默認(rèn)類型為 XmlWebApplicationContextClass<?> contextClass = determineContextClass(sc);if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");}// 通過反射創(chuàng)建容器return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }protected Class<?> determineContextClass(ServletContext servletContext) {/** 讀取用戶自定義配置,比如:* <context-param>* <param-name>contextClass</param-name>* <param-value>XXXConfigWebApplicationContext</param-value>* </context-param>*/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 {/** 若無自定義配置,則獲取默認(rèn)的容器類型,默認(rèn)類型為 XmlWebApplicationContext。* defaultStrategies 讀取的配置文件為 ContextLoader.properties,* 該配置文件內(nèi)容如下:* org.springframework.web.context.WebApplicationContext =* org.springframework.web.context.support.XmlWebApplicationContext*/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);}} }

簡(jiǎn)單說一下 createWebApplicationContext 方法的流程,該方法首先會(huì)調(diào)用 determineContextClass 判斷創(chuàng)建什么類型的容器,默認(rèn)為 XmlWebApplicationContext。然后調(diào)用 instantiateClass 方法通過反射的方式創(chuàng)建容器實(shí)例。instantiateClass 方法就不跟進(jìn)去分析了,大家可以自己去看看,比較簡(jiǎn)單。

繼續(xù)往下分析,接下來分析一下 configureAndRefreshWebApplicationContext 方法的源碼。如下:

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 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// 從 ServletContext 中獲取用戶配置的 contextId 屬性String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam != null) {// 設(shè)置容器 idwac.setId(idParam);}else {// 用戶未配置 contextId,則設(shè)置一個(gè)默認(rèn)的容器 idwac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(sc.getContextPath()));}}wac.setServletContext(sc);// 獲取 contextConfigLocation 配置String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);// 刷新容器wac.refresh(); }

上面的源碼不是很長(zhǎng),邏輯不是很復(fù)雜。下面簡(jiǎn)單總結(jié) configureAndRefreshWebApplicationContext 方法主要做了事情,如下:

  • 設(shè)置容器 id
  • 獲取 contextConfigLocation 配置,并設(shè)置到容器中
  • 刷新容器
  • 到此,關(guān)于業(yè)務(wù)容器的創(chuàng)建過程就分析完了,下面我們繼續(xù)分析 Web 容器的創(chuàng)建過程。

    ?2.2 Web 容器的創(chuàng)建過程

    前面說了業(yè)務(wù)容器的創(chuàng)建過程,業(yè)務(wù)容器是通過 ContextLoaderListener。那 Web 容器是通過什么創(chuàng)建的呢?答案是通過 DispatcherServlet。我在上一篇文章介紹 HttpServletBean 抽象類時(shí),說過該類覆寫了父類 HttpServlet 中的 init 方法。這個(gè)方法就是創(chuàng)建 Web 容器的入口,那下面我們就從這個(gè)方法入手。如下:

    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 // -☆- org.springframework.web.servlet.HttpServletBean public final void init() throws ServletException {if (logger.isDebugEnabled()) {...}// 獲取 ServletConfig 中的配置信息PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {/** 為當(dāng)前對(duì)象(比如 DispatcherServlet 對(duì)象)創(chuàng)建一個(gè) BeanWrapper,* 方便讀/寫對(duì)象屬性。*/ BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);// 設(shè)置配置信息到目標(biāo)對(duì)象中bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {...}throw ex;}}// 進(jìn)行后續(xù)的初始化initServletBean();if (logger.isDebugEnabled()) {...} }protected void initServletBean() throws ServletException { }

    上面的源碼主要做的事情是將 ServletConfig 中的配置信息設(shè)置到 HttpServletBean 的子類對(duì)象中(比如 DispatcherServlet),我們并未從上面的源碼中發(fā)現(xiàn)創(chuàng)建容器的痕跡。不過如果大家注意看源碼的話,會(huì)發(fā)現(xiàn) initServletBean 這個(gè)方法稍顯奇怪,是個(gè)空方法。這個(gè)方法的訪問級(jí)別為 protected,子類可進(jìn)行覆蓋。HttpServletBean 子類 FrameworkServlet 覆寫了這個(gè)方法,下面我們到 FrameworkServlet 中探索一番。

    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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 // -☆- org.springframework.web.servlet.FrameworkServlet protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");if (this.logger.isInfoEnabled()) {...}long startTime = System.currentTimeMillis();try {// 初始化容器this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException ex) {this.logger.error("Context initialization failed", ex);throw ex;}catch (RuntimeException ex) {this.logger.error("Context initialization failed", ex);throw ex;}if (this.logger.isInfoEnabled()) {...} }protected WebApplicationContext initWebApplicationContext() {// 從 ServletContext 中獲取容器,也就是 ContextLoaderListener 創(chuàng)建的容器WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;/** 若下面的條件成立,則需要從外部設(shè)置 webApplicationContext。有兩個(gè)途徑可以設(shè)置 * webApplicationContext,以 DispatcherServlet 為例:* 1. 通過 DispatcherServlet 有參構(gòu)造方法傳入 WebApplicationContext 對(duì)象* 2. 將 DispatcherServlet 配置到其他容器中,由其他容器通過 * setApplicationContext 方法進(jìn)行設(shè)置* * 途徑1 可參考 AbstractDispatcherServletInitializer 中的 * registerDispatcherServlet 方法源碼。一般情況下,代碼執(zhí)行到此處,* this.webApplicationContext 為 null,大家可自行調(diào)試進(jìn)行驗(yàn)證。*/if (this.webApplicationContext != null) {wac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {if (cwac.getParent() == null) {// 設(shè)置 rootContext 為父容器cwac.setParent(rootContext);}// 配置并刷新容器configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// 嘗試從 ServletContext 中獲取容器wac = findWebApplicationContext();}if (wac == null) {// 創(chuàng)建容器,并將 rootContext 作為父容器wac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {onRefresh(wac);}if (this.publishContext) {String attrName = getServletContextAttributeName();// 將創(chuàng)建好的容器設(shè)置到 ServletContext 中g(shù)etServletContext().setAttribute(attrName, wac);if (this.logger.isDebugEnabled()) {...}}return wac; }protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {// 獲取容器類型,默認(rèn)為 XmlWebApplicationContext.classClass<?> contextClass = getContextClass();if (this.logger.isDebugEnabled()) {...}if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + getServletName() +"': custom WebApplicationContext class [" + contextClass.getName() +"] is not of type ConfigurableWebApplicationContext");}// 通過反射實(shí)例化容器ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());wac.setParent(parent);wac.setConfigLocation(getContextConfigLocation());// 配置并刷新容器configureAndRefreshWebApplicationContext(wac);return wac; }protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// 設(shè)置容器 idif (this.contextId != null) {wac.setId(this.contextId);}else {// 生成默認(rèn) idwac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}// 后置處理,子類可以覆蓋進(jìn)行一些自定義操作。在 Spring MVC 未使用到,是個(gè)空方法。postProcessWebApplicationContext(wac);applyInitializers(wac);// 刷新容器wac.refresh(); }

    以上就是創(chuàng)建 Web 容器的源碼,下面總結(jié)一下該容器創(chuàng)建的過程。如下:

  • 從 ServletContext 中獲取 ContextLoaderListener 創(chuàng)建的容器
  • 若 this.webApplicationContext != null 條件成立,僅設(shè)置父容器和刷新容器即可
  • 嘗試從 ServletContext 中獲取容器,若容器不為空,則無需執(zhí)行步驟4
  • 創(chuàng)建容器,并將 rootContext 作為父容器
  • 設(shè)置容器到 ServletContext 中
  • 到這里,關(guān)于 Web 容器的創(chuàng)建過程就講完了。總的來說,Web 容器的創(chuàng)建過程和業(yè)務(wù)容器的創(chuàng)建過程大致相同,但是差異也是有的,不能忽略。

    ?3.總結(jié)

    本篇文章對(duì) Spring MVC 兩種容器的創(chuàng)建過程進(jìn)行了較為詳細(xì)的分析,總的來說兩種容器的創(chuàng)建過程并不是很復(fù)雜。大家在分析這兩種容器的創(chuàng)建過程時(shí),看的不明白的地方,可以進(jìn)行調(diào)試,這對(duì)于理解代碼邏輯還是很有幫助的。當(dāng)然閱讀 Spring MVC 部分的源碼最好有 Servlet 和 Spring IOC 容器方面的知識(shí),這些是基礎(chǔ),Spring MVC 就是在這些基礎(chǔ)上構(gòu)建的。

    限于個(gè)人能力,文章敘述有誤,還望大家指明。也請(qǐng)多多指教,在這里說聲謝謝。好了,本篇文章就到這里了。感謝大家的閱讀。

    ?參考

    • 《看透Spring MVC》- 韓路彪

    ?附錄:Spring 源碼分析文章列表

    ?Ⅰ. IOC

    更新時(shí)間標(biāo)題
    2018-05-30Spring IOC 容器源碼分析系列文章導(dǎo)讀
    2018-06-01Spring IOC 容器源碼分析 - 獲取單例 bean
    2018-06-04Spring IOC 容器源碼分析 - 創(chuàng)建單例 bean 的過程
    2018-06-06Spring IOC 容器源碼分析 - 創(chuàng)建原始 bean 對(duì)象
    2018-06-08Spring IOC 容器源碼分析 - 循環(huán)依賴的解決辦法
    2018-06-11Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對(duì)象
    2018-06-11Spring IOC 容器源碼分析 - 余下的初始化工作

    ?Ⅱ. AOP

    更新時(shí)間標(biāo)題
    2018-06-17Spring AOP 源碼分析系列文章導(dǎo)讀
    2018-06-20Spring AOP 源碼分析 - 篩選合適的通知器
    2018-06-20Spring AOP 源碼分析 - 創(chuàng)建代理對(duì)象
    2018-06-22Spring AOP 源碼分析 - 攔截器鏈的執(zhí)行過程

    ?Ⅲ. MVC

    更新時(shí)間標(biāo)題
    2018-06-29Spring MVC 原理探秘 - 一個(gè)請(qǐng)求的旅行過程
    2018-06-30Spring MVC 原理探秘 - 容器的創(chuàng)建過程
    • 本文鏈接:?https://www.tianxiaobo.com/2018/06/30/Spring-MVC-原理探秘-容器的創(chuàng)建過程/

    http://www.tianxiaobo.com/2018/06/30/Spring-MVC-%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A7%98-%E5%AE%B9%E5%99%A8%E7%9A%84%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B/?

    總結(jié)

    以上是生活随笔為你收集整理的Spring MVC 原理探秘 - 容器的创建过程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。