Part3:上一次说到了Spring的DefaultBeanDefinitionDocumentReader类中的parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)方法,这个方法中引入了对两种不同标签的处理【当完成从配置文件到Document的转换并提取对应的root后,就开始了所有元素的解析,而在这一过程中便 开始了默认标签与自定义标签两种格式的区分
】:其实这个过程也就是Spring将bean从配置文件到加载到内存中的全过程
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
...
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对beans的处理,可以看出就是当Spring拿到一个元素时首先要做的是根据命名空间进行解析
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 ele) {
//对bean的处理。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
//如果不是默认的命名空间而是自定义命名空间,则使用parseCustomElement方法进行元素解析
else {
//对bean的处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
}
...
}
默认标签的解析是在parseDefaultElement函数中进行的,分别对4种不同标签(import、alias、bean和beans)做了不同的处理
。...
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
...
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);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse,对beans标签的处理
doRegisterBeanDefinitions(ele);
}
}
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//进入BeanDefinition Delegate类的parseBeanDefinitionElement方法中可以看到。在这个方法中其实就是解析bean标签中的id属性、name属性、
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//如果需要的话就对beanDefinition进行装饰,当Spring中的bean使用的是默认的标签配置,但是其中的子元素却使用了自定义的配置时这句代码便会起作用了。首先获取属性或者元素的命名空间,以此来判断该元素或者属性是否适用于自定义标签的解析条件,找出自定义类型所对应的NamespaceHandler并进行进一步解析
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//执行到这一步,配置文件中的东西该解析的也解析完了,该装饰的也装饰完了,此时得到的beanDefinition已经可以满足后续的使用要求了,唯一还剩下的工作就是注册bean,所以这一句代码或者说这一步就是来进行注册。在这个registerBeanDefinition方法中,使用beanName做唯一标识注册,解析的beanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
//当程序开发人员需要对注册BeanDefinition事件进行监听时可以通过注册监听器的方式并将处理逻辑写入监听器中,目前在Spring中并没有对此事件做任何逻辑处理。
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
等执行到processBeanDefinition函数中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry())这一句代码时,配置文件中的东西该解析的也解析完了,该装饰的也装饰完了,此时得到的beanDefinition已经可以满足后续的使用要求了,唯一还剩下的工作就是注册bean,所以这一句代码或者说这一步就是来进行注册。在这个registerBeanDefinition方法中,使用beanName做唯一标识注册,解析的beanDefinition都会被注册到BeanDefinitionRegistry类型的实例registry中
。看这个方法的源码可以知道,beanDefinition的注册分成了两部分:通过beanName的注册以及通过别名的注册
Spring中对于beanDefinition的注册基本上就是将beanDefinition直接放入map中就好了,使用beanName作为key
。只不过就算是个小学生干啥前也得做点预备东西干啥后也得检查检查,更何况Spring这一大家子呢,人家不得做做预备做做检查嘛。人家在代码中会对AbstractBeanDefinition属性中的methodOverrides校验,看methodOverrides是否与工厂方法并存或者methodOverrides对应方法根本不存在、有没有出现并发访问的情况【因为beanDefinitionMap是全局共享变量】、如果对应的BeanName已经注册且在配置中配置了bean不允许被覆盖,则抛出异常否则直接覆盖、加入map缓存以及清楚之前留下的对应的beanName的缓存BeanDefinition
:其中涉及到的这个 BeanDefinition接口【BeanDefinition是配置文件元素标签在容器中的内部表示形式,Spring通过BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefiniton注册到BeanDefinitonRegistry中,元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和中的属性是一一对应的。】
很重要,BeanDefinition接口在Spring中存在三种实现:RootBeanDefinition、ChildBean Definition以及GenericBeanDefinition【要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例,然后解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。】,三种实现均继承了AbstractBeanDefiniton,大部分的通用属性都保存在了AbstractBeanDefinition中
大部分的通用属性都保存在了AbstractBeanDefinition中
。】Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息
。创建了bean信息的承载实例GenericBeanDefinition类型的实例后,便可以进行bean信息的各种属性解析了,而parseBeanDefinitionAttributes方法是对element所有元素属性进行解析,咱们可以看看咱们经常接触的属性的解析源码,如下:
public class BeanDefinitionParserDelegate {
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
//解析singleton属性。scope与singleton两个属性只能指定其中之一,不可以同时出现,否则Spring将会报出异常
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
}
//解析scope属性
else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
//在嵌入beanDifinition情况下且没有单独指定scope属性则使用父类默认的属性
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
//解析abstract属性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
//解析lazy-init属性
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (isDefaultValue(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}//若没有设置或设置成其他字符都会被设置为false
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
//解析autowired属性
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
//解析depends-on属性
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
//解析autowired-candidate属性
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if (isDefaultValue(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);
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);
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;
}
}
Spring是通过parseConstructorArgElements函数来实现对于constructor-arg子元素的解析的,这个函数中的逻辑其实就是遍历所有子元素并提取所有的constructor-arg,然后在parseConstructorArgElement方法中进行解析,解析不就是把这个constructor-arg中的index属性、name属性、type属性等提取出来,说是这样说,但是其实具体的解析代码并不是简简单单的for循环就完了,而是在parseConstructorArgElement中,parseConstructorArgElement中主要的逻辑也就是下面的看有没有在配置中指定了index属性两方面
】:【先举个小例子,实现的功能就是对HelloBean自动寻找对应的构造函数,并在初始化的时候将设置的参数传入进去】...
<beans>
<bean id="helloBean" class='com. HelloBean">
' 0'>
<value>YWMvalue>
constructor-arg>
<constructor-arg index='1'>
<value>HHBvalue>
constructor-arg>
bean>
...
beans>
...
Object value = parsePropertyValue(ele, ...);
,这个parsePropertyValue方法中主要的如下面伪代码:@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
String elementName = (propertyName != null ?
" element for property '" + propertyName + "'" :
" element" );
// Should only have one child element: ref, value, list, etc.意思就是一个属性只能对应一种类型:ref、value、list等
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//对应description或者meta不处理,略过description或者meta
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
if (subElement != null) {
error(elementName + " must not contain more than one sub-element", ele);
}
else {
subElement = (Element) node;
}
}
}
//解析constructor-arg上的ref属性
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
//解析constructor-arg上的value属性
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
if (hasRefAttribute) {
//red属性的处理,使用RuntimeBeanReference封装对应的ref名称
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
ref.setSource(extractSource(ele));
return ref;
}
else if (hasValueAttribute) {
//value属性的处理,使用TypedStringValue封装
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
else if (subElement != null) {
return parsePropertySubElement(subElement, bd);
}
else {
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
AbstractBeanDefinition
:上面巴拉巴拉由BeanDefinition引出来了一大串,也该说说这个AbstractBeanDefinition了。大部分的通用属性都保存在了AbstractBeanDefinition中
,那都有哪些属性呢?看看源码呗。挑点常见的:public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
implements BeanDefinition, Cloneable {
...
//bean的作用范围,对应bean属性的scope
public static final String SCOPE_DEFAULT = "";
@Nullable
private String scope = SCOPE_DEFAULT;
//是否是抽象,对应bean属性的abstract
private boolean abstractFlag = false;
//是否延迟加载,对应bean属性的lazy-init
private Boolean lazyInit;
//自动注入模式,对应bean属性的autowire
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
private int autowireMode = AUTOWIRE_NO;
//用来表示一个bean的实例化依靠另一个bean先实例化,对应bean的属性depend-on
private String[] dependsOn;
//autowire-candidate属性设置为false,这样容器在查找自动装配对象时,将不考虑该bean,即它不会被考虑作为其他bean自动装配的候选者,但是该bean本身还是可以使用自动装配来注入其他bean的。
private boolean autowireCandidate = true;
//自动装配时当出现多个bean候选者时,将作为首选者,对应bean属性primary
private boolean primary = false;
//记录构造函数注入属性,对应bean属性constructor -arg
private ConstructorArgumentValues constructorArgumentValues;
//普通属性集合
private MutablePropertyValues propertyValues;
//初始化方法,对应bean属性init- method
private String[] initMethodNames;
//销毁方法,对应bean属性destory-method
private String[] destroyMethodNames;
//是否执行init- method以及destory-method,程序设置
private boolean enforceInitMethod = true;
private boolean enforceDestroyMethod = true;
//bean的描述信息
private String description;
...
}
而所有的这些名称都指向同一个bean
,在某些情况下提供别名非常有用,,比如为了让应用的每一个组件能更容易地对公共组件进行引用。我感觉就是给一帮不认识的人起个代号好记好认人而已。而 咱们又不可能在一开始就给所有的bean起好所有的别名,咱们肯定是想在这个地方或者这个时间段为别人或者别处的bean起个别名。所以 应运而生,在XML配置文件中可以用单独的 元素来完成bean别名的定义
protected void processAliasRegistration(Element ele) {
//获取beanName
String name = ele.getAttribute(NAME_ATTRIBUTE);
//获取alias
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
...
boolean valid = true;
if (valid) {
try {
//注册alias,将别名与beanName组成一对注册至registry中
getReaderContext().getRegistry().registerAlias(name, alias);
}
}
...
}
使用import的方式导入有模块配置文件,以后若有新模块的加入,那就可以简单修改这个文件了。这样大大简化了配置后期维护的复杂度,并使配置模块化,易于管理
。创建一个普通的用来接收配置文件的POJO
**一堆属性、一堆get/set方法创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义,其实就是先从element中解析并提取对应的元素然后将提取的数据放入到BeanDefinitionBuilder中,待到完成所有的bean解析后将所有bean统一注册到beanFactory中
。【XSD文件是XML DTD的替代者,使用XML Schema语言进行编写】Spring加载自定义标签的大致流程是遇到自定义标签然后就去Spring.handlers和Spring.schemas中去到默认位置是/META-INF/下找对应的handler和XSD,进而又找到对应的handler以及解析元素的Parser,从而完成了整个自定义元素的解析,也就是说自定义与Spring中默认的标准配置不同在于Spring将自定义标签解析的工作委托给了用户去实现
。@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
/**
* Parse a custom element (outside the default namespace).
* @param ele the element to parse
* @param containingBd the containing bean definition (if any),containingBd为父类bean,对顶层元素的解析应该设置为null
* @return the resulting bean definition
*/
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
//根据对应的bean获取对应的命名空间。标签的解析是从命名空间的提起开始的,无论是区分Spring中默认标签和自定义标签还是区分自定义标签中不同标签的处理器都是以标签所提供的命名空间为基础的。如何提取对应元素的命名空间其实并不需要我们亲自去实现,在org.w3c.dom.Node中已经提供了方法供我们直接调用
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
//根据命名空间找到对应的NamespaceHandler,相当于根据命名空间解析对应的处理器。在readerContext初始化的时候其属性namespaceHandlerResolver已经被初始化为了DefaultNamespaceHandlerResolver的实例,这句话是不是很熟悉。所以,这里调用的resolve方法其实调用的是DefaultNamespaceHandlerResolver类中的方法。在这个resolve方法中的主要逻辑也就是获取所有已经配置的handler映射,然后根据命名空间找到对应的信息,Spring才能根据映射关系找到匹配的处理器。当获取到自定义的NamespaceHandler之后就可以进行处理器初始化【当得到自定义命名空间处理后会马上执行namespaceHandler.init()来进行自定义Bean DefinitionParser的注册。注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器进行解析】并解析了
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处理器进行解析。得到了解析器以及要分析的元素后,Spring就可以将解析工作委托给自定义解析器去解析了。此时的handler已经被实例化成为了我们自定义的MyXxxHandler了,而MyXxxHandler也已经完成了初始化的工作。解析工作也就是首先是寻找元素对应的解析器,进而调用解析器中的parse方法进行进一步解析。
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
在这个处理过程中同样也是按照Spring中默认标签的处理方式进行,包括创建BeanDefinition以及进行相应默认属性的设置,对于这些工作Spring都默默地帮我们实现了,只是暴露出一些接口来供用户实现个性化的业务
。默认标签和自定义标签的解析就是Spring将bean从配置文件到加载到内存中的全过程,那咱们把bean从配置文件中按照人家配置文件编写者的要求或者说设计想法抠出来并加载到内存中之后,该干吗了,不就是加载bean,然后把bean扔到Spring容器中,按需分配呗,下篇Part1-4见
巨人的肩膀:
(很多很多好的文章,特此感谢google以及B站各位前辈)
Spring源码深度解析