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的主要方法,我們先挑幾個易於理解的說明:

  1. setBeanClassName(String beanClassName)和getBeanClassName()用於設置和獲取一個class對象,spring可以根據BeanDefinition所指向的class對象,生成一個bean。那么,是不是說所有的BeanDefinition必須要設置一個class呢?畢竟spring是根據BeanDefinition所指向的class來生成bean的,答案是:否,BeanDefinition不一定要指定一個class。至於是為什么后面我們再詳細解釋。
  2. setScope(String scope)和getScope()用於獲取一個bean的作用域,是單例(singleton)還是原型(prototype)。
  3. setLazyInit(boolean lazyInit)和isLazyInit()用於設置和判斷一個bean是否是懶加載。
  4. setDependsOn(String... dependsOn)和getDependsOn()用於設置和獲取bean在初始化前的依賴項。
  5. setDescription(String description)和getDescription()用來設置和描述這個bean,在程序運行時並不會有太大的影響。
  6. isSingleton()判斷這個bean是否是單例。
  7. 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。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM