Spring源码解析之BeanDefinition(二)
构造参数
spring允许我们在XML文件中可以配置一个bean的构造参数,这些属性最终会存放进BeanDefinition的constructorArgumentValues属性中:
<bean id="amy" class="org.example.beans.Person"> <constructor-arg name="name" value="Amy"></constructor-arg> <constructor-arg name="age" value="16"></constructor-arg> </bean> <bean id="john" class="org.example.beans.Person"> <constructor-arg index="0" value="John"></constructor-arg> <constructor-arg index="1" value="12"></constructor-arg> </bean>
测试用例:
@Test public void test04() { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml"); BeanDefinition amyBd = cc.getBeanFactory().getBeanDefinition("amy"); for (ConstructorArgumentValues.ValueHolder holder : amyBd.getConstructorArgumentValues().getGenericArgumentValues()) { System.out.println(holder.getName() + ":" + holder.getValue()); } BeanDefinition johnBd = cc.getBeanFactory().getBeanDefinition("john"); for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : johnBd.getConstructorArgumentValues().getIndexedArgumentValues().entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue().getValue()); } }
运行结果:
name:TypedStringValue: value [Amy], target type [null] age:TypedStringValue: value [16], target type [null] 0:TypedStringValue: value [John], target type [null] 1:TypedStringValue: value [12], target type [null]
工厂方法
spring除了可以调用类的构造方法生成bean,还可以调用bean的实例方法或者类的静态方法来生成bean。比如beanName为xiaomi这个bean就是通过调用同为bean对象的tvFactory的方法createMi来生成的,tcl这个bean则是TVFactory类的静态方法createTCL来生成bean。
<bean id="tvFactory" class="org.example.beans.TVFactory"></bean> <bean id="xiaomi" factory-bean="tvFactory" factory-method="createMi"></bean> <bean id="tcl" class="org.example.beans.TVFactory" factory-method="createTCL"></bean>
TVFactory.java
package org.example.beans; public class TVFactory { public TV createMi() { return new TV("小米"); } public static TV createTCL() { return new TV("TCL"); } } class TV { private final String name; public TV(String name) { this.name = name; } @Override public String toString() { return "TV{" + "name='" + name + '\'' + '}'; } }
测试用例:
@Test public void test05() { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml"); BeanDefinition xiaomi = cc.getBeanFactory().getBeanDefinition("xiaomi"); System.out.println("xiaomi beanClassName:" + xiaomi.getBeanClassName()); System.out.println("xiaomi factoryBeanName:" + xiaomi.getFactoryBeanName()); System.out.println("xiaomi factoryMethodName:" + xiaomi.getFactoryMethodName()); System.out.println("__________________"); BeanDefinition tcl = cc.getBeanFactory().getBeanDefinition("tcl"); System.out.println("tcl beanClassName:" + tcl.getBeanClassName()); System.out.println("tcl factoryMethodName:" + tcl.getFactoryMethodName()); }
运行结果:
xiaomi beanClassName:null xiaomi factoryBeanName:tvFactory xiaomi factoryMethodName:createMi __________________ tcl beanClassName:org.example.beans.TVFactory tcl factoryMethodName:createTCL
可以看到,xiaomi这个BeanDefinition并没有beanClassName,笔者之前说过,spring会根据BeanDefinition的beanClassName来生成bean,但这只是spring生成bean的手段之一,如果spring可以依靠其他方式生成bean,beanClassName也并非必须,正如XML配置中,spring可以调用tvFactory的createMi()这个方法来生成xiaomi这个bean,所以就不需要再xiaomi对应的BeanDefinition填充beanClassName。而tcl的BeanDefinition有beanClassName,是因为我们要借助TVFactory这个类,调用静态方法createTCL来生成bean。因此,一个BeanDefinition是否有beanClassName,关键还是看这个BeanDefinition是否有生成bean的需要,如果一个BeanDefinition只是为了让其他BeanDefinition继承它的属性,那就没必要有beanClassName,即便一个BeanDefinition有生成bean的需要,也要看它是否真的需要借助beanClassName来生成bean。
初始化和销毁方法
bean的初始化和销毁方法,分别以setInitMethodName(String initMethodName)、setDestroyMethodName(String destroyMethodName)来保存,以及用getInitMethodName()、getDestroyMethodName()来获取。
<bean id="a" class="org.example.beans.A" init-method="init" destroy-method="destroy"></bean>
测试用例:
@Test public void test06() { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml"); BeanDefinition beanDefinition = cc.getBeanFactory().getBeanDefinition("a"); System.out.println("a initMethodName:" + beanDefinition.getInitMethodName()); System.out.println("a destroyMethodName:" + beanDefinition.getDestroyMethodName()); cc.close(); }
运行结果:
LifeCycle init... a initMethodName:init a destroyMethodName:destroy LifeCycle destroy...
至此,我们了解完BeanDefinition大部分的方法。这里注意到,BeanDefinition有实现两个接口AttributeAccessor和BeanMetadataElement :
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { …… }
我们先来看AttributeAccessor接口,可以把AttributeAccessor想象成一个Map<String,Object>对象,事实上spring对AttributeAccessor的实现也是这样,AttributeAccessor主要用来保存BeanDefinition一些额外的属性,那么为什么spring需要给BeanDefinition保存一些额外属性呢?想象下你编写了一个Person类,类里有四个字段眼、耳、口、鼻,你想用Person类来描述人,但人这个概念实在太复杂,远非眼耳口鼻四个字段能够描述,如果你想拿Person来描述教师,教师可以有授课科目、授课年级、班级这几个字段,你想拿Person来描述马爸爸,那就更难描述了,马爸爸还有房产、股票、公司……等等这些字段。所以spring在保证大部分BeanDefinition的实现都是通用的情况下,如果有部分场景需要BeanDefinition保存一些额外的字段,就通过实现AttributeAccessor接口的实现来保存。
public interface AttributeAccessor { //设置属性名name和对应的属性value void setAttribute(String name, @Nullable Object value); //根据属性名获取属性 Object getAttribute(String name); //根据属性名移除属性 Object removeAttribute(String name); //检查是否包含属性名 boolean hasAttribute(String name); //获取所有属性名 String[] attributeNames(); }
我们定义两个类MyConfig和MyConfig2来初始化容器,MyConfig2只比MyConfig多一个@Configuration,然后我们在测试用例里打印这两个类BeanDefinition的class,以及attribute的name和value。
package org.example.config; import org.springframework.context.annotation.ComponentScan; @ComponentScan("org.example.service") public class MyConfig { } package org.example.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("org.example.service") public class MyConfig2 { }
测试用例:
@Test public void test07() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class, MyConfig2.class); System.out.println("___myConfigBd___"); BeanDefinition myConfigBd = ac.getBeanFactory().getBeanDefinition("myConfig"); System.out.println("myConfigBd class:" + myConfigBd.getClass()); for (String attributeName : myConfigBd.attributeNames()) { System.out.println(attributeName + ":" + myConfigBd.getAttribute(attributeName)); } System.out.println("___myConfig2Bd___"); BeanDefinition myConfig2Bd = ac.getBeanFactory().getBeanDefinition("myConfig2"); System.out.println("myConfig2Bd class:" + myConfig2Bd.getClass()); for (String attributeName : myConfig2Bd.attributeNames()) { System.out.println(attributeName + ":" + myConfig2Bd.getAttribute(attributeName)); } System.out.println("___a1ServiceBd___"); BeanDefinition a1ServiceBd = ac.getBeanFactory().getBeanDefinition("a1Service"); System.out.println("a1ServiceBd class:" + a1ServiceBd.getClass()); for (String attributeName : a1ServiceBd.attributeNames()) { System.out.println(attributeName + ":" + myConfigBd.getAttribute(attributeName)); } }
运行结果:
___myConfigBd___ myConfigBd class:class org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass:lite ___myConfig2Bd___ myConfig2Bd class:class org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass:full org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass:true ___a1ServiceBd___ a1ServiceBd class:class org.springframework.context.annotation.ScannedGenericBeanDefinition org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass:lite
可以看到MyConfig和MyConfig2的BeanDefinition实现都是AnnotatedGenericBeanDefinition,MyConfig的configurationClass属性为lite,MyConfig2的configurationClass属性为full,这里我们还打印了a1Service的BeanDefinition的实现是ScannedGenericBeanDefinition,而configurationClass属性值为lite。因此只要标注了@Configuration的类,spring都会在attribute中标记configurationClass为full。
BeanDefinition还实现了BeanMetadataElement接口,这个接口可以返回元信息,什么是元信息呢?一个对象的元信息是类,那么一个类的元信息是什么呢?是类文件的路径。BeanDefinition返回的元信息,即是类文件的路径,这里要注意,如果是传入给spring应用上下文初始化的配置类,返回的元信息为null,是因为配置类是我们主动传入的,spring不需要类文件路径也能拿到这个类,而像标记了@Component的类,spring在扫描时需要先拿到类文件的路径,在通过路径(classes\org\example\service\A1Service.class)推断时类的包名(org.example.service.A1Service)。
public interface BeanMetadataElement { /** * Return the configuration source {@code Object} for this metadata element * (may be {@code null}). */ @Nullable default Object getSource() { return null; } }
测试用例:
@Test public void test08() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class); BeanDefinition myConfigBd = ac.getBeanFactory().getBeanDefinition("myConfig"); System.out.println("myConfigBd source:" + myConfigBd.getSource()); BeanDefinition a1ServiceBd = ac.getBeanFactory().getBeanDefinition("a1Service"); System.out.println("a1Service source:" + a1ServiceBd.getSource()); System.out.println("________________"); ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml"); BeanDefinition amyBd = cc.getBeanFactory().getBeanDefinition("amy"); System.out.println("amy source:" + amyBd.getSource()); }
运行结果:
myConfigBd source:null a1Service source:file [D:\F\java_space\spring-source\spring-bd\target\classes\org\example\service\A1Service.class] ________________ amy source:null
根据运行结果我们可以看到,配置类是没有元信息的,用@Component标记的a1Service会返回元信息,而用XML声明的bean也没有元信息,因为在声明bean的时候,我们已经告诉spring类的包名,所以就不需要元信息。