spring 啟動載入過程
阿新 • • 發佈:2019-01-01
spring啟動component-scan類掃描載入過程—原始碼分析
spring通過DispatcherServlet載入:
系統配置:
servlet的規範中,如果load-on-startup被設定了,那麼就會被初始化的時候裝載,而servlet裝載時會呼叫其init()方法,那麼自然是呼叫DispatcherServlet的init方法,
通過原始碼一看,竟然沒有,但是並不帶表真的沒有,你會發現在父類的父類中:org.springframework.web.servlet.HttpServletBean有這個方法,
原始碼如下所示:
注意程式碼:initServletBean(); 其餘的都和載入bean關係並不是特別大,跟蹤進去會發現這個方法是在類:org.springframework.web.servlet.FrameworkServlet類中(是DispatcherServlet的父類、HttpServletBean的子類),
內部通過呼叫initWebApplicationContext()來初始化一個WebApplicationContext,原始碼片段
程式碼片段如下:
例如我們需要將WEB容器、以及容器的配置資訊設定進去,然後會呼叫一個refresh()方法,這個方法表面上是用來重新整理的,其實也是用來做初始化bean用的,也就是配置修改後,
如果你能呼叫它的這個方法,就可以重新裝載spring的資訊,我們看看原始碼中的片段如下:
其實這個方法,不論是通過ClassPathXmlApplicationContext還是WEB裝載都會呼叫這裡,我們看下ClassPathXmlApplicationContext中呼叫的部分:
不論是XmlWebApplicationContext、還是ClassPathXmlApplicationContext都繼承了類(間接繼承),AbstractApplicationContext,這個類中的refresh()方法是共用的,
也就是他們都呼叫的這個方法來載入bean的,在這個方法中,通過obtainFreshBeanFactory方法來構造beanFactory的
是不是看到一層呼叫一層很煩人,其實回過頭來想一想,它沒一層都有自己的處理動作,畢竟spring不是簡單的做一個bean載入,即使是這樣,
我們最少也需要做xml解析、類裝載和例項化的過程,每個步驟可能都有很多需求,因此分離設計,使得程式碼更加具有擴充套件性,我們繼續來看obtainFreshBeanFactory方法的描述:
這裡也是這樣,refreshBeanFactory如果沒有初始化beanFactory就是初始化它了,後面你看到的都是getBeanFactory的程式碼,也就是已經初始化好了,這個refreshBeanFactory方法類AbstractRefreshableApplicationContext中的方法,
它是AbstractApplicationContext的子類,同樣不論是XmlWebApplicationContext、還是ClassPathXmlApplicationContext都繼承了它,因此都能呼叫到這個一樣的初始化方法,來看看body部分的程式碼
我們要看的是bean在哪裡載入的,現在貌似還沒看到重點,繼續跟蹤loadBeanDefinitions(DefaultListableBeanFactory)方法
它由AbstractXmlApplicationContext類中的方法實現,web專案中將會由類:XmlWebApplicationContext來實現,其實差不多,主要是看啟動檔案是在那裡而已,
如果在非web類專案中沒有自定義的XmlApplicationContext,那麼其實功能可以參考XmlWebApplicationContext,可以認為是一樣的功能。那麼看看loadBeanDefinitions方法如下:
通過XmlBeanDefineitionReader來解析,跟蹤到loadBeanDefinitions方法裡面,會發現方法實現體在XmlBeanDefineitionReader的父類:AbstractBeanDefinitionReader中,程式碼如下:
所以是一個數組(這裡如何通過location找到檔案部分,在我們找class的時候自然明瞭,大家先不糾結這個問題)。
接下來有很多層呼叫,會以此呼叫:
AbstractBeanDefinitionReader.loadBeanDefinitions(Resources []) 迴圈Resource陣列,呼叫方法:
XmlBeanDefinitionReader.loadBeanDefinitions(Resource ) 和上面這個類是父子關係,接下來會做:doLoadBeanDefinitions、registerBeanDefinitions的操作,在註冊beanDefinitions的時候,其實就是要真正開始解析XML了
步驟一:loadBeanDefinitions
步驟二:doLoadBeanDefinitions
步驟三:registerBeanDefinitions
中間有解析XML的過程,但是貌似我們不是很關心,我們就關係類是怎麼載入的,雖然已經到XML解析部分了,所以主要看parseBeanDefinitions這個方法,
裡面會呼叫到BeanDefinitionParserDelegate類的parseCustomElement方法,用來解析bean的資訊:
這裡解析了XML的資訊,跟蹤進去,會發現用了NamespaceHandlerSupport的parse方法,它會根據節點的型別,找到一種合適的解析BeanDefinitionParser(介面),
他們預先被spring註冊好了,放在一個HashMap中,例如我們在spring 的annotation掃描中,通常會配置:<context:component-scan base-package="com.xxx" />
此時根據名稱“component-scan”就會找到對應的解析器來解析,而與之對應的就是ComponentScanBeanDefinitionParser的parse方法,這地方已經很明顯有掃描bean的概念在裡面了,
這裡的parse獲取到後,中間有一個非常非常關鍵的步驟那就是定義了ClassPathBeanDefinitionScanner來掃描類的資訊,它掃描的是什麼?是載入的類還是class檔案呢?
答案是後者,為何,因為有些類在初始化化時根本還沒被載入,ClassLoader根本還沒載入,只是ClassLoader可以找到這些class的路徑而已
那麼再哪裡掃描呢?這裡的doScan返回了一個Set<BeanDefinitionHolder>我們感到希望就在不遠處,進去看看doScan方法。
我們看到這麼大一坨程式碼,其實我們目前不關心的程式碼,暫時可以不管,我們就看怎麼掃描出來的,可以看出最關鍵的掃描程式碼是:findCandidateComponents(String basePackage)方法,
也就是通過每個basePackage去找到有那些類是匹配的,我們這裡假如配置了com.abc,或配置了 * 兩種情況說明。
但是這個好像和我們用的東西不太一樣,java中也沒見這種URL可以獲取到,spring到底是怎麼搞的呢?就要看如下程式碼
這裡有些細節,我們先不說,先看下這個resourcePatternResolover是什麼型別的,看到定義部分是:
而不是classpath*:*(這裡我就不說擷取字串的原始碼了),回到上一段程式碼,這裡再次呼叫了getResources(String)方法,又回到前面一個方法,這一次,依然是以classpath*:開頭,
所以第一層 if 語句會進去,而第二層不會,為什麼?在裡面的isPattern() 的實現中是這樣寫的:
因此在我們的這條路上,這個方法返回的是false,就會走到程式碼段findAllClassPathResources中,這就是為什麼上面提到會有用途的原因,好了,最最最最關鍵的地方來了哦。例如我們知道了一個com/abc/為字首,
此時要知道相關的classpath下面有哪些class是匹配的,如何做?自然用ClassLoader,我們看看Spring是不是這樣做的:
類名.class.getClassLoader().getResources("");
如果放為空,那麼就是獲取classpath的相關的根路徑(classpath可能有很多,但是根路徑,可以被合併),也就是如果你配置的*,獲取到的將是這個,也許你在web專案中,你會獲取到專案的根路徑(classes下面,以及tomcat的lib目錄)。
如果寫入一個:com/abc/ 那麼得到的將是掃描相關classpath下面所有的class和jar包中與之匹配的類名(字首部分)的路徑資訊,但是需要注意的是,如果有兩層jar包,而你想要掃描的類或者說想要通過spring載入的類在第二層jar包中,
這個方法是獲取不到的,這不是spring沒有去做這個事情,而是,java提供的getResources方法就是這樣的,有朋友問我的時候,正好遇到了類似的事情,另外需要注意的是,getResources這個方法是包含當前路徑的一個遞迴檔案查詢
(一般環境變數中都會配置 . ),所以如果是一個jar包,你要執行的話,切記放在某個根目錄來跑,因為當前目錄,就是根目錄也會被遞迴下去,你的程式會被莫名奇怪地慢。
回到上面的程式碼中,在findPathMatchingResources中我們這裡剛剛獲取到base的路徑列表,也就是所有包含類似com/abc/為字首的路徑,或classpath合併後的目錄根路徑;此時我們需要下面所有的class,
那麼就需要的是遞迴,這裡我就不再跟蹤了,大家可以自己去跟蹤裡面的幾個方法呼叫:doFindPathMatchingJarResources、doFindPathMatchingFileResources 。
幾乎不會用到:VfsResourceMatchingDelegate.findMatchingResources,所以主要是上面兩個,分別是jar包中的和工程裡面的class,跟蹤進去會發現,程式碼會不斷遞迴迴圈呼叫目錄路徑下的class檔案的路徑資訊,
最終會拿到相關的class列表資訊,但是這些class還並沒有做檢測是否有annotation,那是下一步做的事情,但是下一個步驟已經很簡單了,因為要檢測一個類的annotation
這裡大家還可以通過以下簡單的方式來測試呼叫路徑的問題:
回到findCandidateComponents方法中,isCandidateComponent(MetadataReader metadataReader)方法,這個方法裡先迴圈excludeFilters,再迴圈includeFilters,excludeFilters預設情況下沒有啥內容,
includeFilters預設情況下最少會有一個new AnnotationTypeFilter(Component.class);也就是預設情況下excludeFilters排除內容不會迴圈,includeFilters包含內容最少會匹配到AnnotationTypeFilter,
呼叫AnnotationTypeFilter.match方法是其父類AbstractTypeHierarchyTraversingFilter.math()方法,其內部呼叫matchSelf()調回子類的AnnotationTypeFilter.matchSelf()方法。
該方法中用||連線兩個判定分別是hasAnnotation、hasMetaAnnotation,前者判定註解名稱本身是否匹配因此Component肯定能匹配上,後者會判定註解的meta註解是否包含,Service、Controller、Repository註解都註解了Component,
因此它們會在後者匹配上。這樣match就肯定成立了
看了這麼多,是不是有點暈,沒關係,誰第一回看都這樣,當你下一次看的時候,有個思路就好了,我這裡並沒有像UML一樣理出他們的層次關係,和呼叫關係,僅僅針對程式碼呼叫逐層來說明,
大家如果初步看就是,由Servlet初始化來建立ApplicationContext,在設定了Servelt相關引數後,獲取servlet的配置檔案路徑或自己指定的配置檔案路徑(applicationContext.xml或其他的名字,可以一個或多個),
然後通過系列的XML解析,以及針對每種不同的節點型別使用不同的載入方式,其中component-scan用於指定掃描類的對應有一個Scanner,它會通過ClassLoader的getResources方法來獲取到class的路徑資訊,
那麼class的路徑都能獲取到,類的什麼還拿不到呢?
step 4下面 看refresh()的核心finishBeanFactoryInitialization(beanFactory);
經過obtainFreshBeanFactory() 這個方法,我們的beanFactory就準備好了,接下來我們主要圍繞finishBeanFactoryInitialization(beanFactory)方法,聊聊Spring是如何例項化bean的。
這個方法,就是為了例項化非懶載入的單例bean,我們走進 beanFactory.preInstantiateSingletons(); 看一看
(注意,這裡例項化單例,而Struts中Action是每次請求都建立,所以Action並不是單例的)
因為Struts專案中Action並不滿足條件 “不是抽象類, 且是單例, 且不是延遲載入”,所以該方法對我們自定義的Action幾乎沒有用,我們一直迴圈直到單例的物件出現,再來看這個程式碼。
我們把這小段程式碼提出來單獨看
判斷這個bean是否是抽象類,是否是單例,是否延遲載入
如果不是抽象類, 且是單例, 且不是延遲載入,那麼判斷是否實現 FactoryBean 介面
如果實現了 FactoryBean,則 getBean(FACTORY_BEAN_PREFIX + beanName),否則 getBean(beanName)
如果我們跟進 getBean 這個方法,發現它呼叫了 doGetBean 這個方法,我們再跟進,這個方法非常長(這裡就不貼出來了)
在這個方法中,你可以不斷地去跟進(這裡不再做具體展開),你會發現大概的步驟差不多是
建立一個bean的例項
將這個例項封裝到BeanWrapper中
而這裡bean的例項化方法,其實是 beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
這個instantiate 方法在 package org.springframework.beans.factory.support; --> SimpleInstantiationStrategy.java
在這之中採用反射機制將物件進行了例項化。
其實還涉及到bean例項化以後,Spring是如何將bean的屬性進行注入的,這裡暫時不做進一步的展開了。
可以知道的是,最終屬性的注入是利用反射機制,通過setter賦值的
spring通過DispatcherServlet載入:
系統配置:
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
servlet的規範中,如果load-on-startup被設定了,那麼就會被初始化的時候裝載,而servlet裝載時會呼叫其init()方法,那麼自然是呼叫DispatcherServlet的init方法,
通過原始碼一看,竟然沒有,但是並不帶表真的沒有,你會發現在父類的父類中:org.springframework.web.servlet.HttpServletBean有這個方法,
原始碼如下所示:
public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); 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) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like. initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
注意程式碼:initServletBean(); 其餘的都和載入bean關係並不是特別大,跟蹤進去會發現這個方法是在類:org.springframework.web.servlet.FrameworkServlet類中(是DispatcherServlet的父類、HttpServletBean的子類),
內部通過呼叫initWebApplicationContext()來初始化一個WebApplicationContext,原始碼片段
接下來需要知道的是如何初始化這個context的(按照使用習慣,其實只要得到了ApplicationContext,就得到了bean的資訊,所以在初始化ApplicationCotext的時候,就已經初始化好了bean的資訊,至少至少,它初始化好了bean的路徑,以及描述資訊),所以我們一旦知道ApplicationCotext是怎麼初始化的,就基本知道bean是如何載入的了。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"); } }
程式碼片段如下:
因為Root的ApplicationContext的資訊還根本沒建立,所以主要是看createWebApplicationContext這個方法,進去後,該方法前面部分,都是在設定一些相關的引數,protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; ... if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. onRefresh(wac); } if (this.publishContext) { // Publish the context as a servlet context attribute. 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; }
例如我們需要將WEB容器、以及容器的配置資訊設定進去,然後會呼叫一個refresh()方法,這個方法表面上是用來重新整理的,其實也是用來做初始化bean用的,也就是配置修改後,
如果你能呼叫它的這個方法,就可以重新裝載spring的資訊,我們看看原始碼中的片段如下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
...
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
...
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
其實這個方法,不論是通過ClassPathXmlApplicationContext還是WEB裝載都會呼叫這裡,我們看下ClassPathXmlApplicationContext中呼叫的部分:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
他們的區別在於,web容器中,用servlet裝載了,servlet中包裝了一個XmlWebApplicationContext而已,而ClassPathXmlApplicationContext是直接呼叫的,他們共同點是,不論是XmlWebApplicationContext、還是ClassPathXmlApplicationContext都繼承了類(間接繼承),AbstractApplicationContext,這個類中的refresh()方法是共用的,
也就是他們都呼叫的這個方法來載入bean的,在這個方法中,通過obtainFreshBeanFactory方法來構造beanFactory的
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
...
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
...
}
}
是不是看到一層呼叫一層很煩人,其實回過頭來想一想,它沒一層都有自己的處理動作,畢竟spring不是簡單的做一個bean載入,即使是這樣,
我們最少也需要做xml解析、類裝載和例項化的過程,每個步驟可能都有很多需求,因此分離設計,使得程式碼更加具有擴充套件性,我們繼續來看obtainFreshBeanFactory方法的描述:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
這裡很多人可能會不太注意refreshBeanFactory()這個方法,尤其是第一遍看這個程式碼的,如果你忽略掉,你可能會找不到bean在哪裡載入的,前面提到了refresh其實可以用以初始化,這裡也是這樣,refreshBeanFactory如果沒有初始化beanFactory就是初始化它了,後面你看到的都是getBeanFactory的程式碼,也就是已經初始化好了,這個refreshBeanFactory方法類AbstractRefreshableApplicationContext中的方法,
它是AbstractApplicationContext的子類,同樣不論是XmlWebApplicationContext、還是ClassPathXmlApplicationContext都繼承了它,因此都能呼叫到這個一樣的初始化方法,來看看body部分的程式碼
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
建立了一個beanFactory,然後下面的方法可以通過名稱就能看出是“載入bean的定義”,將beanFactory傳入,自然要載入到beanFactory中了,createBeanFactory就是例項化一個beanFactory沒別的,我們要看的是bean在哪裡載入的,現在貌似還沒看到重點,繼續跟蹤loadBeanDefinitions(DefaultListableBeanFactory)方法
它由AbstractXmlApplicationContext類中的方法實現,web專案中將會由類:XmlWebApplicationContext來實現,其實差不多,主要是看啟動檔案是在那裡而已,
如果在非web類專案中沒有自定義的XmlApplicationContext,那麼其實功能可以參考XmlWebApplicationContext,可以認為是一樣的功能。那麼看看loadBeanDefinitions方法如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
這裡有一個XmlBeanDefineitionReader,是讀取XML中spring的相關資訊(也就是解析SpringContext.xml的),這裡通過getConfigLocations()獲取到的就是這個或多個檔案的路徑,會迴圈,通過XmlBeanDefineitionReader來解析,跟蹤到loadBeanDefinitions方法裡面,會發現方法實現體在XmlBeanDefineitionReader的父類:AbstractBeanDefinitionReader中,程式碼如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
這裡大家會疑惑,為啥裡面還有一個loadBeanDefinitions,大家要知道,我們目前只解析到我們的springContext.xml在哪裡,但是還沒解析到springContext.xml的內容是什麼,可能有多個spring的配置檔案,這裡會出現多個Resource,所以是一個數組(這裡如何通過location找到檔案部分,在我們找class的時候自然明瞭,大家先不糾結這個問題)。
接下來有很多層呼叫,會以此呼叫:
AbstractBeanDefinitionReader.loadBeanDefinitions(Resources []) 迴圈Resource陣列,呼叫方法:
XmlBeanDefinitionReader.loadBeanDefinitions(Resource ) 和上面這個類是父子關係,接下來會做:doLoadBeanDefinitions、registerBeanDefinitions的操作,在註冊beanDefinitions的時候,其實就是要真正開始解析XML了
步驟一:loadBeanDefinitions
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
步驟二:doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
步驟三:registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
步驟三呼叫了DefaultBeanDefinitionDocumentReader類的registerBeanDefinitions方法,如下圖所示:
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
中間有解析XML的過程,但是貌似我們不是很關心,我們就關係類是怎麼載入的,雖然已經到XML解析部分了,所以主要看parseBeanDefinitions這個方法,
裡面會呼叫到BeanDefinitionParserDelegate類的parseCustomElement方法,用來解析bean的資訊:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
這裡解析了XML的資訊,跟蹤進去,會發現用了NamespaceHandlerSupport的parse方法,它會根據節點的型別,找到一種合適的解析BeanDefinitionParser(介面),
他們預先被spring註冊好了,放在一個HashMap中,例如我們在spring 的annotation掃描中,通常會配置:<context:component-scan base-package="com.xxx" />
此時根據名稱“component-scan”就會找到對應的解析器來解析,而與之對應的就是ComponentScanBeanDefinitionParser的parse方法,這地方已經很明顯有掃描bean的概念在裡面了,
這裡的parse獲取到後,中間有一個非常非常關鍵的步驟那就是定義了ClassPathBeanDefinitionScanner來掃描類的資訊,它掃描的是什麼?是載入的類還是class檔案呢?
答案是後者,為何,因為有些類在初始化化時根本還沒被載入,ClassLoader根本還沒載入,只是ClassLoader可以找到這些class的路徑而已
public BeanDefinition parse(Element element, ParserContext parserContext) {
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
注意這裡的scanner建立後,最關鍵的是doScan的功能,解析XML我想來看這個的不是問題,如果還不熟悉可以先看看,那麼我們得到了類似:com.xxx這樣的資訊,就要開始掃描類的列表,那麼再哪裡掃描呢?這裡的doScan返回了一個Set<BeanDefinitionHolder>我們感到希望就在不遠處,進去看看doScan方法。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
我們看到這麼大一坨程式碼,其實我們目前不關心的程式碼,暫時可以不管,我們就看怎麼掃描出來的,可以看出最關鍵的掃描程式碼是:findCandidateComponents(String basePackage)方法,
也就是通過每個basePackage去找到有那些類是匹配的,我們這裡假如配置了com.abc,或配置了 * 兩種情況說明。
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
是已經拿到了類的定義,會組裝資訊,如果我們配置了 com.abc會組裝為:classpath*:com/abc/**/*.class ,如果配置是 * ,那麼將會被組裝為classpath*:*/**/*.class ,但是這個好像和我們用的東西不太一樣,java中也沒見這種URL可以獲取到,spring到底是怎麼搞的呢?就要看如下程式碼
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
它竟然神奇般的通過這個路徑獲取到了URL,你一旦跟蹤你會發現,獲取出來的全是.class的路徑,包括jar包中的相關class路徑,這裡有些細節,我們先不說,先看下這個resourcePatternResolover是什麼型別的,看到定義部分是:
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
做了一個測試,用一個簡單main方法寫了一段: ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources("classpath*:com/abc/**/*.class");
獲取出來的果然是那樣,可以猜測,這個和ClassLoader的getResource方法有關係了,因為太類似了,我們跟蹤進去看下: public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
這個CLASSPATH_ALL_URL_PREFIX就是字串 classpath*: , 我們傳遞引數進來的時候一致,繼續走findPathMatchingResources方法,好了,越來越接近真相了。 protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
String rootDirPath = determineRootDir(locationPattern);
String subPattern = locationPattern.substring(rootDirPath.length());
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirURL = rootDirResource.getURL();
if (equinoxResolveMethod != null) {
if (rootDirURL.getProtocol().startsWith("bundle")) {
rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
rootDirResource = new UrlResource(rootDirURL);
}
}
if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
}
else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
這裡有一個rootDirPath,這個地方有個容易出錯的,是如果你配置的是 com.abc,那麼rootDirPath部分應該是:classpath*:com/abc/ 而如果配置是 * 那麼classpath*: 只有這個結果,而不是classpath*:*(這裡我就不說擷取字串的原始碼了),回到上一段程式碼,這裡再次呼叫了getResources(String)方法,又回到前面一個方法,這一次,依然是以classpath*:開頭,
所以第一層 if 語句會進去,而第二層不會,為什麼?在裡面的isPattern() 的實現中是這樣寫的:
public boolean isPattern(String path) {
return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}
在匹配前,做了一個substring的操作,會將“classpath*:”這個字串去掉,如果是配置的是com.abc就變成了"com/abc/",而如果配置為*,那麼得到的就是“” ,也就是長度為0的字串,因此在我們的這條路上,這個方法返回的是false,就會走到程式碼段findAllClassPathResources中,這就是為什麼上面提到會有用途的原因,好了,最最最最關鍵的地方來了哦。例如我們知道了一個com/abc/為字首,
此時要知道相關的classpath下面有哪些class是匹配的,如何做?自然用ClassLoader,我們看看Spring是不是這樣做的:
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isDebugEnabled()) {
logger.debug("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
果然不出所料,它也是用ClassLoader,只是它自己提供的getClassLoader()方法,也就是和spring的類使用同一個載入器範圍內的,以保證可以識別到一樣的classpath,自己模擬的時候,可以用一個類類名.class.getClassLoader().getResources("");
如果放為空,那麼就是獲取classpath的相關的根路徑(classpath可能有很多,但是根路徑,可以被合併),也就是如果你配置的*,獲取到的將是這個,也許你在web專案中,你會獲取到專案的根路徑(classes下面,以及tomcat的lib目錄)。
如果寫入一個:com/abc/ 那麼得到的將是掃描相關classpath下面所有的class和jar包中與之匹配的類名(字首部分)的路徑資訊,但是需要注意的是,如果有兩層jar包,而你想要掃描的類或者說想要通過spring載入的類在第二層jar包中,
這個方法是獲取不到的,這不是spring沒有去做這個事情,而是,java提供的getResources方法就是這樣的,有朋友問我的時候,正好遇到了類似的事情,另外需要注意的是,getResources這個方法是包含當前路徑的一個遞迴檔案查詢
(一般環境變數中都會配置 . ),所以如果是一個jar包,你要執行的話,切記放在某個根目錄來跑,因為當前目錄,就是根目錄也會被遞迴下去,你的程式會被莫名奇怪地慢。
回到上面的程式碼中,在findPathMatchingResources中我們這裡剛剛獲取到base的路徑列表,也就是所有包含類似com/abc/為字首的路徑,或classpath合併後的目錄根路徑;此時我們需要下面所有的class,
那麼就需要的是遞迴,這裡我就不再跟蹤了,大家可以自己去跟蹤裡面的幾個方法呼叫:doFindPathMatchingJarResources、doFindPathMatchingFileResources 。
幾乎不會用到:VfsResourceMatchingDelegate.findMatchingResources,所以主要是上面兩個,分別是jar包中的和工程裡面的class,跟蹤進去會發現,程式碼會不斷遞迴迴圈呼叫目錄路徑下的class檔案的路徑資訊,
最終會拿到相關的class列表資訊,但是這些class還並沒有做檢測是否有annotation,那是下一步做的事情,但是下一個步驟已經很簡單了,因為要檢測一個類的annotation
這裡大家還可以通過以下簡單的方式來測試呼叫路徑的問題:
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
Set<BeanDefinition> beanDefinitions = provider.findCandidateComponents("com/abc");
for(BeanDefinition beanDefinition : beanDefinitions) {
System.out.println(beanDefinition.getBeanClassName()
+ "\t" + beanDefinition.getResourceDescription()
+ "\t" + beanDefinition.getClass());
}
回到findCandidateComponents方法中,isCandidateComponent(MetadataReader metadataReader)方法,這個方法裡先迴圈excludeFilters,再迴圈includeFilters,excludeFilters預設情況下沒有啥內容,
includeFilters預設情況下最少會有一個new AnnotationTypeFilter(Component.class);也就是預設情況下excludeFilters排除內容不會迴圈,includeFilters包含內容最少會匹配到AnnotationTypeFilter,
呼叫AnnotationTypeFilter.match方法是其父類AbstractTypeHierarchyTraversingFilter.math()方法,其內部呼叫matchSelf()調回子類的AnnotationTypeFilter.matchSelf()方法。
該方法中用||連線兩個判定分別是hasAnnotation、hasMetaAnnotation,前者判定註解名稱本身是否匹配因此Component肯定能匹配上,後者會判定註解的meta註解是否包含,Service、Controller、Repository註解都註解了Component,
因此它們會在後者匹配上。這樣match就肯定成立了
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}
看了這麼多,是不是有點暈,沒關係,誰第一回看都這樣,當你下一次看的時候,有個思路就好了,我這裡並沒有像UML一樣理出他們的層次關係,和呼叫關係,僅僅針對程式碼呼叫逐層來說明,
大家如果初步看就是,由Servlet初始化來建立ApplicationContext,在設定了Servelt相關引數後,獲取servlet的配置檔案路徑或自己指定的配置檔案路徑(applicationContext.xml或其他的名字,可以一個或多個),
然後通過系列的XML解析,以及針對每種不同的節點型別使用不同的載入方式,其中component-scan用於指定掃描類的對應有一個Scanner,它會通過ClassLoader的getResources方法來獲取到class的路徑資訊,
那麼class的路徑都能獲取到,類的什麼還拿不到呢?
step 4下面 看refresh()的核心finishBeanFactoryInitialization(beanFactory);
經過obtainFreshBeanFactory() 這個方法,我們的beanFactory就準備好了,接下來我們主要圍繞finishBeanFactoryInitialization(beanFactory)方法,聊聊Spring是如何例項化bean的。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
return getEnvironment().resolvePlaceholders(strVal);
}
});
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
這個方法,就是為了例項化非懶載入的單例bean,我們走進 beanFactory.preInstantiateSingletons(); 看一看
(注意,這裡例項化單例,而Struts中Action是每次請求都建立,所以Action並不是單例的)
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
smartSingleton.afterSingletonsInstantiated();
return null;
}
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
因為Struts專案中Action並不滿足條件 “不是抽象類, 且是單例, 且不是延遲載入”,所以該方法對我們自定義的Action幾乎沒有用,我們一直迴圈直到單例的物件出現,再來看這個程式碼。
我們把這小段程式碼提出來單獨看
for (String beanName : beanNames) { //將載入進來的beanDefinitionNames迴圈分析
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { //如果不是抽象類, 且是單例, 且不是延遲載入
if (isFactoryBean(beanName)) { //是否實現FactoryBean介面
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
判斷這個bean是否是抽象類,是否是單例,是否延遲載入
如果不是抽象類, 且是單例, 且不是延遲載入,那麼判斷是否實現 FactoryBean 介面
如果實現了 FactoryBean,則 getBean(FACTORY_BEAN_PREFIX + beanName),否則 getBean(beanName)
如果我們跟進 getBean 這個方法,發現它呼叫了 doGetBean 這個方法,我們再跟進,這個方法非常長(這裡就不貼出來了)
在這個方法中,你可以不斷地去跟進(這裡不再做具體展開),你會發現大概的步驟差不多是
建立一個bean的例項
將這個例項封裝到BeanWrapper中
而這裡bean的例項化方法,其實是 beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
這個instantiate 方法在 package org.springframework.beans.factory.support; --> SimpleInstantiationStrategy.java
在這之中採用反射機制將物件進行了例項化。
其實還涉及到bean例項化以後,Spring是如何將bean的屬性進行注入的,這裡暫時不做進一步的展開了。
可以知道的是,最終屬性的注入是利用反射機制,通過setter賦值的