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类的包名,所以就不需要元信息。
浙公网安备 33010602011771号