1. 程式人生 > >曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解

曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解

寫在前面的話

相關背景及資源:

曹工說Spring Boot原始碼系列開講了(1)-- Bean Definition到底是什麼,附spring思維導圖分享

工程程式碼地址 思維導圖地址

工程結構圖:

正文

我這裡,先把org.springframework.beans.factory.config.BeanDefinition介面的方法再簡單列一下:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    // scope:單例
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    // scope:prototype
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
    // 角色:屬於應用程式的bean
    int ROLE_APPLICATION = 0;
    // 角色:支援?不甚瞭解,先跳過
    int ROLE_SUPPORT = 1;
    // 角色:屬於框架自身的bean
    int ROLE_INFRASTRUCTURE = 2;
    // parent bean的名字
    String getParentName();
    
    void setParentName(String parentName);
    // 核心屬性,此為bean的class名稱
    String getBeanClassName();

    void setBeanClassName(String beanClassName);
    // 核心屬性,本屬性獲取工廠bean的名稱,getFactoryMethodName獲取工廠方法的名稱,配合使用,生成         // 本bean
    String getFactoryBeanName();

    void setFactoryBeanName(String factoryBeanName);

    String getFactoryMethodName();

    void setFactoryMethodName(String factoryMethodName);
    //scope,bean是單例的,還是每次new一個(prototype),就靠它了
    String getScope();

    void setScope(String scope);
    // 懶bean?預設情況下,都是容器啟動時,初始化;如果設定了這個值,容器啟動時不會初始化,首次getBean    // 時才初始化
    boolean isLazyInit();

    void setLazyInit(boolean lazyInit);
    // 在本bean初始化之前,需要先初始化的bean;注意,這裡不是說本bean依賴的其他需要注入的bean
    String[] getDependsOn();

    void setDependsOn(String[] dependsOn);
    // 是否夠資格作為自動注入的候選bean。。。如果這裡返回false,那就連自動注入的資格都沒得
    boolean isAutowireCandidate();

    void setAutowireCandidate(boolean autowireCandidate);
    // 當作為依賴,要注入給某個bean時,當有多個候選bean時,本bean是否為頭號選手
    boolean isPrimary();

    void setPrimary(boolean primary);
    
    // 通過xml <bean>方式定義bean時,通過<constructor-arg>來定義構造器的引數,這裡即:獲取構造器引數
    ConstructorArgumentValues getConstructorArgumentValues();
    
    // 通過xml <bean>方式定義bean時,通過 <property name="testService" ref="testService"/> 這種方     式來注入依賴,這裡即:獲取property注入的引數值
    MutablePropertyValues getPropertyValues();
    // 是否單例
    boolean isSingleton();
    // 是否prototype
    boolean isPrototype();

    // 是否為抽象的,還記得<bean>方式定義的時候,可以這樣指定嗎?<bean id="testByPropertyController" class="org.springframework.simple.TestByPropertyController" abstract="true">
    boolean isAbstract();

    // 獲取角色
    int getRole();

    // 獲取描述
    String getDescription();
    
    String getResourceDescription();
    // 未知。。。
    BeanDefinition getOriginatingBeanDefinition();
}

beanName

雖然這個接口裡沒這個東西,但這個我要重點說下,預設規則是:beanClassName按駝峰轉換後的名字。

這裡面有個重點是,org.springframework.beans.factory.support.DefaultListableBeanFactory中,採用了下面的欄位來存bean和對應的BeanDefinition。

/** Map of bean definition objects, keyed by bean name */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);

這裡說了,key是beanName。

那大家想過沒,我如果同一個上下文中,有兩個beanName相同的BeanDefinition會怎樣呢?

之前spring cloud專案整合feign時,我們的程式碼是下面這樣的,即假設生產者提供了10個服務,分屬不同的模組:

@FeignClient(name = "SCM",path = "scm",configuration = { FeignConfig.class })
public interface ScmCenterService extends ScmFeignCenterService {
}
@FeignClient(name = "SCM",path = "scm",configuration = { FeignConfig.class })
public interface ScmClientConfigService extends ScmFeignClientConfigService {
}

我們這裡,就是按照不同模組,在多個接口裡來繼承feign api。

結果呢,啟動報錯了,就是因為beanName重複了,具體可以參考下面的連結:

【Feign】@FeignClient相同名字錯誤 The bean 'xxx.FeignClientSpecification', defined in null, could not be registered

最終解決這個問題,就是要加個配置,

spring:
  main:
    allow-bean-definition-overriding: true

這個配置,在spring之前的版本里,預設是true,結果在spring boot裡,預設改為false了。

我這邊通過下面的程式碼測試了一下:

  1. 當這個配置為true時

     public static void main(String[] args) throws Exception {
         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:application-context.xml"},false);
            // 這裡設為true,不設也可以,預設就是true
            context.setAllowBeanDefinitionOverriding(true);
         context.refresh();
    
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(String.class);
            beanFactory.registerBeanDefinition("testService", beanDefinition);

    console中有如下提示:

    資訊: Overriding bean definition for bean 'testService': replacing [Generic bean: class [org.springframework.simple.TestService]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [application-context.xml]] with [Generic bean: class [java.lang.String]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]

  2. 當這個配置為false時

    context.setAllowBeanDefinitionOverriding(false);

    會直接報錯:

    Invalid bean definition with name 'testService' defined in null: Cannot register bean definition [Generic bean: class [java.lang.String]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] for bean 'testService':

    //這裡說,早已存在xxx

    There is already [Generic bean: class [org.springframework.simple.TestService]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [application-context.xml]] bound.

    測試程式碼在:

    https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-xml-demo

scope

這個不多說了,預設為singleton,在容器內只會有一個bean。prototype,每次getBean的時候都會new一個物件,這個一般不會在啟動時去初始化,如果寫的有問題的話,啟動時不報錯,runtime的時候報執行時異常。

其他幾個scope,web相關的,先不多說。

parentName

指定parentBean的名稱,以前xml的時候可能會用,現在註解驅動了,基本很少用了。

beanClassName

核心屬性,bean的class型別,這裡說的是實際型別,而一般不是介面的名稱。比如,我們的註解一般是加在class上的,而不是加在介面上,對吧;即使加在介面上,那肯定也是動態代理技術,對吧,畢竟,bean是要以這個class的元資料來進行建立(一般通過反射)

factoryBeanName、factoryMethodName

如果本bean是通過其他工廠bean來建立,則這兩個欄位為對應的工廠bean的名稱,和對應的工廠方法的名稱

lazyInit

是否延遲初始化,取值為true、false、default。

Indicates whether or not this bean is to be lazily initialized.
If false, it will be instantiated on startup by bean factories
that perform eager initialization of singletons. The default is
"false".

簡單說:如果設了這個為true,則啟動時不初始化;否則在啟動時進行初始化,這也是spring官方推薦的,可以儘早發現問題。

dependsOn

通過這個屬性設定的bean,會保證先於本bean被初始化

The names of the beans that this bean depends on being initialized.
The bean factory will guarantee that these beans get initialized
before this bean.

autowireCandidate,布林

這個是個boolean值,表示是否可以被其他的bean使用@autowired的方式來注入。如果設定為false的話,那完了,沒資格被別人注入。

primary,布林

表示當有多個候選bean滿足@autowired要求時,其中primary被設定為true的,會被注入;否則會報二義性錯誤,即:程式期待注入一個,卻發現了很多個。

constructorArgumentValues

建構函式屬性值。我測試發現,通過下面的方式,這個欄位是用不上的:

public class TestController {

    TestService testService;
    
    @Autowired
    public TestController(TestService testService) {
        this.testService = testService;
    }
}

這個欄位,什麼時候有值呢,當採用下面的方式的時候,就會用這種:

public class TestController {

    TestService testService;

    public TestController(TestService testService) {
        this.testService = testService;
    }
}
    <bean name="testService" class="org.springframework.simple.TestService" />

    <bean id="testController" class="org.springframework.simple.TestController">
        <constructor-arg ref="testService"/>
    </bean>

演示圖如下:

propertyValues

property方式注入時的屬性值。在以下方式時,生效:

public class TestByPropertyController {

    TestService testService;
    
    //注意,這裡是set方法注入
    public void setTestService(TestService testService) {
        this.testService = testService;
    }
}
<bean name="testService" class="org.springframework.simple.TestService" />
  
<bean id="testByPropertyController" class="org.springframework.simple.TestByPropertyController"  >
        <property name="testService" ref="testService"/>
    </bean>

演示圖如下:

總結

今天內容大概到這裡,有問題請留