Spring源码解析之BeanDefinition(一)
BeanDefinition
在spring中,BeanDefinition是十分重要的概念,可以说绝大部分的bean,都是从BeanDefinition产生的。那么BeanDefinition到底是什么呢?在回答这个问题前,我们先来思考Java是如何产生一个对象的?要产生一个Java对象,最基础是是要有一个class对象,好让Java知道如何描述这个对象,这个对象有多少个字段,这个对象有什么行为,需要为这个对象开辟多大的空间。同理,spring中的bean,我们可以选择是单例还是原型?是否懒加载?实现不同的回调方法让bean在不同的生命周期里进行回调……等等,描述一个bean的性质,同样需要一个对象,也就是BeanDefinition。
我们先来看下BeanDefinition接口的概览,从下面的图可以知道,BeanDefinition继承了AttributeAccessor和BeanMetadataElement两个接口,并且BeanDefinition有众多实现,比如:AnnotatedGenericBeanDefinition、ChildBeanDefinition、ConfigurationClassBeanDefinition、GenericBeanDefinition、RootBeanDefinition、ScannedGenericBeanDefinition……,这些不同的BeanDefinition分别在不同的场景下使用。
虽然上面存在众多接口和实现,但笔者会带大家逐一认识这些接口、以及接口的作用。首先还是从我们最重要的BeanDefinition接口讲起:
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { …… void setParentName(String parentName); String getParentName(); void setBeanClassName(String beanClassName); String getBeanClassName(); void setScope(String scope); String getScope(); void setLazyInit(boolean lazyInit); boolean isLazyInit(); void setDependsOn(String... dependsOn); String[] getDependsOn(); void setAutowireCandidate(boolean autowireCandidate); boolean isAutowireCandidate(); void setPrimary(boolean primary); boolean isPrimary(); void setFactoryBeanName(String factoryBeanName); String getFactoryBeanName(); void setFactoryMethodName(String factoryMethodName); String getFactoryMethodName(); ConstructorArgumentValues getConstructorArgumentValues(); default boolean hasConstructorArgumentValues() { return !getConstructorArgumentValues().isEmpty(); } MutablePropertyValues getPropertyValues(); default boolean hasPropertyValues() { return !getPropertyValues().isEmpty(); } void setInitMethodName(String initMethodName); String getInitMethodName(); void setDestroyMethodName(String destroyMethodName); String getDestroyMethodName(); void setDescription(String description); String getDescription(); boolean isSingleton(); boolean isPrototype(); boolean isAbstract(); …… }
上面是BeanDefinition的主要方法,我们先挑几个易于理解的说明:
- setBeanClassName(String beanClassName)和getBeanClassName()用于设置和获取一个class对象,spring可以根据BeanDefinition所指向的class对象,生成一个bean。那么,是不是说所有的BeanDefinition必须要设置一个class呢?毕竟spring是根据BeanDefinition所指向的class来生成bean的,答案是:否,BeanDefinition不一定要指定一个class。至于是为什么后面我们再详细解释。
- setScope(String scope)和getScope()用于获取一个bean的作用域,是单例(singleton)还是原型(prototype)。
- setLazyInit(boolean lazyInit)和isLazyInit()用于设置和判断一个bean是否是懒加载。
- setDependsOn(String... dependsOn)和getDependsOn()用于设置和获取bean在初始化前的依赖项。
- setDescription(String description)和getDescription()用来设置和描述这个bean,在程序运行时并不会有太大的影响。
- isSingleton()判断这个bean是否是单例。
- isPrototype()判断这个bean是否是原型。
AutowireCandidate和Primary
setAutowireCandidate(boolean autowireCandidate)和isAutowireCandidate()用于设置和判断bean是否自动参与候选,在AbstractBeanDefinition抽象类中默认为true。来看下面这段代码,A1Service和A2Service分别实现了AService,然后我们在TestAService注入AService。
package org.example.service; public interface AService { } package org.example.service; import org.springframework.stereotype.Component; @Component public class A1Service implements AService { } package org.example.service; import org.springframework.stereotype.Component; @Component public class A2Service implements AService { } package org.example.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class TestAService { @Autowired private AService service; public AService getService() { return service; } }
正常来说spring容器是无法启动的,因为在注入TestAService的AService时spring会检查到存在两个候选bean,从而报错。但我们可以通过修改A1Service或A2Service对应BeanDefinition的autowireCandidate属性,让容器只存在一个实现了AService候选的bean,从而完成注入。在修改BeanDefinition的属性之前,这里要介绍一个接口BeanFactoryPostProcessor:
public interface BeanFactoryPostProcessor { void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; }
我们一般是通过@ComponentScan注解或XML的<context:component-scan base-package="..."/>标签来配置bean的扫描路径,在spring生成bean的时候,spring会为扫描路径下的每个有需要生成bean类生成对应的BeanDefinition,然后根据这些BeanDefinition生成对应的bean。在根据BeanDefinition生成bean的中间,会执行我们所实现的BeanFactoryPostProcessor 接口,所以我们可以在这个接口修改BeanDefinition的autowireCandidate属性。我们调用beanFactory的getBeanDefinition(String beanName)方法获取A1Service对应的BeanDefinition,然后修改autowireCandidate为false,不参与bean的候选。
package org.example.service; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component; @Component public class AServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinition beanDefinition = beanFactory.getBeanDefinition("a1Service"); beanDefinition.setAutowireCandidate(false); } }
测试用例:
@Test public void test01() { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class); TestAService test1Service = ac.getBean(TestAService.class); System.out.println(test1Service.getService().getClass()); }
运行结果:
class org.example.service.A2Service
如果不设置两个BeanDefinition的autowireCandidate属性,我们也可以设置primary属性,setPrimary(boolean primary)和isPrimary()是用来设置和判断主要候选bean,在AbstractBeanDefinition类的实现中primary属性默认为false,如果存在多个候选bean的时候,spring会选择primary为true的bean进行注入。大家可以修改AServiceBeanFactoryPostProcessor的代码将A1Service对应的BeanDefinition的primary属性设置为true,这时候TestAService的service属性会以A1Service的bean进行注入,就不再是之前的A2Service了。
autowireCandidate和primary在XML中如下配置:
<bean id="..." class="..." autowire-candidate="true"/> <bean id="..." class="..." primary="true"/>
注:这里不能将A1Service和A2Service对应BeanDefinition的autowireCandidate属性同时改为false,也不能将primary同时改为true,否则spring容器找不到可参与候选的bean或者找到多个主要参与候选的bean,都会报错。
BeanDefinition的继承
我们之前说过,spring容器的大部分bean都是根据BeanDefinition来生成,但BeanDefinition又不一定要指定class,这是为什么呢?因为BeanDefinition可以继承,每个BeanDefinition都有属于自己的beanName,而子BeanDefinition通过setParentName(String parentName)和getParentName()来获取父BeanDefinition的beanName。来看下面这段配置,我们配置了两个bean,abPerson的abstract属性为true,代表这个bean不需要实例化,作用域scope是原型,同时bean的内部还指定了一个属性的值,age是18。第二个bean我们指定了Person类,指定了它的父BeanDefinition为abPerson,设置了一个属性name为sam。于是abPerson的age属性,作用域都得以继承。
<bean id="abPerson" abstract="true" scope="prototype"> <property name="age" value="18"></property> </bean> <bean id="sam" class="org.example.beans.Person" parent="abPerson"> <property name="name" value="Sam"></property> </bean>
注:abPerson不指定abstract属性也不会报错,但最好还是指定下,告诉spring这个不用根据这个BeanDefinition实例化bean。
Person.java
package org.example.beans; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
测试用例:
@Test public void test02() { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml"); Person sam = cc.getBean("sam", Person.class); System.out.println(sam); System.out.println(sam.getName()); System.out.println(sam.getAge()); System.out.println("_____________"); sam = cc.getBean("sam", Person.class); System.out.println(sam); System.out.println(sam.getName()); System.out.println(sam.getAge()); }
运行结果:
org.example.beans.Person@77f99a05 Sam 18 _____________ org.example.beans.Person@63440df3 Sam 18
可以看到,两次获取sam这个bean内存地址都不一样,age打印都是18,表明abPerson的作用域和age属性也得以继承。
我们还可以从spring容器中获取sam和abPerson对应的BeanDefinition,在获取abPerson的BeanDefinition时,我们通过samBeanDefinition的parentName来获取,并且我们打印这两个BeanDefinition的class:
@Test public void test03() { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("spring.xml"); BeanDefinition samBd = cc.getBeanFactory().getBeanDefinition("sam"); BeanDefinition parentBd = cc.getBeanFactory().getBeanDefinition(samBd.getParentName()); System.out.println("samBd parentName:" + samBd.getParentName()); System.out.println("samBd class:" + samBd.getClass()); System.out.println("parentBd class:" + parentBd.getClass()); }
运行结果:
samBd parentName:abPerson samBd class:class org.springframework.beans.factory.support.GenericBeanDefinition parentBd class:class org.springframework.beans.factory.support.GenericBeanDefinition
可以看到samBd正确打印了parentName,且通过XML配置的bean,其BeanDefinition的实现为GenericBeanDefinition。事实上,标记了@Component、@Configuration的类都会生成不同实现的BeanDefinition,后续还会介绍。
我们注意到,BeanDefinition有个 getPropertyValues()方法,这个方法是返回我们在<bean/>标签里配置的<property/>属性,在samBd的propertyValues属性中,只有一个元素,即为<name:Sam>,而我们在abPerson配置的<age:18>并不存在,但我们获取sam这个bean的age又是有值的,之所以能完成这样的功能是因为spring有一个合并BeanDefinition的概念,合并BeanDefinition就是逐级查找父BeanDefinition的属性(property、scope、lazy)进行合并,然后spring根据这个合并完的BeanDefinition生成我们所设定的bean。