本文節選自《Spring 5核心原理》
1 IoC與DI基本概念
IoC(Inversion of Control,控制反轉)就是把原來程式碼裡需要實現的物件建立、依賴,反轉給容器來幫忙實現。我們需要建立一個容器,同時需要一種描述來讓容器知道要建立的物件與物件的關係。這個描述最具體的表現就是我們所看到的配置檔案。
DI(Dependency Injection,依賴注入)就是指物件被動接受依賴類而不自己主動去找,換句話說,就是指物件不是從容器中查詢它依賴的類,而是在容器例項化物件時主動將它依賴的類注入給它。
我們先從自己設計的視角來考慮。
(1)物件與物件的關係怎麼表示?
可以用XML、properties等語義化配置檔案表示。
(2)描述物件關係的檔案存放在哪裡?
可能是classpath、filesystem或者URL網路資源、servletContext等。
(3)不同的配置檔案對物件的描述不一樣,如標準的、自定義宣告式的,如何統一?
在內部需要有一個統一的關於物件的定義,所有外部的描述都必須轉化成統一的描述定義。
(4)如何對不同的配置檔案進行解析?
需要對不同的配置檔案語法採用不同的解析器。
2 Spring核心容器類圖
2.1. BeanFactory
Spring中Bean的建立是典型的工廠模式,這一系列的Bean工廠,即IoC容器,為開發者管理物件之間的依賴關係提供了很多便利和基礎服務,在Spring中有許多IoC容器的實現供使用者選擇,其相互關係如下圖所示。
其中,BeanFactory作為最頂層的一個介面類,定義了IoC容器的基本功能規範,BeanFactory有三個重要的子類:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是從類圖中我們可以發現最終的預設實現類是DefaultListableBeanFactory,它實現了所有的介面。那麼為何要定義這麼多層次的介面呢?查閱這些介面的原始碼和說明發現,每個介面都有它的使用場合,主要是為了區分在Spring內部操作過程中物件的傳遞和轉化,對物件的資料訪問所做的限制。例如,ListableBeanFactory介面表示這些Bean可列表化,而HierarchicalBeanFactory表示這些Bean是有繼承關係的,也就是每個Bean可能有父Bean。AutowireCapableBeanFactory介面定義Bean的自動裝配規則。這三個介面共同定義了Bean的集合、Bean之間的關係及Bean行為。最基本的IoC容器介面是BeanFactory,來看一下它的原始碼:
public interface BeanFactory {
//對FactoryBean的轉義定義,因為如果使用Bean的名字檢索FactoryBean得到的物件是工廠生成的物件
//如果需要得到工廠本身,需要轉義
String FACTORY_BEAN_PREFIX = "&";
//根據Bean的名字,獲取在IoC容器中得到的Bean例項
Object getBean(String name) throws BeansException;
//根據Bean的名字和Class型別來得到Bean例項,增加了型別安全驗證機制
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//提供對Bean的檢索,看看在IoC容器中是否有這個名字的Bean
boolean containsBean(String name);
//根據Bean的名字得到Bean例項,同時判斷這個Bean是不是單例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws
NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws
NoSuchBeanDefinitionException;
//得到Bean例項的Class型別
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
//得到Bean的別名,如果根據別名檢索,那麼其原名也會被檢索出來
String[] getAliases(String name);
}
在BeanFactory裡只對IoC容器的基本行為做了定義,根本不關心你的Bean是如何定義及怎樣載入的。正如我們只關心能從工廠裡得到什麼產品,不關心工廠是怎麼生產這些產品的。
要知道工廠是如何產生物件的,我們需要看具體的IoC容器實現,Spring提供了許多IoC容器實現,比如GenericApplicationContext、ClasspathXmlApplicationContext等。
ApplicationContext是Spring提供的一個高階的IoC容器,它除了能夠提供IoC容器的基本功能,還為使用者提供了以下附加服務。
(1)支援資訊源,可以實現國際化(實現MessageSource介面)。
(2)訪問資源(實現ResourcePatternResolver介面,後面章節會講到)。
(3)支援應用事件(實現ApplicationEventPublisher介面)。
2.2. BeanDefinition
BeanDefinition 用於儲存 Bean 的相關資訊,包括屬性、構造方法引數、依賴的 Bean 名稱及是否單例、延遲載入等,它相當於例項化 Bean 的原材料,Spring 就是根據 BeanDefinition 中的資訊例項化 Bean。,其繼承體系如下圖所示。
2.3. BeanDefinitionReader
Bean的解析過程非常複雜,功能被分得很細,因為這裡需要被擴充套件的地方很多,必須保證足夠的靈活性,以應對可能的變化。Bean的解析主要就是對Spring配置檔案的解析。這個解析過程主要通過BeanDefinitionReader來完成,看看Spring中BeanDefinitionReader的類結構圖,如下圖所示。
通過前面的分析,我們對Spring框架體系有了一個基本的巨集觀瞭解,希望“小夥伴們”好好理解,最好在腦海中形成畫面,為以後的學習打下良好的基礎。
3 基於Web的IoC容器初體驗
我們還是從大家最熟悉的DispatcherServlet開始,最先想到的應該是DispatcherServlet的init()方法。我們在DispatherServlet中並沒有找到init()方法,經過探索,在其父類HttpServletBean中找到了,程式碼如下:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//定位資源
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
//載入配置資訊
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader,
getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
在init()方法中,真正完成初始化容器動作的程式碼其實在initServletBean()方法中,我們繼續跟進:
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
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()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed
in " + elapsedTime + " ms");
}
}
在上面的程式碼中終於看到了似曾相識的程式碼initWebApplicationContext(),繼續跟進:
protected WebApplicationContext initWebApplicationContext() {
//先從ServletContext中獲得父容器WebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//宣告子容器
WebApplicationContext wac = null;
//建立父、子容器之間的關聯關係
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//先去ServletContext中查詢Web容器的引用是否存在,並建立好預設的空IoC容器
if (wac == null) {
wac = findWebApplicationContext();
}
//給上一步建立好的IoC容器賦值
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
//觸發onRefresh()方法
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
@Nullable
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
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");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
wac.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());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
從上面的程式碼可以看出,在configAndRefreshWebApplicationContext()方法中呼叫了refresh()方法,這是真正啟動IoC容器的入口,後面會詳細介紹。IoC容器初始化以後,呼叫了DispatcherServlet的onRefresh()方法,在onRefresh()方法中又直接呼叫initStrategies()方法初始化Spring MVC的九大元件:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
//初始化策略
protected void initStrategies(ApplicationContext context) {
//多檔案上傳的元件
initMultipartResolver(context);
//初始化本地語言環境
initLocaleResolver(context);
//初始化模板處理器
initThemeResolver(context);
//初始化handlerMapping
initHandlerMappings(context);
//初始化引數介面卡
initHandlerAdapters(context);
//初始化異常攔截器
initHandlerExceptionResolvers(context);
//初始化檢視前處理器
initRequestToViewNameTranslator(context);
//初始化檢視轉換器
initViewResolvers(context);
//初始化Flashmap管理器
initFlashMapManager(context);
}
本文為“Tom彈架構”原創,轉載請註明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。
原創不易,堅持很酷,都看到這裡了,小夥伴記得點贊、收藏、在看,一鍵三連加關注!如果你覺得內容太乾,可以分享轉發給朋友滋潤滋潤!