【Spring原始碼閱讀】BeanDefinition原理與載入流程
BeanDefinition介面定義及其相關子類實現
在Spring容器初始化過程中,Spring會將配置檔案中配置的Java類封裝成一個個BeanDefinition。
BeanDefinition儲存了具體代表Bean的類,並通過實現了AttributeAccessor介面定義了讀寫屬性配置的相關方法。在基於xml配置Spring容器中,我們為某個Bean配置了具體的屬性值,這些都根據name-value對對映到BeanDefinition的元資料集合attributes中,在例項化成具體Bean的時候通過AttributeAccessor介面實現的方法完成屬性注入。
在這個基礎上BeanDefinition定義一個Bean基於Spring容器中的相關屬效能力,比如是否為單例,是否支援懶載入,是否支援自動裝配、為自動裝配首選等。
BeanDefinition作為介面定義,有眾多的實現類,常用的如下圖所示:
- AbstractBeanDefinition:BeanDefinition的較為完整、具體的抽象實現類,定義了三大子類ChildBeanDefinition,RootBeanDefinition,GenericBeanDefinition的基礎公用實現,具體實現了BeanDefinition屬性付覆寫,解析獲取BeanDefinition對應的BeanClass和Spring相關屬效能力的讀寫等(比如是否為單例,支援自動裝配等)
- RootBeanDefinition/ChildBeanDefinition:BeanDefinition存在父子關係,子BeanDefinition用ChildBeanDefinition表示,父BeanDefinition或沒有父子關係的用RootBeanDefinition表示。
- GenericBeanDefinition: 相比於RootBeanDefinition和ChildBeanDefinition在定義的時候就必須硬編碼,GenericBeanDefinition的優點可以動態的為GenericBeanDefinition設定parent。
- AnnotatedBeanDefinition:讀取基於註解定義的Bean。
Spring解析載入BeanDefinition流程
最終載入資源前的複雜過程
在最終開始載入資源,會經過一系列繁瑣的準備工作,核心載入邏輯在下一節doLoadBeanDefinitions載入邏輯實現描述,下面先是前期的準備工作:
在Spring初始化容器過程,第一步首先建立BeanFactory的時候,會呼叫AbstractApplicationContext的obtainFreshBeanFactory
refreshBeanFactory
方法,在這個方法裡,呼叫了loadBeanDefinitions(beanFactory)
來解析BeanDefinition,具體如下所示
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 載入Beanfinitions
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
具體實現如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 先建立一個基於xml的BeanDefinitionReader。
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 設定上下文環境和資源載入器,環境主要根據profile進行配置
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
//設定xml解析工具
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 在初始化裡呼叫了reader.setValidating(this.validating),其中this.validating預設為true,表示需要驗證xml檔案格式
initBeanDefinitionReader(beanDefinitionReader);
// 嘗試獲取配置檔案位置後,呼叫XmlBeanDefinitionReader內方法進行載入
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
XmlBeanDefinitionReader的loadBeanDefinitions方法定義在抽象父類AbstractBeanDefinitionReader中:
public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {
// 先呼叫這個方法
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
//根據傳入的位置陣列遍歷載入,對載入的BeanDefinition個數進行累加返回
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
// 隨後呼叫這個方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
//最後呼叫這個方法
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) {
// 根據根據模式配置載入路徑資源
try {
// 解析資源通配拿到資源陣列
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// 內部是一個for迴圈遍歷,最終呼叫int loadBeanDefinitions(Resource resource)方法
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 {
// 只能根據絕對路徑載入單個
Resource resource = resourceLoader.getResource(location);
// 具體載入操作,呼叫int loadBeanDefinitions(Resource resource)
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
//
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
// 最終呼叫方法入口,對資源進行編碼
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return 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());
}
// 初始載入為null,初始化一個HashSet,存入資源入參
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);
// 首次載入為null
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校驗、解析xml檔案流程
通過上面一系列的資源路徑檢索和檢查,最終進入了下面開始真正的載入資源的邏輯
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 有3種驗證方式,VALIDATION_AUTO、VALIDATION_DTD、VALIDATION_XSD,如果為第一種,會根據xml檔案的頭部是DTD型別還是XSD型別格式檔案解析
int validationMode = getValidationModeForResource(resource);
/*
java載入xml文件的基本流程,封裝了以下步驟:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.parse(inputSource)
*/
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
// 走到這一步,說明xml檔案的格式校驗,Java解析工作已完成,下面進一步解析xml檔案的具體內容,建立BeanDefinition。
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 解析文件建立BeanDefinition
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 建立文件讀取器DefaultBeanDefinitionDocumentReader進行解析文件
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
documentReader.setEnvironment(getEnvironment());
// 獲取當前已經存在的BeanDefinition數量,便於後面解析完後再一次獲取數量,兩次相減得到這次解析的數量
int countBefore = getRegistry().getBeanDefinitionCount();
// 具體註冊操作
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 獲取這次解析的BeanDefinition數量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 獲取文件根節點
Element root = doc.getDocumentElement();
// 真正註冊流程
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
// 判斷xml配置檔案中是否指定了環境
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
// 建立委託解析物件,在解析xml過程中,預設為Beans名稱空間
// 但實際可能會有多個名稱空間
// 如<aop:cofnig>等,或巢狀的如<bean class = "xxx" p:arg1="xxx">
// 對於非預設名稱空間的,會委託給BeanDefinitionParserDelegate進行解析。
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(this.readerContext, root, parent);
// 解析前處理,留給子類拓展
preProcessXml(root);
// 具體解析操作
parseBeanDefinitions(root, this.delegate);
// 解析後處理,留給子類拓展
postProcessXml(root);
this.delegate = parent;
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 如果根節點是預設名稱空間,直接走Beans名稱空間處理
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 {
// 交給委託物件處理自定義的名稱空間如aop,p等或使用者自定義的名稱空間
delegate.parseCustomElement(root);
}
}
// 預設的解析流程如下,如果節點是import,alias,beans,bead等型別,則呼叫相關的方法依次進行解析配置裡的屬性
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// import標籤
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// alias標籤
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// bean標籤,核心解析邏輯
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 巢狀beans標籤
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
// 對於自定義的名稱空間,會走以下流程,先根據元素獲取名稱空間地址,再根據地址找到相應的配置檔案找到對應的對映處理器,再交由相關的處理器進行解析
// 如<aop:xxx>則交給AopNamespaceHandler,<context:xxx>則交給ContextNamespaceHandler等。
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
至此,xml配置檔案解析成相應的BeanDefinition流程如上所示,對於具體的將xml標籤裡的一個個諸如class,id等元素解析為具體的BeanDefinition屬性,中間流程過於繁雜,但原理相對還是比較清晰的,這裡就不細述了。