“當時年少春衫薄,騎馬倚斜橋,滿樓紅袖招”
常常有人沉溺于回憶之中陡舅,但是我個人覺得還是要向前看抵乓,做好眼前的事才是對自己最大的負責伴挚,“來世不可待,往事不可追靶衍。”
一茎芋、我閱讀源碼前參照書籍
相信閱讀源碼的人颅眶,應該都查過相關的書籍,我在閱讀源碼的時候田弥,我查看了一下相關的書籍涛酗,業(yè)界好像常常有人推薦《spring源碼深度解析》這本書,我也看了一半,開始閱讀源碼商叹,這本書從BeanFactory入手燕刻,先從解析配置文件開始,講XmlBeanFactory
這個類剖笙,我依葫蘆畫瓢卵洗,發(fā)現這個類已經不推薦使用了。
@Deprecated:若某類或某方法加上該注解之后弥咪,表示此方法或類不再建議使用过蹂,調用時也會出現刪除線,但并不代表不能用聚至,只是說酷勺,不推薦使用,因為還有更好的方法可以調用扳躬。
還有一本書《Spring技術內幕》脆诉,這個本書也應該看spring源碼的朋友,可能會看的一本書贷币。
二库说、閱讀源碼前自己的理解與想法
一開始到這個地方,不知道該怎么繼續(xù)看下去了片择,但是細細想來潜的,按照我們自己的理解,首先字管,我的理解是(個人理解啰挪,錯誤的地方請大家指正),任何項目啟動的時候嘲叔,第一件事是干什么咧亡呵,我認為是讀取配置,講項目需要的相關的類加載到內存中硫戈,依此類推锰什,spring最開始學習的時候,大家都是先從xml配置beans開始丁逝,那么啟動的時候汁胆,spring是不是也應該先把xml中的配置讀取到,然后按照配置霜幼,將相關的bean放到容器中嫩码,依照設定好的流程,完善上下文的其他部分罪既,也就是ApplicationContext铸题。
我先把自己的理解猜想寫下來铡恕,再閱讀源碼的過程,一點點修正我的想法丢间。
三探熔、開始證實
既然我認為上下文(ApplicationContext)是這個源碼閱讀的關鍵,那么我就從構建一個上下文(ApplicationContext)開始烘挫。
package com.laoye.test;
import com.laoye.spring.beans.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeansTest {
@Test
public void testStudent(){
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
Student student = context.getBean("student",Student.class);
System.out.println(student.getName());
}
}
代碼如上祭刚,具體的如何構建spring閱讀源碼環(huán)境以及增加一個測試模塊,我已經在記錄二中介紹過了墙牌,我就不過多介紹了涡驮。
總共三行代碼,其中第一行喜滨,讀取xml配置捉捅,生成上下文,可以看看ApplicationContext和ClassPathXmlApplicationContext之間的關系:
首先走進第一行代碼虽风,就是進入ClassPathXmlApplicationContext的構造器中棒口,構造器代碼如下:
/**
* Create a new ClassPathXmlApplicationContext, loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation resource location
* @throws BeansException if context creation failed
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
首先看注解中的介紹這個構造器要做什么事:創(chuàng)建新的ClassPathXmlApplicationContext,加載xml文件中的定義并自動刷新上下文辜膝。那么實際怎么做的无牵,向下走,代碼如下:
/**
* Create a new ClassPathXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of resource locations
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
其中的configLocations是我們寫入的參數classpath*:application.xml
,refresh為true
,parent為null
,當然在這里最好也讀一下注釋內容厂抖,前半部分跟上面看的內容差不多茎毁,多了一個內容loading all bean definitions and creating all singletons.Alternatively, call refresh manually after further configuring the context.
(加載所有定義的bean,或者在進一步配置上下文之后手動調用refresh)忱辅。
代碼中super(parent);
這句代碼七蜘,Java中子類的構造器中必須調用父類的構造器,如果父類沒有無參的構造器墙懂,則必須顯式調用父類的構造器橡卤,就是使用supper
,并且是寫到第一行损搬,相當于添加父類的引用碧库,這種顯式調用一直到類AbstractApplicationContext
,可以由上面提供的類的繼承圖可以看到ClassPathXmlApplicationContext
向上跨過四層到AbstractApplicationContext
,AbstractApplicationContext中使用的代碼如下:
/**
* Create a new AbstractApplicationContext with the given parent context.
* @param parent the parent context
*/
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
按照習慣巧勤,先讀注釋嵌灰,再看代碼,下面是兩行代碼分別調用的方法:
this()指向AbstractApplicationContext的構造方法
/**
* Create a new AbstractApplicationContext with no parent.
*/
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
這個方法構造器內生成了一個資源解析器踢关,可以走進去看一下伞鲫,就是new了一個資源解析器對象PathMatchingResourcePatternResolver
(它實現了接口ResourcePatternResolver
)。不過記住這個對象签舞,在后面的源碼中會用到秕脓,不然到時看代碼的時候一頭霧水。
setParent(parent)方法的代碼:
/**
* Set the parent of this application context.
* <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is
* {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
* this (child) application context environment if the parent is non-{@code null} and
* its environment is an instance of {@link ConfigurableEnvironment}.
* @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
*/
@Override
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
應該可以知道儒搭,參數傳進來是null吠架,所以代碼就很好懂。
顯式的supper()方法到這里就是盡頭搂鲫,接下來回到ClassPathXmlApplicationContext構造器傍药,看第二行代碼 setConfigLocations(configLocations);
走到方法里面去看
/**
* Set the config locations for this application context.
* <p>If not set, the implementation may use a default as appropriate.
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
我們傳的參數locations為classpath*:application.xml
,代碼簡單,configLocations
是一個String數組魂仍,值得注意的是拐辽,setConfigLocations
這個方法,在類AbstractRefreshableConfigApplicationContext
中擦酌,有上面的繼承圖我們可以知道俱诸,ClassPathXmlApplicationContext
向上兩層就是該類。
ClassPathXmlApplicationContext構造器中剩下的就是整個方法的重中之重赊舶,refresh()
方法(boolean變量refresh傳的true睁搭,所以refresh是必須調用的),
走到refresh()方法里:
@Override
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);
try {
// 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();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
這里我們可以看到笼平,spring的整體風格的一個鮮明的例子园骆,我們寫代碼也應該學習這種方式,整個方法步驟分明寓调,每一個步驟都有說明锌唾,組合各個基礎方法,并且異常處理非常的好夺英,我個人認為這一個模版方法鸠珠,值得深入了解。
由于這個是接口方法的實現秋麸,所以整個方法的注釋在上層接口方法上渐排,這個方法到底在干一個什么事,可以從注解中了解到:
Load or refresh the persistent representation of the configuration,which might an XML file, properties file, or relational database schema.As this is a startup method, it should destroy already created singletons if it fails, to avoid dangling resources. In other words, after invocation of that method, either all or no singletons at all should be instantiated.
(加載或刷新配置的持久表示形式灸蟆,這可能是XML文件驯耻、屬性文件或關系數據庫。由于這是一種啟動方法炒考,因此如果失敗可缚,它應該銷毀已創(chuàng)建的單例,以避免資源空置斋枢。換句話說帘靡,在調用該方法之后,要么全部實例化瓤帚,要么根本不實例化單例描姚。)
現在再來看這個方法涩赢,先讀每一行代碼上的注解,可以看到步驟:
1.Prepare this context for refreshing.(準備上下文的刷新)
2.Tell the subclass to refresh the internal bean factory.(通知子類刷新內部的bean factory)
3.Prepare the bean factory for use in this context.(準備上下文使用的bean factory)
4.Allows post-processing of the bean factory in context subclasses.(在上下文子類中轩勘,允許bean factory的后處理)
5.Invoke factory processors registered as beans in the context.(在上下文中調用注冊為bean的工廠處理器)
6.Register bean processors that intercept bean creation.(注冊攔截bean創(chuàng)建的bean處理器筒扒。)
7.Initialize message source for this context.(為此上下文初始化消息源。)
8.Initialize event multicaster for this context.(初始化上下文中事件廣播绊寻。)
9.Initialize other special beans in specific context subclasses.(初始化特定上下文子類中的其他特殊bean花墩。)
10.Check for listener beans and register them.(檢查監(jiān)聽bean并注冊。)
11.Instantiate all remaining (non-lazy-init) singletons.(實例化(非延遲加載)剩下的單例)
12.Last step: publish corresponding event.(最后一步:發(fā)布對應的事件)
翻譯僅供參考
說明一下:lazy-init這個配置只針對scope為singleton的bean澄步,延遲加載 ,設置為lazy的bean將不會在ApplicationContext啟動時提前被實例化冰蘑,而是在第一次向容器通過getBean索取bean時實例化的。這種懶加載的方式村缸,也是降低啟動時間的一種手段祠肥。
我會一點點慢慢記錄,爭取每一篇文章不會太長也不會太短王凑,太長看到一半就不想看了搪柑,太短說不了什么內容。會在下一篇開始詳細介紹每一個步驟索烹。