Spring源码分析(四)标签的解析之默认标签的解析

2023年 9月 3日 28.6k 0

7f233cf5de98caadc304aee20a1a491.jpg

  • 本图:川西旅游中拍摄的(业余摄影)
  • 官网:Home
  • 参考书籍:Spring源码深度解析-郝佳编著-微信读书
  • 参考文章:Spring IoC之存储对象BeanDefinition
  • 上一篇文章我们介绍了Spring容器注册的流程有了一个基本的了解,但是还没有进行具体的解析,这里我感觉与Mybatis的源码很相似,首先都是XML格式转换成Docment对象,在进行标签节点的解析,下面我们来看看Spring中对标签节点的解析,这里我们分析默认标签的解析
    DefaultBeanDefinitionDocumentReader
    image.png
/**
	 * 注册bean定义,从给定的根{@code }元素开始。
	 * @param root
	 */
	@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
	protected void doRegisterBeanDefinitions(Element root) {
		// 获取父类的BeanDefinitionParserDelegate
		BeanDefinitionParserDelegate parent = this.delegate;
		// 创建BeanDefinitionParserDelegate
		this.delegate = createDelegate(getReaderContext(), root, parent);
		// 如果根元素的命名空间是默认的命名空间,且根元素的profile属性不为空
		if (this.delegate.isDefaultNamespace(root)) {
			// 获取profile属性值
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			// 如果profile属性值不为空
			if (StringUtils.hasText(profileSpec)) {
				// 将profile属性值按照逗号分隔符分割成数组
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// 如果当前环境不包含指定的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);
		// 解析BeanDefinition
		parseBeanDefinitions(root, this.delegate);
		// 解析后置处理
		postProcessXml(root);
		// 将父类的BeanDefinitionParserDelegate赋值给当前的BeanDefinitionParserDelegate
		this.delegate = parent;
	}

BeanDefinitionParserDelegate 用于解析XML bean定义,preProcessXml()方法在当前类中属于空方法,重点是 parseBeanDefinitions(root, this.delegate)。
DefaultBeanDefinitionDocumentReader

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		// 判断根节点使用的标签所对应的命名空间是否为Spring提供的默认命名空间,
		// 这里根节点为beans节点,该节点的命名空间通过其xmlns属性进行了定义
		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;
					// 使用默认的命名空间解析,这里的默认命名空间为http://www.springframework.org/schema/beans
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					// 使用自定义的命名空间解析
					else {

						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

image.png
image.png

  • 该方法用来遍历 root 节点下的子节点,比如说 root 节点为节点,则遍历它所包含的等节点
  • 如果根节点或者子节点采用默认命名空间的话,则调用 parseDefaultElement() 进行默认标签解析
  • 否则调用 delegate.parseCustomElement() 方法进行自定义解析
  • 下面我们先来分析默认标签的解析

image.png

一 默认标签解析

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)) {
			logger.info("parse bean element "+ ele.getNodeName());
			processBeanDefinition(ele, delegate);
		}
		// 如果标签名为beans
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
  • 方法的功能一目了然,分别是对四种不同的标签进行解析,分别是 import、alias、bean、beans。
  • 对应 bean 标签的解析是最核心的功能,对于 alias、import、beans 标签的解析都是基于 bean 标签解析的。

image.png

1.1 Bean标签的解析

  • Spring 中最复杂也是最重要的是 bean 标签的解析过程。
  • 在方法 parseDefaultElement() 中,如果遇到标签 为 bean 则调用 processBeanDefinition() 方法进行 bean 标签解析,通过代码我们可以发现关键方法processBeanDefinition()



	


/**
	 * 解析bean标签
	 * @param ele
	 * @param delegate
	 */
	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		// 解析bean标签,返回BeanDefinitionHolder对象
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		// 判空处理
		if (bdHolder != null) {
			// 解析bean标签的子标签
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				// 注册bean
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			// 解析成功后,进行监听器激活处理
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

我们来梳理一下上面的步骤,刚开始看的话很懵逼
(1)首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如class、name、id、alias之类的属性。
(2)当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
(3)解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了Bean DefinitionReaderUtils的registerBeanDefinition方法。
(4)最后发出响应事件,通知想关的监听器,这个bean已经加载完成了。
DefaultBeanDefinitionDocumentReader_processBeanDefinition.png

1.3.1 解析BeanDefinition

首先我们从元素解析及信息提取开始,进入BeanDefinition Delegate类的parseBeanDefinitionElement方法。
BeanDefinitionParserDelegate

/**
	 * 解析bean定义元素
	 * @param ele
	 * @param containingBean
	 * @return
	 */
	@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		// 解析id和name属性
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		List aliases = new ArrayList();
		// 解析name属性
		if (StringUtils.hasLength(nameAttr)) {
			// 解析name属性,可能是多个,用逗号分隔
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			// 将name属性添加到别名集合中
			aliases.addAll(Arrays.asList(nameArr));
		}
		// 获取beanName
		String beanName = id;
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
			beanName = aliases.remove(0);
			if (logger.isTraceEnabled()) {
				logger.trace("No XML 'id' specified - using '" + beanName +
						"' as bean name and " + aliases + " as aliases");
			}
		}
		if (containingBean == null) {
			// 检查beanName和aliases是否唯一
			checkNameUniqueness(beanName, aliases, ele);
		}
		// 解析属性,构造 AbstractBeanDefinition
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			//   如果 beanName 不存在,则根据条件构造一个 beanName
			if (!StringUtils.hasText(beanName)) {
				try {
                    //
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						beanName = this.readerContext.generateBeanName(beanDefinition);
						// Register an alias for the plain bean class name, if still possible,
						// if the generator returned the class name plus a suffix.
						// This is expected for Spring 1.2/2.0 backwards compatibility.
						String beanClassName = beanDefinition.getBeanClassName();
						if (beanClassName != null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
							aliases.add(beanClassName);
						}
					}
					if (logger.isTraceEnabled()) {
						logger.trace("Neither XML 'id' nor 'name' specified - " +
								"using generated bean name [" + beanName + "]");
					}
				}
				catch (Exception ex) {
					error(ex.getMessage(), ele);
					return null;
				}
			}
			String[] aliasesArray = StringUtils.toStringArray(aliases);
			//  /封装 BeanDefinitionHolder
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}

		return null;
	}

总结一下上面代码的步骤:
(1)提取元素中的id以及name属性,并检查名称唯一性
(2)进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中
(3)如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName
(4)将获取到的信息封装到BeanDefinitionHolder的实例中
image.pngimage.png
我们再来看看第二步解析其他属性进行封装,看看他是如何解析其他属性的
BeanDefinitionParserDelegate
image.png

@Nullable
	public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
		// 解析状态新增一个 BeanEntry
		this.parseState.push(new BeanEntry(beanName));
		// 解析class属性
		String className = null;
		if (ele.hasAttribute("class")) {
			className = ele.getAttribute("class").trim();
		}
		// 解析parent属性
		String parent = null;
		if (ele.hasAttribute("parent")) {
			parent = ele.getAttribute("parent");
		}
		// 下面是关键代码,我们仔细看看
		try {
			//创建用于承载属性的AbstractBeanDefinition类型的GenericBeanDefinition实例
			AbstractBeanDefinition bd = this.createBeanDefinition(className, parent);
			//硬编码解析bean的各种属性
			this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			//设置description属性
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, "description"));
			//解析元素
			this.parseMetaElements(ele, bd);
			//解析lookup-method属性
			this.parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
			//解析replace-method属性
			this.parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
			//解析构造函数的参数
			this.parseConstructorArgElements(ele, bd);
			//解析properties子元素
			this.parsePropertyElements(ele, bd);
			//解析qualifier子元素
			this.parseQualifierElements(ele, bd);
			bd.setResource(this.readerContext.getResource());
			bd.setSource(this.extractSource(ele));
			AbstractBeanDefinition var7 = bd;
			return var7;
		} catch (ClassNotFoundException var13) {
			this.error("Bean class [" + className + "] not found", ele, var13);
		} catch (NoClassDefFoundError var14) {
			this.error("Class that bean class [" + className + "] depends on not found", ele, var14);
		} catch (Throwable var15) {
			this.error("Unexpected failure during bean definition parsing", ele, var15);
		} finally {
			this.parseState.pop();
		}

		return null;
	}

image.png

1.3.1.1 AbstractBeanDefinition的创建

  • BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。
  • 三种实现均继承了AbstractBeanDefiniton,其中BeanDefinition是配置文件元素标签在容器中的内部表示形式
  • 元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和中的属性是一一对应的。
  • 其中RootBeanDefinition是最常用的实现类,它对应一般性的元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务类。
  • 在配置文件中可以定义父和子,父用RootBeanDefinition表示,而子用ChildBeanDefiniton表示,而没有父就使用RootBeanDefinition表示。
  • Spring通过BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefiniton注册到BeanDefinitonRegistry中。
  • Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinition Registry中读取配置信息。

image.png

  • 由此可知,要解析属性首先要创建用于承载属性的实例,也就是创建 GenericBeanDefinition类型的实例
  • 而代码createBeanDefinition(className,parent)的作用就是实现此功能。

BeanDefinitionParserDelegate

    protected AbstractBeanDefinition createBeanDefinition(String className, String parentName)
    throws ClassNotFoundException {
                  return BeanDefinitionReaderUtils.createBeanDefinition(
                          parentName, className, this.readerContext.getBeanClassLoader());
        }

BeanDefinitionReaderUtils

	public static AbstractBeanDefinition createBeanDefinition(
			@Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
		// Create bean definition instance.
		GenericBeanDefinition bd = new GenericBeanDefinition();
		// Set parent name, if any.
		bd.setParentName(parentName);
		if (className != null) {
			if (classLoader != null) {
				bd.setBeanClass(ClassUtils.forName(className, classLoader));
			}
			else {
				bd.setBeanClassName(className);
			}
		}
		return bd;
	}

image.png
上面很简单就是创建了一个容器对象,但是我这里有疑问?GenericBeanDefinition,RootBeanDefinition,ChildBeanDefinition有啥区别?
解答:
在Spring框架中,这三个类都是用于定义Bean的类。

  • GenericBeanDefinition是最通用的Bean定义类。它允许您定义Bean的类名、Bean的属性以及Bean之间的依赖关系。
  • RootBeanDefinition是GenericBeanDefinition的子类。除了GenericBeanDefinition定义的内容之外,它还可以定义一些特殊的属性,例如Bean的作用域、是否延迟初始化等。
  • ChildBeanDefinition同样是GenericBeanDefinition的子类。它用于定义一个已有Bean定义的子Bean定义。在这种情况下,ChildBeanDefinition继承其父Bean定义的所有属性,但可以通过重写属性来自定义特定的属性。

综上所述,GenericBeanDefinition是最通用的Bean定义类,RootBeanDefinition是GenericBeanDefinition的子类,用于定义更具体的Bean属性,而ChildBeanDefinition是用于定义已有Bean定义的子Bean定义。
上面我们有一个装容器的东西,下面就是解析不同的属性填充其中,这里的思想与MyBatis中对Mybatis.config.xml 的解析一致,下面我们来看看属性的解析

1.3.1.2 解析bean的各种属性

参考官网:核心技术
image.png
一个Spring IoC容器管理着一个或多个Bean。这些Bean是用你提供给容器的配置元数据创建的(例如,以XML 定义的形式)。
在容器本身中,这些Bean定义被表示为 BeanDefinition 对象,它包含(除其他信息外)以下元数据。

  • 一个全路径类名:通常,被定义的Bean的实际实现类。
  • Bean的行为配置元素,它说明了Bean在容器中的行为方式(scope、生命周期回调,等等)。
  • 对其他Bean的引用,这些Bean需要做它的工作。这些引用也被称为合作者或依赖。
  • 要在新创建的对象中设置的其他配置设置—例如,pool的大小限制或在管理连接池的Bean中使用的连接数。
1.3.1.2.1 Scope

image.png
当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
  • session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效
  • 其中比较常用的是singleton和prototype两种作用域,对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。
  • 容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为,如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。
  • 在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。
  • 如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。
  • 因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用,因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

BeanDefinitionParserDelegate

/**
	 * 解析bean的各种属性
	 * @param ele
	 * @param beanName
	 * @param containingBean
	 * @param bd
	 * @return
	 */
	public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
			@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
		// 有singleton属性,报错
		if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
			error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
		}
		// scope属性作用域:singleton、prototype、request、session、global-session
		else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
			bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
		}
		// 如果containingBean不为空,说明是内部bean,那么就从containingBean中获取scope属性
		else if (containingBean != null) {
			// Take default from containing bean in case of an inner bean definition.
			bd.setScope(containingBean.getScope());
		}
    }
1.3.1.2.2 Abstrat
  • abstract 英文含义是抽象的意思,在 java 中用来修饰类代表的意思是该类为抽象类,不能被实例化,而 Spring 中 bean 标签里的 abstract 的含义也是差不多,该属性的默认值是 false ,表示当前 bean 是一个抽象的 bean 不能被实例化,那么这就有问题了,既然一个 bean 不能被实例化,那么这个 bean 存在的意义是什么?
  • Spring 之所以这么设计,必然有其存在的道理,在说 abstract 属性之前,我们再来说 bean 标签中的另外一个属性 parent ,顾名思义,就是一个认爸爸的属性,用来表明当前的 bean 的老爸是谁,是继承它的属性。

BeanDefinitionParserDelegate

    	// 是否抽象类
		if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
			bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
		}
1.3.1.2.3 Lazy-init

延迟加载,ApplicationContext实现的默认行为就是在启动服务器时将所有singleton bean提前进行实例化(也就是依赖注入),如果你不想让一个singleton bean在ApplicationContext,实现初始化时被提前实例化,那么可以将bean设置为延时实例化。
BeanDefinitionParserDelegate

    	// 是否延迟初始化
		String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
		if (isDefaultValue(lazyInit)) {
			lazyInit = this.defaults.getLazyInit();
		}
		bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
1.3.1.2.4 Autowire
  • no (默认)不采用autowire机制,当我们需要使用依赖注入,只能用 ref
  • byName 通过属性的名称自动装配(注入)。Spring会在容器中查找名称与bean属性名称一致的bean,并自动注入到bean属性中。当然bean的属性需要有setter方法。例如:bean A有个属性master,master的setter方法就是setMaster,A设置了autowire=“byName”,那么Spring就会在容器中查找名为master的bean通过setMaster方法注入到A中。
  • byType 通过类型自动装配(注入)。Spring会在容器中查找类(Class)与bean属性类一致的bean,并自动注入到bean属性中,如果容器中包含多个这个类型的bean,Spring将抛出异常。如果没有找到这个类型的bean,那么注入动作将不会执行。
  • constructor 类似于byType,但是是通过构造函数的参数类型来匹配。假设bean A有构造函数A(B b, C c),那么Spring会在容器中查找类型为B和C的bean通过构造函数A(B b, C c)注入到A中。与byType一样,如果存在多个bean类型为B或者C,则会抛出异常。但时与byType不同的是,如果在容器中找不到匹配的类的bean,将抛出异常,因为Spring无法调用构造函数实例化这个bean。
  • default采用父级标签的配置,(即beans的default-autowire属性)

BeanDefinitionParserDelegate

	String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
		bd.setAutowireMode(getAutowireMode(autowire));
1.3.1.2.5 Depends-on

depend-on用来表示一个Bean的实例化依靠另一个Bean先实例化。如果在一个bean A上定义了depend-on B那么就表示:A 实例化前先实例化 B。
BeanDefinitionParserDelegate

if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
1.3.1.2.6 Autowire-candidates

beans元素是xml中定义bean的根元素,beans元素有个default-autowire-candidates属性,再来说一下bean元素的autowire-candidate属性,这个属性有3个可选值:
default:这个是默认值,autowire-candidate如果不设置,其值就是default
true:作为候选者
false:不作为候选者
BeanDefinitionParserDelegate

    	// 是否自动装配候选
		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));
		}
1.3.1.2.7 Primary

primary的值有true和false两个可以选择,默认为false。
当一个bean的primary设置为true,然后容器中有多个与该bean相同类型的其他bean,此时,当使用@Autowired想要注入一个这个类型的bean时,就不会因为容器中存在多个该类型的bean而出现异常。而是优先使用primary为true的bean。
BeanDefinitionParserDelegate

	if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
			bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
		}
1.3.1.2.8 Init-method

bean 配置文件属性 init-method 用于在bean初始化时指定执行方法,用来替代继承 InitializingBean接口。
BeanDefinitionParserDelegate

	// 是否初始化
		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);
		}
1.3.1.2.9 Destroy-method

销毁方法
BeanDefinitionParserDelegate

	// 是否销毁
		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);
		}
1.3.1.2.10 Factory-method

工厂方法
BeanDefinitionParserDelegate

if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
			bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
		}
		if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
			bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}

我们可以清楚地看到Spring完成了对所有bean属性的解析,这些属性中有很多是我们经常使用的,同时我相信也一定会有或多或少的属性是读者不熟悉或者是没有使用过的,有兴趣的读者可以查阅相关资料进一步了解每个属性

1.3.1.3 解析description标签

BeanDefinitionParserDelegate

	bd.setDescription(DomUtils.getChildElementValueByTagName(ele, "description"));

这个没啥说的

1.3.1.4 解析meta标签

在开始解析元数据的分析前,我们先回顾下元数据meta属性的使用。

        
                
        

BeanDefinitionParserDelegate

              public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
                //获取当前节点的所有子元素
                NodeList nl = ele.getChildNodes();
                for (int i = 0; i < nl.getLength(); i++) {
                      Node node = nl.item(i);
                      //提取meta
                      if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
                          Element metaElement = (Element) node;
                          String key = metaElement.getAttribute(KEY_ATTRIBUTE);
                          String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
                          //使用key、value构造BeanMetadataAttribute
                          BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
                          attribute.setSource(extractSource(metaElement));
                          //记录信息
                          attributeAccessor.addMetadataAttribute(attribute);
                      }
                  }
        }

1.3.1.5 解析lookup-method标签

同样,子元素lookup-method似乎并不是很常用,但是在某些时候它的确是非常有用的属性,通常我们称它为获取器注入,引用《Spring in Action》中的一句话:获取器注入是一种特殊的方法注入,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。
我们来个案例来理解它,首先我们定义一个类

package org.springframework.shu.Test;

/**
 * @description:
 * @author: shu
 * @createDate: 2023/4/13 20:25
 * @version: 1.0
 */
public class User {
	/**
	 * @description: show
	 */
	public void show(){
		System.out.println("I am a user");
	}
}

package org.springframework.shu.Test;

/**
 * @description:
 * @author: shu
 * @createDate: 2023/4/13 20:36
 * @version: 1.0
 */
public class Student extends User{
	/**
	 * @description: show
	 */
	@Override
	public void show() {
		System.out.println("I am a student");
	}
}

package org.springframework.shu.Test;

/**
 * @description:
 * @author: shu
 * @createDate: 2023/4/13 20:36
 * @version: 1.0
 */
public class Student extends User{
	/**
	 * @description: show
	 */
	@Override
	public void show() {
		System.out.println("I am a student");
	}
}

package org.springframework.shu.Test;

/**
 * @description:
 * @author: shu
 * @createDate: 2023/4/13 20:26
 * @version: 1.0
 */
public abstract class GetBeanTest {

	public void showMe(){
		this.getBean().show();
	}

	public abstract User getBean();
}

配置文件配置Xml




	
		
	

	



package org.springframework.shu.Test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @description:
 * @author: shu
 * @createDate: 2023/4/13 20:27
 * @version: 1.0
 */
public class BeanUserTest {
	public static void main(String[] args) {
		ApplicationContext bf = new ClassPathXmlApplicationContext("spring-config.xml");
		GetBeanTest test=(GetBeanTest) bf.getBean("getBeanTest");
		test.showMe();
	}
}

image.png到现在为止,除了配置文件外,整个测试方法就完成了,如果之前没有接触过获取器注入的读者们可能会有疑问:抽象方法还没有被实现,怎么可以直接调用呢?
在配置文件中,我们看到了源码解析中提到的lookup-method子元素,这个配置完成的功能是动态地将teacher所代表的bean作为getBean的返回值,我们可以替换配置文件
image.png
上面我们知道了什么样的效果,下面我们来看他的源码分析吧

public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//仅当在Spring默认bean的子元素下且为

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论