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。