public class Animal { private String name; private Integer age; //省略 getter/setter }然后有一个 Dog 类,如下:
public class Dog { private String name; private Integer age; private String color; //省略 getter/setter }小伙伴们注意,这里的 Dog 类并没有继承自 Animal 类,但是有两个跟 Animal 同名的属性。之所以这样设计是希望小伙伴们理解 BeanDefinition 中的 parentName 属性和 Java 中的继承并无关系,虽然大部分情况下我们用到 parentName 的时候,Java 中相关的类都是继承关系。现在,有一些通用的属性我想在 Animal 中进行配置,Dog 中特有的属性则在 Dog 中进行配置,我们来看下通过 XML 和 Java 分别该如何配置。
<bean id="animal" class="org.javaboy.demo.p2.Animal"> <property name="name" value="小黑"/> <property name="age" value="3"/> </bean> <bean class="org.javaboy.demo.p2.Dog" id="dog" parent="animal"> <property name="color" value="黑色"/> </bean>小伙伴们看到,首先我们配置 Animal,Animal 中有 name 和 age 两个属性,然后我又配置了 Dog Bean,并未之指定了 parent 为 animal,然后给 Dog 设置了 color 属性。
// 堆代码 duidaima.com AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); RootBeanDefinition pbd = new RootBeanDefinition(); MutablePropertyValues pValues = new MutablePropertyValues(); pValues.add("name", "小黄"); pbd.setBeanClass(Animal.class); pbd.setPropertyValues(pValues); GenericBeanDefinition cbd = new GenericBeanDefinition(); cbd.setBeanClass(Dog.class); cbd.setParentName("parent"); MutablePropertyValues cValues = new MutablePropertyValues(); cValues.add("name", "小强"); cbd.setPropertyValues(cValues); ctx.registerBeanDefinition("parent", pbd); ctx.registerBeanDefinition("child", cbd); ctx.refresh(); Dog child = (Dog) ctx.getBean("child"); System.out.println("child = " + child);这里我使用了 RootBeanDefinition 来做 parent,其实从名字上就能看出来 RootBeanDefinition 适合做 parent,并且 RootBeanDefinition 不能作为 child。强行设置运行时会抛出异常,RootBeanDefinition#setParentName 方法如下:
@Override public void setParentName(@Nullable String parentName) { if (parentName != null) { throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference"); } }MutablePropertyValues 是为相应的对象设置属性值。
protected RootBeanDefinition getMergedBeanDefinition( String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException { synchronized (this.mergedBeanDefinitions) { RootBeanDefinition mbd = null; RootBeanDefinition previous = null; // Check with full lock now in order to enforce the same merged instance. if (containingBd == null) { mbd = this.mergedBeanDefinitions.get(beanName); } if (mbd == null || mbd.stale) { previous = mbd; if (bd.getParentName() == null) { // Use copy of given root bean definition. if (bd instanceof RootBeanDefinition rootBeanDef) { mbd = rootBeanDef.cloneBeanDefinition(); } else { mbd = new RootBeanDefinition(bd); } } else { // Child bean definition: needs to be merged with parent. BeanDefinition pbd; try { String parentBeanName = transformedBeanName(bd.getParentName()); if (!beanName.equals(parentBeanName)) { pbd = getMergedBeanDefinition(parentBeanName); } else { if (getParentBeanFactory() instanceof ConfigurableBeanFactory parent) { pbd = parent.getMergedBeanDefinition(parentBeanName); } else { throw new NoSuchBeanDefinitionException(parentBeanName, "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + "': cannot be resolved without a ConfigurableBeanFactory parent"); } } } // Deep copy with overridden values. mbd = new RootBeanDefinition(pbd); mbd.overrideFrom(bd); } // Set default singleton scope, if not configured before. if (!StringUtils.hasLength(mbd.getScope())) { mbd.setScope(SCOPE_SINGLETON); } // A bean contained in a non-singleton bean cannot be a singleton itself. // Let's correct this on the fly here, since this might be the result of // parent-child merging for the outer bean, in which case the original inner bean // definition will not have inherited the merged outer bean's singleton status. if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { mbd.setScope(containingBd.getScope()); } // Cache the merged bean definition for the time being // (it might still get re-merged later on in order to pick up metadata changes) if (containingBd == null && isCacheBeanMetadata()) { this.mergedBeanDefinitions.put(beanName, mbd); } } if (previous != null) { copyRelevantMergedBeanDefinitionCaches(previous, mbd); } return mbd; } }这个方法看名字就是要获取一个合并之后的 BeanDefinition,就是将 child 中的属性和 parent 中的属性进行合并,然后返回,这个方法中有一个名为 mbd 的变量,这就是合并之后的结果。
1.首先会尝试从 mergedBeanDefinitions 变量中获取到合并之后的 BeanDefinition,mergedBeanDefinitions 相当于就是一个临时缓存,如果之前已经获取过了,那么获取成功之后就将之保存到 mergedBeanDefinitions 中,如果是第一次进入到该方法中,那么该变量中就没有我们需要的数据,所以会继续执行后面的步骤。
2.当第 1 步并未拿到 mbd 的时候,接下来继续判断 bd.getParentName() 是否为空,这个其实就是查看当前的 BeanDefinition 是否有设置 parentName,如果有设置,这里获取到的就不为 null,否则为 null。如果这里获取到的值为 null,那么就会根据当前传入的 BeanDefinition 生成一个 mbd,至于具体的生成方式:如果传入的 BeanDefinition 是 RootBeanDefinition 类型的,则调用 clone 方法去生成 mbd(本质上也是 new 一个新的 RootBeanDefinition),如果传入的 BeanDefinition 不是 RootBeanDefinition 类型的,则直接 new 一个新的 RootBeanDefinition,在 new 的过程中,会把传入的 BeanDefinition 上的属性都复制到新的 RootBeanDefinition 中。
3.如果 bd.getParentName() 不为空,则意味着存在 parent BeanDefinition,所以就要进行合并处理了,合并时候又有一个小细节,如果 parentBeanName 等于当前的 beanName,由于 Spring 在同一个容器中不允许存在同名的 bean,所以这就说明 parentBeanName 可能是父容器的 Bean,此时就要去父容器中去处理,当然最终调用到的还是当前方法,关于父子容器这一块,小伙伴们可以参考我之前的 Spring 中的父子容器是咋回事? 一文。如果 parentBeanName 不等于当前 beanName,那么现在就可以调用 getMergedBeanDefinition 方法去获取到 parentBeanDefinition 了,getMergedBeanDefinition 是当前方法的重载方法,该方法最终也会调用到当前方法,原因就在于 parentBeanDefinition 本身也可能存在 parentBeanDefinition。
4.有了 pbd 之后,接下来 new 一个 RootBeanDefinition,然后调用 overrideFrom 方法进行属性合并,合并的方式就是用传入的 BeanDefinition 中的属性去覆盖 pbd 中同名的属性。
概念和作用:Java 中的继承是一种面向对象的编程概念,用于定义类之间的父子关系,子类继承父类的属性和方法。而在 Spring 中,BeanDefinition 的 parentName 属性是用于定义 bean 之间的父子关系,一个派生 bean 可以继承另一个已定义的 bean 的配置。
语法和用法:在 Java 中,继承是通过使用关键字 extends 来实现的,子类通过继承父类来获得父类的属性和方法。而在 Spring 中,通过在 BeanDefinition 中配置 parentName 属性来指定一个 bean 的父 bean,从而继承父 bean 的配置。