【Spring】BeanFactory 解析 bean 詳解

weknow619發表於2018-09-17

本文是Spring原始碼分析中的一篇,來講講Spring框架中BeanFactory解析bean的過程,先來看一個在Spring中一個基本的bean定義與使用。(也可以來公號檢視)

package bean;
public class TestBean {
    private String beanName = "beanName";

	public String getBeanName() {
		return beanName;
	}

	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}
}
複製程式碼

Spring配置檔案定義如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

	<bean id="testBean" class="bean.TestBean">
</beans>
複製程式碼

下面使用XmlBeanFactory來獲取該bean:

public class BeanTest {
	
	private static final java.util.logging.Logger logger = LoggerFactory.getLogger(BeanTest.class);
	
	@Test
	public void getBeanTest() {
		BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
		TestBean bean = factory.getBean("testBean");
		logger.info(bean.getBeanName);
	}
}
複製程式碼

這個單元測試執行結果就是輸出beanName,上面就是Spring最基本的bean的獲取操作,這裡我用BeanFactory作為容器來獲取bean的操作並不多見,在企業開發中一般是使用功能更完善的ApplicationContext,這裡先不討論這個,下面重點講解使用BeanFactory獲取bean的過程。

現在就來分析下上面的測試程式碼,看看Spring到底為我們做了什麼工作,上面程式碼完成功能的流程不外乎如此:

  1. 讀取Spring配置檔案root.xml;
  2. 根據root.xml中的bean配置找到對應的類的配置,並例項化;
  3. 呼叫例項化後的物件輸出結果。

先來看看XmlBeanFactory原始碼:

public class XmlBeanFactory extends DefaultListableBeanFactory {

	  private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

	  public XmlBeanFactory(Resource resource) throws BeansException {
		  this(resource, null);
	  }

	  public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
		  super(parentBeanFactory);
		  this.reader.loadBeanDefinitions(resource);
	  }
}
複製程式碼

從上面可以看出XmlBeanFactory繼承了DefaultListableBeanFactory,DefaultListableBeanFactory是Spring註冊載入bean的預設實現,它是整個bean載入的核心部分,XmlBeanFactory與它的不同點就是XmlBeanFactory使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了自己的BeanDefinitionReader讀取。 XmlBeanFactory載入bean的關鍵就在於XmlBeanDefinitionReader,下面看看XmlBeanDefinitionReader的原始碼(只列出部分):

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

	private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

	private ProblemReporter problemReporter = new FailFastProblemReporter();

	private ReaderEventListener eventListener = new EmptyReaderEventListener();

	private SourceExtractor sourceExtractor = new NullSourceExtractor();

	private NamespaceHandlerResolver namespaceHandlerResolver;

	private DocumentLoader documentLoader = new DefaultDocumentLoader();

	private EntityResolver entityResolver;

	private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
}
複製程式碼

XmlBeanDefinitionReader繼承自AbstractBeanDefinitionReader,下面是AbstractBeanDefinitionReader的原始碼(只列出部分):

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

	protected final Log logger = LogFactory.getLog(getClass());

	private final BeanDefinitionRegistry registry;

	private ResourceLoader resourceLoader;

	private ClassLoader beanClassLoader;

	private Environment environment;

	private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
}
複製程式碼

XmlBeanDefinitionReader主要通過以下三步來載入Spring配置檔案中的bean:

  1. 通過繼承自AbstractBeanDefinitionReader中的方法,使用ResourLoader將資原始檔(root.xml)路徑轉換為對應的Resource檔案;
  2. 通過DocumentLoader對Resource檔案進行轉換,將Resource檔案轉換為Ducument檔案;
  3. 通過DefaultBeanDefinitionDocumentReader類對Document進行解析,最後再對解析後的Element進行解析。

瞭解以上基礎後,接下來詳細分析下一開始例子中的程式碼:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
複製程式碼

先看看下面XmlBeanFactory初始化的時序圖來進一步瞭解這段程式碼的執行,

xmlBeanFactory建立序列圖.png
在這裡可以看出BeanTest測試類通過向ClassPathResource的構造方法傳入spring的配置檔案構造一個Resource資原始檔的例項物件,再通過這個Resource資原始檔來構造我們想要的XmlBeanFactory例項。在前面XmlBeanFactory原始碼中的構造方法可以看出,

public XmlBeanFactory(Resource resource) throws BeansException {
     this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
     super(parentBeanFactory);
     this.reader.loadBeanDefinitions(resource);
}
複製程式碼

this.reader.loadBeanDefinition(resource)就是資源載入真正的實現,時序圖中XmlBeanDefinitionReader載入資料就是在這裡完成的。

接下來跟進this.reader.loadBeanDefinition(resource)方法裡面(只列關鍵部分):

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
	
	@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}
}
複製程式碼

在loadBeanDefinition(resource)方法裡對資原始檔resource使用EncodedResource類進行編碼處理後繼續傳入loadBeanDefinitions方法,繼續跟進loadBeanDefinitions(new EncodedResource(resource))方法原始碼:

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 {
        // 從resource中獲取對應的InputStream,用於下面構造InputSource
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
            // 呼叫doLoadBeanDefinitions方法
			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(inputSource, encodedResource.getResource())方法,這是整個bean載入過程的核心方法,在這個方法執行bean的載入。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
		throws BeanDefinitionStoreException {
	try {
		Document doc = doLoadDocument(inputSource, resource);
		return registerBeanDefinitions(doc, resource);
	}
    /* 省略一堆catch */
}
複製程式碼

跟進doLoadDocument(inputSource, resource)原始碼:

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
	return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
			getValidationModeForResource(resource), isNamespaceAware());
}
複製程式碼

在doLoadDocument(inputSource, resource)方法裡就使用到了前面講的documentLoader載入Document,這裡DocumentLoader是個介面,真正呼叫的是其實現類DefaultDocumentLoader的loadDocument方法,跟進原始碼:

public class DefaultDocumentLoader implements DocumentLoader {

	@Override
	public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
			ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

		DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
		if (logger.isDebugEnabled()) {
			logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
		}
		DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
		return builder.parse(inputSource);
	}
}
複製程式碼

從原始碼可以看出這裡先建立DocumentBuilderFactory,再用它建立DocumentBuilder,進而解析inputSource來返回Document物件。得到Document物件後就可以準備註冊我們的Bean資訊了。

在上面的doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法中拿到Document物件後下面就是執行registerBeanDefinitions(doc, resource)方法了,看原始碼:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	documentReader.setEnvironment(getEnvironment());
    // 還沒註冊bean前的BeanDefinition載入個數
	int countBefore = getRegistry().getBeanDefinitionCount();
    // 載入註冊bean
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 本次載入註冊的BeanDefinition個數
	return getRegistry().getBeanDefinitionCount() - countBefore;
}
複製程式碼

這裡的doc就是上面的loadDocument方法載入轉換來的,從上面可以看出主要工作是交給BeanDefinitionDocumentReader的registerBeanDefinitions()方法實現的,這裡BeanDefinitionDocumentReader是個介面,註冊bean功能在預設實現類DefaultBeanDefinitionDocumentReader的該方法實現,跟進它的原始碼:

@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	logger.debug("Loading bean definitions");
	Element root = doc.getDocumentElement();
	doRegisterBeanDefinitions(root);
}
複製程式碼

到這裡通過doc.getDocumentElement()獲得Element物件後,交給doRegisterBeanDefinitions()方法後就是真正執行XML文件的解析了,跟進doRegisterBeanDefinitions()方法原始碼:

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)) {
				return;
			}
		}
	}

	preProcessXml(root);
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);

	this.delegate = parent;
}
複製程式碼

到這裡處理流程就很清晰了,先是對profile進行處理,之後就通過parseBeanDefinitions()方法進行文件的解析操作,跟進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;
                // 下面對bean進行處理
				if (delegate.isDefaultNamespace(ele)) {
					parseDefaultElement(ele, delegate);
				}
				else {
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		delegate.parseCustomElement(root);
	}
}
複製程式碼

上面if-else語句塊中的parseDefaultElement(ele, delegate)和delegate.parseCustomElement(ele)就是對Spring配置檔案中的預設名稱空間和自定義名稱空間進行解析用的。在Spring的XML配置中,預設Bean宣告就如前面定義的:

<bean id="testBean" class="bean.TestBean">
複製程式碼

自定義的Bean宣告如:

<tx:annotation-driven />
複製程式碼

XmlBeanFactory載入bean的整個過程基本就講解到這裡了。

作者注:原文發表在公號(點選檢視),定期分享IT網際網路、金融等工作經驗心得、人生感悟,歡迎訂閱交流,目前就職阿里-移動事業部,需要大廠內推的也可到公眾號砸簡歷,或檢視我個人資料獲取。(公號ID:weknow619)。

【Spring】BeanFactory 解析 bean 詳解

相關文章