通俗理解spring源碼(五)—— 解析及註冊BeanDefinitions 上節講到瞭如何獲取document,當把文件轉換為document後,接下來的提取及註冊bean就是我們的重頭戲。 protected int doLoadBeanDefinitions(InputSource input ...
通俗理解spring源碼(五)—— 解析及註冊BeanDefinitions
上節講到瞭如何獲取document,當把文件轉換為document後,接下來的提取及註冊bean就是我們的重頭戲。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //從資源文件轉換為document對象 Document doc = doLoadDocument(inputSource, resource); //解析document,並註冊beanDefiniton到工廠中 int count = registerBeanDefinitions(doc, resource); if (logger.isDebugEnabled()) { logger.debug("Loaded " + count + " bean definitions from " + resource); } return count; } 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); } }
在xmlBeanDefinitionReader的doLoadBeanDefinitions方法中,將document對象交給registerBeanDefinitions方法,返回本次載入的BeanDefinition個數。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //創建預設的documentReader,即DefaultBeanDefinitionDocumentReader,用來解析document BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //獲取註冊中心,記錄統計前的BeanDefinition載入個數 int countBefore = getRegistry().getBeanDefinitionCount(); //載入及註冊BeanDefinition documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //記錄本次載入的BeanDefinition個數 return getRegistry().getBeanDefinitionCount() - countBefore; }
這裡首先會createReaderContext(resource),代碼很簡單。
public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); }
這裡重點是將this對象,也就是當前的xmlBeanDefinitionReader對象放進去了,所以XmlReaderContext相當於一個上下文,方便數據的傳遞,類似於ServletContext,SecurityContext等。
然後將document和上下文對象交給documentReader.registerBeanDefinitions方法。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }
此處引用了之前的上下文,然後調用doRegisterBeanDefinitions,在spring中,很多以"do"開頭的方法名就是真正幹活的方法。
這裡獲取了document的根元素,進入documentReader.doRegisterBeanDefinitions方法
protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; //委托給delegate解析 this.delegate = createDelegate(getReaderContext(), root, parent); //判斷當前Beans節點是否是預設命名空間 if (this.delegate.isDefaultNamespace(root)) { //獲取beans節點的profile屬性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { //可以使用逗號或分號將當前beans標簽指定為多個profile類型 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. //判斷當前beans標簽的profile是否被激活 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("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;
可以先根據我的註釋看看這個方法到底是在幹啥,此處documentReader對象引用了一個BeanDefinitionParserDelegate,又將解析過程委托給了delegate 處理,在parseBeanDefinitions(root, this.delegate)方法中實現。
方法一開頭,便有一大段英文註釋,大致意思就是說,任何內嵌的beans標簽,將會導致該方法的遞歸調用,為了正確的傳播和保留<beans>標簽的default屬性,追蹤當前delegate(即父delegete,可能為null),每次都會創建一個新的delegate(即子delegate),引用父delegate,而這個子delegate下次又會作為父delegate。
腦瓜子是不是有點嗡嗡的???但是大概也能明白這裡就是為了處理beans標簽的default屬性的。
因為我們在配置xml文件的時候,是可以在根beans標簽中嵌套beans標簽的(雖然這樣的寫法很少,一般是寫在另外一個xml文件中,然後通過import標簽導入,但實際上效果是一樣的),類似這樣:
<?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.xsd" default-autowire="byType"> <beans profile="test" default-autowire="byType"> <bean id="user" class="cn.zxh.po.User" > <property name="name" value="張三"/> </bean> </beans> <beans profile="dev" default-autowire="constructor"> <bean id="user" class="cn.zxh.po.User"> <property name="name" value="李四"/> </bean> </beans> </beans>
理論上沒有棧溢出的情況下beans內部應該是可以無限嵌套beans的(不一定正確,還沒有試過),後面會講到每次解析到beans標簽都會進入到該方法中,所以該方法可能會遞歸調用,每次都會創建delegate,對應一個beans標簽,根據父Delegate來決定當前Delegate的預設屬性。
1、創建Delegate
BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent);
createDelegate點進入看看。
protected BeanDefinitionParserDelegate createDelegate( XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) { BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); //根據父delegate的預設屬性,初始化當前beans的預設屬性 delegate.initDefaults(root, parentDelegate); return delegate; }
首先會實例化一個BeanDefinitionParserDelegate對象,該對象引用了之前的上下文readerContext,並且還引用了一個DocumentDefaultsDefinition。
private final DocumentDefaultsDefinition defaults = new DocumentDefaultsDefinition(); public BeanDefinitionParserDelegate(XmlReaderContext readerContext) { Assert.notNull(readerContext, "XmlReaderContext must not be null"); this.readerContext = readerContext; }
DocumentDefaultsDefinition保存了根節點的預設配置屬性值,比如說default-lazyInit,default-autowire,default-initMethod,default-destroyMethod等,這幾種屬性應該都用過吧,如果該beans標簽下的bean沒有配置這些屬性,就會使用beans標簽的預設配置。
所以在這裡,delegate引用了一個DocumentDefaultsDefinition,將來在解析各個bean標簽時會起作用,然後調用initDefaults(root, parentDelegate),就是根據父delegate,初始化它自身的DefaultsDefinition。
在initDefaults方法中大概就是採用子配置優先的原則給DocumentDefaultsDefinition屬性賦值的,具體就不帶大家細看了,不然很容易從入門到放棄,反正經過幾層方法的調用,最終進入到這個方法中,這裡僅僅貼一下代碼。
protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) { String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE); if (isDefaultValue(lazyInit)) { // Potentially inherited from outer <beans> sections, otherwise falling back to false. lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE); } defaults.setLazyInit(lazyInit); String merge = root.getAttribute(DEFAULT_MERGE_ATTRIBUTE); if (isDefaultValue(merge)) { // Potentially inherited from outer <beans> sections, otherwise falling back to false. merge = (parentDefaults != null ? parentDefaults.getMerge() : FALSE_VALUE); } defaults.setMerge(merge); String autowire = root.getAttribute(DEFAULT_AUTOWIRE_ATTRIBUTE); if (isDefaultValue(autowire)) { // Potentially inherited from outer <beans> sections, otherwise falling back to 'no'. autowire = (parentDefaults != null ? parentDefaults.getAutowire() : AUTOWIRE_NO_VALUE); } defaults.setAutowire(autowire); if (root.hasAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)) { defaults.setAutowireCandidates(root.getAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)); } else if (parentDefaults != null) { defaults.setAutowireCandidates(parentDefaults.getAutowireCandidates()); } if (root.hasAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)) { defaults.setInitMethod(root.getAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)); } else if (parentDefaults != null) { defaults.setInitMethod(parentDefaults.getInitMethod()); } if (root.hasAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)) { defaults.setDestroyMethod(root.getAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)); } else if (parentDefaults != null) { defaults.setDestroyMethod(parentDefaults.getDestroyMethod()); } defaults.setSource(this.readerContext.extractSource(root)); }
2、判斷當前profile是否被激活
我們知道spring支持配置多個profile,可在beans標簽的profile屬性配置,然後在運行前動態指定spring.active.profile的。在java項目中,可以配置系統屬性System.setProperty("spring.profiles.active","test"),web項目中配置ServletContext上下文參數指定,springboot中也可以通過spring.active.profile指定。
//判斷當前Beans節點是否是預設命名空間 if (this.delegate.isDefaultNamespace(root)) { //獲取beans節點的profile屬性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { //可以使用逗號或分號將當前beans標簽指定為多個profile類型 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. //判斷當前beans標簽的profile是否被激活 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } }
知道了怎麼用,就容易多了,首先得到beans標簽的指定的profile數組,與指定的spring.active.profile對比,符合條件的話改beans才會被載入。
首先通過getReaderContext().getEnvironment(),獲取StandardEnvironment。這裡的getReaderContext()就是獲取的剛剛說的XmlReaderContext上下文,再從上下文中的得到XmlBeanDefinitionReader初始化時引用的StandardEnvironment。
下麵是XmlBeanDefinitionReader繼承自抽象父類的構造方法。
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; // Determine ResourceLoader to use. if (this.registry instanceof ResourceLoader) { this.resourceLoader = (ResourceLoader) this.registry; } else { this.resourceLoader = new PathMatchingResourcePatternResolver(); } // Inherit Environment if possible if (this.registry instanceof EnvironmentCapable) { this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); } else { this.environment = new StandardEnvironment(); } }
這裡的StandardEnvironment初始化會載入當前的系統變數和環境變數,是對系統變數和環境變數的封裝。在StandardEnvironmen繼承自父類的構造方法中,會調用customizePropertySources方法
private final MutablePropertySources propertySources = new MutablePropertySources() protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast( new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast( new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
該方法將當前系統變數和環境變數保存在其propertySources屬性中。
public class MutablePropertySources implements PropertySources { private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); /** * Create a new {@link MutablePropertySources} object. */ public MutablePropertySources() { } }
而MutablePropertySources 有一個List屬性,保存多個屬性來源。
也就是說,StandardEnvironment初始化完成時,就會載入系統變數和環境變數,然後這裡會調用acceptsProfiles(specifiedProfiles)方法判定當前beans標簽的profile是否應該被載入,遍歷給定的profiles數組,只要有一個被指定為spring.active.profile就返回true。
public boolean acceptsProfiles(String... profiles) { Assert.notEmpty(profiles, "Must specify at least one profile"); for (String profile : profiles) { if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') { if (!isProfileActive(profile.substring(1))) { return true; } } else if (isProfileActive(profile)) { return true; } } return false; } protected boolean isProfileActive(String profile) { validateProfile(profile); Set<String> currentActiveProfiles = doGetActiveProfiles(); return (currentActiveProfiles.contains(profile) || (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile))); }
重點是獲取spring.active.profile的方法。
protected Set<String> doGetActiveProfiles() { synchronized (this.activeProfiles) { if (this.activeProfiles.isEmpty()) { //從propertySources中獲取,key為spring.active.profile String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME); if (StringUtils.hasText(profiles)) { setActiveProfiles(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(profiles))); } } return this.activeProfiles; } }
3、解析根節點parseBeanDefinitions
經過了委托類的創建,spring.active.profile判斷返回true的beans標簽,才會進入到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)) { //預設標簽的解析 parseDefaultElement(ele, delegate); } else { //自定義標簽的解析 delegate.parseCustomElement(ele); } } } } else { //自定義標簽的解析 delegate.parseCustomElement(root); } }
看到這裡應該就一目瞭然了,判定root節點以及其子節點是否是預設標簽,預設標簽和自定義標簽有不同的解析方式,除了beans、bean、alias和import四種標簽外,都是自定義標簽,自定義標簽需要實現一些介面和配置。如果是預設標簽,進入parseDefaultElement方法。
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); } }
具體的解析過程下一章會講到,在這裡,看看解析beans標簽的時候,是不是又會調用doRegisterBeanDefinitions方法?還記得嗎?這正是對之前該方法會遞歸調用的解釋,再貼一遍代碼吧。
protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; //委托給delegate解析 this.delegate = createDelegate(getReaderContext(), root, parent); //判斷當前Beans節點是否是預設命名空間 if (this.delegate.isDefaultNamespace(root)) { //獲取beans節點的profile屬性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { //可以使用逗號或分號將當前beans標簽指定為多個profile類型 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. //判斷當前beans標簽的profile是否被激活 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("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; }
走的太遠,不要忘記為什麼出發!
參考:spring源碼深度解析