Spring IOC 标签的解析
Posted wcj-java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IOC 标签的解析相关的知识,希望对你有一定的参考价值。
Spring IOC 标签的解析
上一篇文章说了Spring中的标签包括默认标签和自定义标签两种,本节继续来研究默认标签的解析,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); } }
默认标签的解析是在parseBeanDefinitions函数中进行的,该函数对4种不同的标签做处理
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 对import标签的处理 importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 对alias标签的处理 processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // 对bean标签的处理 processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 对beans标签的处理 doRegisterBeanDefinitions(ele); } }
4种标签的处理中,对bean标签的解析最为复杂也最为重要,所以以此标签分析为例。
1.xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="user" class="com.example.demo.entity.User"> <property name="name" value="zhangsanfeng"></property> <property name="age" value="14"></property> </bean> </beans>
首先进入函数processBeanDefinition(ele, delegate)
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. 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) 首先委托BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法进行元素的解析,返回BeanDefinitionHolder类型的实例bdHolder,这个实例bdHolder包含我们配置文件中配置的各种属性了。
(2) 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
(3) 解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
(4) 最后发出响应时间,通知相关的监听器,这个bean已经加载完成了。
2.解析BeanDefinition
进入BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); }
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // 解析id属性 String id = ele.getAttribute(ID_ATTRIBUTE); // 解析name属性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // 分割name属性 List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML ‘id‘ specified - using ‘" + beanName + "‘ as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { // 如果不存在beanNamename根据Spring中提供的命名规则为当前bean生成对应的beanName if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("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); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
该函数主要功能包括如下内容:
(1) 提取元素中的id以及name属性。
(2) 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
(3) 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。
(4) 将获取到的信息封装到BeanDefinitionHolder的实例中。
对Spring的解析犹如洋葱剥皮一样,一层一层的进行。接下来看步骤(2)中对标签其他属性的解析过程。
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; // 解析class属性 if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; // 解析parent属性 if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { // 创建用于承载属性的AbstractBeanDefinition类型的GenericBeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 硬编码解析默认bean的各种属性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // 提取description bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // 解析元数据 parseMetaElements(ele, bd); // 解析lookup-method属性 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // 解析replaced-method属性 parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // 解析构造函数参数 parseConstructorArgElements(ele, bd); // 解析property子元素 parsePropertyElements(ele, bd); // 解析qualifier子元素 parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
到这一步,bean标签的所有属性都做了处理,继续对属性的解析。
2.1创建用于属性承载的BeanDefinition
BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition,三种实现均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件<bean>元素标签在容器中的内部表示形式,BeanDefinition和<bean>中的属性是一一对应的。
Spring通过BeanDefinition将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegisty中。Spring容器的BeanDefinitionRegisty就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionRegisty中读取配置信息。
创建用于承载属性的实例在createBeanDefinition方法中
protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName) throws ClassNotFoundException { return BeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this.readerContext.getBeanClassLoader()); }
public static AbstractBeanDefinition createBeanDefinition( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); // parentName可能为空 bd.setParentName(parentName); if (className != null) { if (classLoader != null) { // 如果classLoader不为空,则使用已传入的classLoader统一虚拟机加载类对象,否则只记录className bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; }
2.2解析各种属性
创建了bean信息的承载实例后,便可以进行bean信息的各种属性解析了,进入parseBeanDefinitionAttributes方法
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { error("Old 1.x ‘singleton‘ attribute in use - upgrade to ‘scope‘ declaration", ele); } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); } else if (containingBean != null) { // Take default from containing bean in case of an inner bean definition. bd.setScope(containingBean.getScope()); } if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); if (DEFAULT_VALUE.equals(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); bd.setAutowireMode(getAutowireMode(autowire)); if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); } String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(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)); } if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } 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); } 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); } 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)); } return bd; }
这个方法完成了对所有bean属性的解析。
2.3解析子元素meta
在开始解析元数据的分析前,我们先回顾一下元数据meta属性的使用
<bean id="user" class="com.example.demo.entity.User"> <property name="name" value="zhangsanfeng"></property> <property name="age" value="14"></property> <meta key="test" value="aaaa"/> </bean>
对meta属性的解析代码如下:
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); } } }
2.4解析子元素lookup-method
同样,子元素lookup-method似乎并不是很常用,但是在某些时候它的确非常有用的,通常我们称它为获取器注入。
我们看看具体的应用
(1) 首先创建一个父类。
public class User1 { public void showMe() { System.out.println("i am user"); } }
(2) 创建其子类并覆盖showMe方法。
public class Teacher extends User1{ public void showMe() { System.out.println("i am teacher"); } }
(3) 创建调用方法。
public abstract class GetBeanTest { public void showMe() { this.getBean().showMe(); } public abstract User1 getBean(); }
(4) 创建测试方法。
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext bf = new ClassPathXmlApplicationContext("lookupTest.xml"); GetBeanTest test = (GetBeanTest)bf.getBean("getBeanTest"); test.showMe(); } }
(5) 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="getBeanTest" class="com.example.demo.entity.GetBeanTest"> <lookup-method name = "getBean" bean = "teacher"/> </bean> <bean id = "teacher" class = "com.example.demo.entity.Teacher"></bean> </beans>
例子写完了,可能你会觉得抽象方法还没有实现,怎么可能直接调用呢?答案是在配置文件中,动态的将teacher所代表的bean作为getBean的返回值。
以上是lookup-method子元素所提供的大致功能,然后我们来看它的属性提取源码。
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的子元素下且为<lookup-method时有效。 if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { Element ele = (Element) node; // 获取要修饰的方法 String methodName = ele.getAttribute(NAME_ATTRIBUTE); // 获取配置返回的bean String beanRef = ele.getAttribute(BEAN_ELEMENT); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(extractSource(ele)); overrides.addOverride(override); } } }
上面的代码很眼熟,似乎与parseMetaElements的代码大同小异,最大的区别就是在if判断中的节点名称在这里被修改为LOOKUP_METHOD_ELEMENT。还有,在数据存储上面通过使用LookupOverride类型的实体类类进行数据承载并记录在AbstractBeanDefinition中的MethodOverrides属性中。
以上是关于Spring IOC 标签的解析的主要内容,如果未能解决你的问题,请参考以下文章
[死磕 Spring 12/43] --- IOC 之解析 bean 标签:解析自定义标签
[死磕 Spring 8/43] --- IOC 之解析 bean 标签:开启解析进程
[死磕 Spring 13/43] --- IOC 之解析自定义标签 todo