公司網(wǎng)站建設(shè)中心杭州網(wǎng)站優(yōu)化公司哪家好
這一節(jié)我們來回答上篇文章中避而不談的有關(guān)什么是RootApplicationContext的問題。
這就需要引入Spring MVC的有關(guān)Context Hierarchy的問題。Context Hierarchy意思就是Context層級,既然說到Context層級,說明在Spring MVC項目中,可能存在不止一個Context。
Context是上下文的意思,我們可以直接理解為容器,上一篇文章我們提到了ServletApplicationContext的概念,可以理解為Servlet容器。
除此之外,Spring項目中肯定還有有IoC容器,我們今天就來聊一下這兩個容器之間的關(guān)系。
為什么需要容器
我們可以這樣理解:凡是具有生命周期的對象,也就是說對象并不是在當前現(xiàn)成創(chuàng)建、使用完成之后就銷毀的,而是在當前現(xiàn)成使用完成之后不銷毀、其他線程還需要繼續(xù)使用的。這中對象就需要一個容器來保存。
比如Spring的單例Bean,在Spring初始化的過程中就會創(chuàng)建并存入Ioc容器,之后應(yīng)用使用過程中從Ioc容器中獲取。
Servlet對象(比如DispatchServlet)也是這樣,在Web應(yīng)用初始化的過程中創(chuàng)建,Controller、ViewResolver、ExceptionHandler等對象隨之也完成創(chuàng)建,這些對象在整個應(yīng)用的生命周期中會反復使用,因此,Servlet也必須要有一個容器來存儲。
Spring把容器稱之為上下文,Context。
我們可以把Servlet容器叫做WebApplicationContext,因為Servlet的出現(xiàn)就是為了解決Web應(yīng)用的,所以自然而然的,我們可以把Servlet容器稱為WebApplicationContext。
相對應(yīng)的,Spring Ioc容器,我們可以稱之為RootApplicationContext:根容器。
Servlet和根容器的關(guān)系
下圖一目了然的說明了兩者之間的關(guān)系:
Servlet容器存放Controller、VIewResolver、HanderMapping等DispatcherServlet的相關(guān)對象,根容器可以存放其他Service、Repositories等對象。
一個DispatcherServlet可以對應(yīng)的有一個Servlet容器,一個Web應(yīng)用可以有多個DispatcherServlet(這種應(yīng)用其實比較少見),所以一個應(yīng)用可以有多個Servlet容器。但是一般情況下,即使有多個Servlet容器,一個應(yīng)用也希望只有一個根容器,以便在不同的Servlet容器之間共享根容器的對象。
舉例
我們下面用幾個例子來說明兩者之間的關(guān)系。
還是延用上一篇文章的例子,并做如下簡單的改造。
首先,增加一個單例bean,以便啟用Spring IoC容器。我們只是簡單引入IoC容器,單例bean不需要太復雜,
package org.example.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;@Component
public class UserService {@AutowiredApplicationContext app;public String getUserInfo(){System.out.println(" application in UserService:"+ app);return "This is userinfo...from UserService...";}
}
只有一個方法,返回String。不過為了能說明當前單例Bean所處的容器,我們通過@Autowired引入ApplicationContext對象(希望大家還記得這一點,我們前面講過通過什么樣的方式,能夠在應(yīng)用中拿到Bean所處的Application對象),這樣的話我們就能夠知道當前Spring應(yīng)用的Ioc容器具體是哪個對象。
其次,我們需要新增一個Spring Ioc容器的配置類,我們稱之為RootConfiguration,配置類僅指定掃描路徑即可:
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan("org.example.service")
public class RootConfiguration {
}
最后,Controller改造一下,與UserService一樣,引入ApplicationContext(Controller中的ApplicationContext,我們可以人為他就是Servlet容器),log打印一下具體的Context,同時我們打印一下當前Servlet容器的父容器:
package org.example.controller;import org.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;@Controller
public class HelloWorldController {@AutowiredUserService userService;@AutowiredApplicationContext app;@GetMapping("/hello")@ResponseBodypublic String hello(ModelAndView model){DispatcherServlet d;String userInfo = userService.getUserInfo();System.out.println("app in controller:"+app);System.out.println("servletContext's parent Context"+app.getParent());return "<h1>"+userInfo+"</h1>";}
}
OK,準備工作完成,開始驗證。
舉例1 根容器不是必須存在
首先,根容器不是必須存在的。
但是由于我們增加了UserService這個bean,所以必須有Ioc容器,我們必須為IoC容器指定配置文件的包掃描路徑。
既然我們說根容器不是必須存在,那么,意思就是說,Servlet容器要同時充當IoC容器的角色。
所以我們必須在MvcConfiguration中增加寶掃描路徑:
package org.example.configuration;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan({"org.example.service","org.example.controller"})
//@ComponentScan({"org.example.controller"})
public class MvcConfiguration {
}
MvcInitializer中的getRootConfigClasses()返回null,則應(yīng)用初始化的過程中就不會創(chuàng)建根容器(通過查看createRootApplicationContext方法源碼得出的結(jié)論):
public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Class<?>[] getRootConfigClasses() {return null;}
啟動應(yīng)用,測試結(jié)果:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XBftrdeQ-1693234193599)(/img/bVc9m07)]
說明應(yīng)用是可以正常運行的,后臺log:
application in UserService:WebApplicationContext for namespace 'dispatcher-servlet', started on Tue Aug 22 22:35:34 CST 2023
app in controller:WebApplicationContext for namespace 'dispatcher-servlet', started on Tue Aug 22 22:35:34 CST 2023
servletContext's parent Contextnull
后臺log說明,Servlet容器和Spring IoC容器是同一個,他們的父容器是null。
指定Ioc容器為父容器
首先修改MvcConfiguration,指定Servlet容器只掃描Controller包:
package org.example.configuration;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan({"org.example.controller"})
public class MvcConfiguration {
}
MvcInitializer中的getRootConfigClasses()返回我們新增的RootConfiguration,則應(yīng)用初始化的過程中就會創(chuàng)建根容器:
public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[] {RootConfiguration.class};}
啟動應(yīng)用,測試:
application in UserService:Root WebApplicationContext, started on Tue Aug 22 22:44:08 CST 2023
app in controller:WebApplicationContext for namespace 'dispatcher-servlet', started on Tue Aug 22 22:44:09 CST 2023, parent: Root WebApplicationContext
servletContext's parent ContextRoot WebApplicationContext, started on Tue Aug 22 22:44:08 CST 2023
有測試結(jié)果可知:
1. UserService所處的是根容器。
2. Controller所處的是Servlet容器。
3. Servlet容器的父容器是根容器。
測試結(jié)果驗證了我們前面的推論,而且,Spring底層自動將Servlet容器的父容器設(shè)置為了根容器!在什么地方設(shè)置的?
Spring MVC框架在DispatcherServlet的初始化過程中(init方法),initWebApplicationContext的時候設(shè)置ServletContext的父容器為根容器。
上一篇 Spring MVC 三 :基于注解配置