曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解

寫在前面的話

相關背景及資源:html

曹工說Spring Boot源碼系列開講了(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java

工程代碼地址 思惟導圖地址git

工程結構圖:web

正文

我這裏,先把org.springframework.beans.factory.config.BeanDefinition接口的方法再簡單列一下:spring

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按駝峯轉換後的名字。api

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

/** 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個服務,分屬不一樣的模塊:spring-boot

@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>

演示圖以下:

總結

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

相關文章
相關標籤/搜索