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类的包名,所以就不需要元信息。

 

posted @ 2020-10-27 08:31  北洛  阅读(339)  评论(0编辑  收藏  举报