我們在最開始接觸spring的時候,看到不少書spring入門的例子如下
ApplicationContext atx = new ClassPathXmlApplicationContext("application.xml");
atx.getBean("benefitService");
上面這個例子第一行是表示如何初始化一個spring 容器,第二表示如何從一個已經初始化後的spring容器中按bean id得到這個bean, 絕大部分spring應用中,bean都是按業務模闆和層次分别配置在不同的xml檔案中, spring容器根據配置的xml檔案名路徑去分别解析這些xml 配置檔案,生成相應的BeanDefinition 執行個體,一個bean對應一個BeanDefinition, 解析完成bean 的xml配置檔案之後,spring容器就開始初始bean,大概的過程如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuYWO2cTZ5IWYkZTOzADM2kDO0cDMkRDOiRGOmVGZmRWZvwVbvNmLj5Wat4Wd5lGbh5iY1BXLn1WauU3bop3ZuFGat42YucWbp1iMhRXYvw1LcpDc0RHaiojIsJye.png)
這篇文章主要分析第一個階段,即xml配置檔案 ---->BeanDefinition這個過程,首先根據IDE工具看一下ClassPathXmlApplicationContext 這個類的繼承關系:
通過這個繼續關系,發現ClassPathXmlApplicationContext也是間接實作了ResourceLoader這個接口, ResourceLoader的實作類主要用于根據給定的資源檔案位址傳回對應的Resource,在本例中,這個資源檔案就是application.xml;
接着往下看
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh,
ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
代碼到了含有三個參數的構造方法,主要有三個步驟
super(parent)
這個步驟主要是調父類的構造器初始化容器的parent對象,這示例中,parent這個參數為空,其次是初始化資源模式解析器resourcePatternResolver,是一個實作了ResourceLoader的類,源碼如下 :
public AbstractApplicationContext(ApplicationContext parent) {
this.parent = parent;
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
setConfigLocations(configLocations)
這行代碼,主要就是初始化configLocations這個數組字段,源碼如下:
private String[] configLocations;
public void setConfigLocations(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;
}
}
其中resolvePath主要解析并填充資源路徑中的一些系統占位符,
如開始符:${,
結束符: }
分割符: :
refresh()
前面兩步基本上都是容器本身的設定初始化,這個步驟才是spring 容器解析,建立初始化bean的關鍵步驟,點時去,我們發現這個方法長,隻分析xml解析的過程,其它的在這裡不一 一細說,在refresh方法中,第二代碼是這樣的:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
直接進入org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()這個方法,看一下這個方法體:
@Override
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, 如果已經存在beanFactory,就先銷毀beanFactory中所有的bean,再關閉這個beanFactory
- 建立一個DefaultListableBeanFactory 類型執行個體的beanFactory;
- 設定beanFactory的id,其中id是根據類名生成的,具體代碼是:
obj.getClass().getName() + "@" + getIdentityHexString(obj)
-
定置化beanFactory, 主要是設定: Bean是否需求覆寫重寫,是否允bean循環引用,參數自動發現和注釋字段bean比對解析,例如Autowired注解;
5.解析xml文檔,并生成BeanDefinition對象執行個體集合;
重點看第5個步驟,即loadBeanDefinitions(beanFactory)這行代碼,根據繼承關系,直接進入org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)這個方法
@Override
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.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);
}
這個方法的邏輯也很清晰
- 建立一個XmlBeanDefinitionReader 對象,這個類主要定義讀取的Document,并注冊BeanDefinition的功能;
- 設定beanDefinitionReader 對象的ResourceLoader,即ClassPathXmlApplicationContext類對象;
- 設定beanDefinitionReader 對象的解析對象:ResourceEntityResolver
- 初始化beanDefinitionReader 對象,在spring架構中,這個方法為空,是一個重寫spring架構的擴充點;
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}
- 正式解析xml檔案,并生成BeanDefinitions, 方法體如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
針對每個資源檔案,重點看下面這兩行代碼:
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
第一行代碼主是擷取路徑中以classpath:開頭的xml檔案,當然也有很多其它字首開頭的資源,org.springframework.util.ResourceUtils 這個類中有詳細的說明
第二主要是生成加載BeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
前面都是一些準備過程,接下來,到了真正執行loadBeanDefinitions
- getValidationModeForResource這個方法主要是擷取xml檔案的驗證模式,主要有DTD和XSD兩種驗證模式,這兩種驗證模式,spring都支援,方法源碼如下:
spring 源碼分析(1)-xml檔案解析super(parent)setConfigLocations(configLocations)refresh()
DTD模式示例:
XSD模式示例:
- 加載xml,并得到轉換成對應的Document執行個體
-
解析并注冊xml檔案定義相關的bean;
重點看一下步驟3:
相關的方法源碼如下:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用 DefaultBeanDefinitionDocumentReader執行個體化BeanDefinitionDocumentReader對象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 記錄統計前已經加載的BeanDefinition數量
int countBefore = getRegistry().getBeanDefinitionCount();
// 加載并注冊bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 記錄本次加載的 BeanDefinition數量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
重點分析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource))這行代
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
preProcessXml(root);
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}
registerBeanDefinitions這個方法的邏輯也比較清晰
- 指派readerContext
- 擷取xml對象加載包裝後對應Document對象
- 建立解析器 BeanDefinitionParserDelegate 類型的對象執行個體:delegate
- 處理xml檔案的前置處理,預設為空,這是一個擴充點;
- 解析xml檔案,并注冊到spring容器中
-
處理xml檔案的後軒處理,預設為空,這也是一個擴充點;
重點看一下parseBeanDefinitions這個方法,方法實作源碼如下:
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)) {
//預設标簽bean的解析
//<bean id="benefitService" class="com.tmall.xxx">
parseDefaultElement(ele, delegate);
} else {
//自定義标簽bean的解析
<tx:annotation-driven />
<dubbo:service timeout="3000" interface="com.tmall.xxx" ref="xxxx"/>
<context:component-scan base-package="com.tmall.xxx"/>
delegate.parseCustomElement(ele);
}
}
}
} else {
//自定義标簽bean的解析
<context:component-scan base-package="com.tmall.xxx"/>
delegate.parseCustomElement(root);
}
}
這個方法主要是針對bean的xml配置檔案中預設标簽和自定義标簽分别進行解析
預設标簽的解析方法parseDefaultElement的方法體如下:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 對import标簽的解析處理
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 對alias标簽的解析處理
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//對bean标簽的解析處理
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//對beanS标簽的解析處理
processBeanDefinition(ele, delegate);
}
}
到這個方法,我們終于看到與我們平時寫spring bean配置檔案相關的代碼,下面結bean标簽的解析處理代碼進行分析
bean标簽的解析最終會到parseBeanDefinitionElement方法,這個方法的部分代碼如下:
這個方法的主要工作内容包括:
- 提供元素中的id和name屬性
- 進一步解析其他所有屬性并統一封裝到AbstractBeanDefinition類型的執行個體中
-
如果檢測到bean沒有指定的beanName,那麼使用預設的規則為此Bean生成beanName
4.将擷取到的資訊封裝到BeanDefinitionHolder的執行個體中。
步驟2中對其它标簽的解析過程部分源碼如下:
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
BeanDefinition containingBean, AbstractBeanDefinition bd) {
if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // scope屬性
// Spring 2.x "scope" attribute
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // singleton 屬性
error("Specify either 'scope' or 'singleton', not both", ele);
}
}
else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
// Spring 1.x "singleton" attribute
bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?
BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);
}
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { // abstract屬性
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); // lazy-init屬性
if (DEFAULT_VALUE.equals(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); //autowire屬性
bd.setAutowireMode(getAutowireMode(autowire));
String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
bd.setDependencyCheck(getDependencyCheck(dependencyCheck));
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { // depends-on屬性
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn,
BEAN_NAME_DELIMITERS));
}
// autowire-candidate 屬性
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
// primary 屬性
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
// init-method屬性
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
if (!"".equals(initMethodName)) {
bd.setInitMethodName(initMethodName);
}
}
else {
if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
}
// destroy-method屬性
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
if (!"".equals(destroyMethodName)) {
bd.setDestroyMethodName(destroyMethodName);
}
}
else {
if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
}
// factory-method 屬性
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
// factory-bean 屬性
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
// 解析購造函數constructor-arg标簽元素
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) &&
nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
parseConstructorArgElement((Element) node, bd);
}
}
}
//解析property屬性
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
其它的标簽解析過程請參考下面這個類org.springframework.beans.factory.xml.BeanDefinitionParserDelegate
上面解析分析了預設标簽bean的源碼解析過程,下面再來看一下自定标簽的解析過程,方法位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(Element, BeanDefinition)源碼如下:
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
//擷取對應的命名空間
String namespaceUri = getNamespaceURI(ele);
// 根據命名空間得到對相應的NamespaceHandler
NamespaceHandler handler =
this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" +
namespaceUri + "]", ele);
return null;
}
// 調用自定義的NamespaceHandler handler進行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
由于spring自定義标簽大家在平時用得比較少,由于時間關系和篇幅關系,本來就不對自定義标簽進行詳細分析,下一篇文章會結合spring源碼對自定義标簽的使用和解析原理進行詳細的介紹分析
由于時間關系,文中有些地方沒有寫細緻,講得不夠清楚,可能不少地方還會出現低級的錯誤,請大家指正。