Spring中眼花繚亂的BeanDefinition

本篇博客主要參考:Spring官網(wǎng)閱讀(四)BeanDefinition(上)

碎碎念

在某課網(wǎng)看到很多培訓課程,有Java架構(gòu)師、大前端架構(gòu)師啸盏、大數(shù)據(jù)架構(gòu)師等等,銷量都不錯骑祟,但是數(shù)據(jù)結(jié)構(gòu)與算法回懦、計算機基礎這兩門課程的銷量少的可憐,特別是計算機基礎次企。因為學個XX架構(gòu)師怯晕,辛苦三個月,可以學習很多有用的組件缸棵,看很多源碼舟茶,知道高并發(fā)系統(tǒng)如何設計,雖然只是學個皮毛堵第,學個理論吧凉,不過薪資可能會有很大提升,但是學習計算機基礎踏志,可能一年下來阀捅,薪資可能也不會有提升,但是隨著時間的流逝针余,計算機基礎的重要性就凸顯出來了饲鄙。相信大家都知道一句話:勿在浮沙筑高臺,但是愿意在基礎上花時間涵紊、花精力的人還是不多(雖然我也是)傍妒。

引入主題

為什么要讀Spring源碼幔摸,有的人為了學習Spring中的先進思想摸柄,也有的人是為了更好的理解設計模式,當然也有很大一部分小伙伴是為了應付面試既忆,Spring Bean的生命周期啦驱负,Spring AOP的原理啦,Spring IoC的原理啦患雇,應付面試跃脊,看幾篇博客,對照著看看源碼苛吱,應該就沒什么問題了酪术,但是如果想真正的玩懂Spring,需要花的時間真的很多,需要你沉下心绘雁,從最基礎的看起橡疼,今天我們就來看看Spring中的基礎——BeanDefinition。

什么是BeanDefinition

image.png

Spring官網(wǎng)中有詳細的說明庐舟,我們來翻譯下:
SpringIoc容器管理一個Bean或多個Bean欣除,這些Bean通過我們提供給容器的配置元數(shù)據(jù)被創(chuàng)建出來(例如,在xml中<bean/>的定義)
在容器中挪略,這些Bean的定義用BeanDefinition對象來表示历帚,包含以下元數(shù)據(jù):

  • 全限定類名, 通常是Bean的實際實現(xiàn)類杠娱;
  • Bean行為配置元素挽牢,它們說明Bean在容器中的行為(作用域、生命周期回調(diào)等等)摊求;
  • Bean執(zhí)行工作所需要的的其他Bean的引用卓研,這些Bean也稱為協(xié)作者或依賴項;
  • 其他配置信息睹簇,例如奏赘,管理連接池的bean中,限制池的大小或者使用的連接的數(shù)量太惠。

Spring官網(wǎng)中對BeanDefinition的解釋還是很詳細的磨淌,但是不是那么通俗易懂,其實BeanDefinition是比較容易解釋的:BeanDefinition就是用來描述一個Bean或者BeanDefinition就是Bean的定義凿渊。

創(chuàng)建一個Java Bean梁只,大概是下面這個醬紫:


image.png

我們寫的Java文件,會編譯為Class文件埃脏,運行程序搪锣,類加載器會加載Class文件,放入JVM的方法區(qū)彩掐,我們就可以愉快的new對象了构舟。

創(chuàng)建一個Spring Bean,大概是下面這個醬紫:


image.png

我們寫的Java文件堵幽,會編譯為Class文件狗超,運行程序,類加載器會加載Class文件朴下,放入JVM的方法區(qū)努咐,這一步還是保持不變(當然這個也沒辦法變。殴胧。渗稍。)
下面就是Spring的事情了,Spring會解析我們的配置類(配置文件),假設現(xiàn)在只配置了A竿屹,解析后音五,Spring會把A的BeanDefinition放到一個map中去,隨后羔沙,由一個一個的BeanPostProcessor進行加工躺涝,最終把經(jīng)歷了完整的Spring生命周期的Bean放入了singleObjects。

BeanDefinition類圖鳥瞰

image.png

大家可以看到扼雏,Spring中BeanDefinition的類圖還是相當復雜的坚嗜,我剛開始讀Spring源碼的時候,覺得BeanDefinition應該是一個特別簡單的東西诗充,但是后面發(fā)覺并不是那么回事苍蔬。

下面我將對涉及到的類逐個進行解讀。

AttributeAccessor

AttributeAccessor是一個接口:

/**
 * Interface defining a generic contract for attaching and accessing metadata
 * to/from arbitrary objects.
 *
 * @author Rob Harrop
 * @since 2.0
 */
public interface AttributeAccessor {
    void setAttribute(String name, @Nullable Object value);
    Object getAttribute(String name);
    Object removeAttribute(String name);
    boolean hasAttribute(String name);
    String[] attributeNames();
}

我們來看下類上面的注釋:接口定義了通用的方法來保存或者讀取元數(shù)據(jù)蝴蜓。既然是接口碟绑,那么一定會有實現(xiàn)類,我們先把這個放一邊茎匠。

BeanMetadataElement

BeanMetadataElement也是一個接口格仲,里面只定義了一個方法:

/**
 * Interface to be implemented by bean metadata elements
 * that carry a configuration source object.
 *
 * @author Juergen Hoeller
 * @since 2.0
 */
public interface BeanMetadataElement {
    @Nullable
    Object getSource();

}

我們還是來看下類上的注釋:接口提供了一個方法來獲取Bean的源對象,這個源對象就是源文件诵冒,怎么樣凯肋,是不是不太好理解,沒關系汽馋,我們馬上寫個代碼來看下:

@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class BookService {
}
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBeanDefinition("bookService").getSource());
    }
}
file [D:\cycleinject\target\classes\com\codebear\springcycle\BookService.class]

怎么樣侮东,現(xiàn)在理解了把。

AttributeAccessorSupport

AttributeAccessorSupport類是一個抽象類豹芯,實現(xiàn)了AttributeAccessor接口悄雅,這個AttributeAccessor還記得吧,里面定義了通用的方法來保存或者讀取元數(shù)據(jù)的虛方法铁蹈,AttributeAccessorSupport便實現(xiàn)了這個虛方法宽闲,AttributeAccessorSupport定義了一個map容器,元數(shù)據(jù)就被保存在這個map里面木缝。

為什么要有這個map

初次讀Spring源碼便锨,看到這個map的時候,覺得有點奇怪我碟,元數(shù)據(jù)不應該是保存在BeanDefinition的beanClass、scope姚建、lazyInit這些字段里面嗎矫俺?這個map不是多次一舉嗎?

后面才知道,Spring是為了方便擴展厘托,不然BeanDefinition有新的特性友雳,就要新增字段,這是其一铅匹;其二押赊,如果程序員要擴展Spring,而BeanDefinition中定義的字段已經(jīng)無法滿足擴展了呢包斑?

那Spring自己有使用這個map嗎流礁,答案是有的,我們來看下罗丰,Spring在這個map中放了什么數(shù)據(jù):

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        BeanDefinition appConfig = context.getBeanDefinition("appConfig");
        for (String item : appConfig.attributeNames()) {
            System.out.println(item + ":" + appConfig.getAttribute(item));
        }
    }
org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass:full
org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass:true

可以看到神帅,Spring在里面放了兩個item:

  • 第一個item保存著這個配置類是否是一個Full配置類,關于Full配置類萌抵,我在先前的博客有簡單的介紹過:Spring中你可能不知道的事(二)
  • 第二個item找御,從名字上就可以知道和AOP相關。

BeanDefinition

BeanDefinition是一個接口绍填,繼承了AttributeAccessor霎桅、BeanMetadataElement,這兩個類上面已經(jīng)介紹過了讨永。

BeanDefinition定義了很多方法哆档,比如setBeanClassName、getBeanClassName住闯、setScope瓜浸、getScope、setLazyInit比原、isLazyInit等等插佛,這些方法一眼就知道是什么意思了,這里就不解釋了量窘。

BeanMetadataAttributeAccessor

BeanMetadataAttributeAccessor繼承了AttributeAccessorSupport雇寇,對保存或者讀取元數(shù)據(jù)的方法進行了進一步的封裝。

AbstractBeanDefinition

AbstractBeanDefinition是一個抽象類蚌铜,繼承了BeanMetadataAttributeAccessor锨侯,實現(xiàn)了BeanDefinition。

BeanDefinition實現(xiàn)了BeanDefinition定義的大部分虛方法冬殃,同時定義了很多常量和默認值囚痴。

AbstractBeanDefinition有三個子類,下面我們來看看這三個子類审葬。

ChildBeanDefinition

從Spring2.5開始深滚,ChildBeanDefinition已經(jīng)不再使用奕谭,取而代之的是GenericBeanDefinition。

GenericBeanDefinition

GenericBeanDefinition替代了ChildBeanDefinition痴荐,ChildBeanDefinition從字面上血柳,就可以看出有“子BeanDefinition”的意思,難道BeanDefinition還有“父子關系”嗎生兆?當然有难捌。

public class ChildService {
    private int id;
    private String name;

    public ChildService(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class ParentService {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ParentService(int id, String name) {
        this.id = id;
        this.name = name;
    }
}
 public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        GenericBeanDefinition parentBeanDefinition = new GenericBeanDefinition();
        parentBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
        parentBeanDefinition.setAttribute("name", "codebear");
        parentBeanDefinition.setAbstract(true);
        parentBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(1);
        parentBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue("CodeBear");

        GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
        childBeanDefinition.setParentName("parent");
        childBeanDefinition.setBeanClass(ChildService.class);

        context.registerBeanDefinition("parent", parentBeanDefinition);
        context.registerBeanDefinition("child", childBeanDefinition);

        context.refresh();

        BeanDefinition child = context.getBeanFactory().getMergedBeanDefinition("child");
        for (String s : child.attributeNames()) {
            System.out.println(s + ":" + child.getAttribute(s));
        }
        System.out.println("scope:" + child.getScope());

        System.out.println("-------------------");

        ChildService service = context.getBean(ChildService.class);
        System.out.println(service.getName());
        System.out.println(service.getId());
    }

運行結(jié)果:


name:codebear
scope:singleton
-------------------
CodeBear
1

來分析下代碼:

  1. 創(chuàng)建了GenericBeanDefinition對象parentBeanDefinition,設置為了單例模式鸦难,設置了Attribute根吁,聲明了構(gòu)造方法的兩個參數(shù)值;
  2. 創(chuàng)建了GenericBeanDefinition對象childBeanDefinition明刷,設置parentName為parent婴栽,BeanClass為ChildService;
  3. 注冊parentBeanDefinition辈末,beanName為parent愚争,childBeanDefinition,beanName為child挤聘;
  4. 刷新容器轰枝;
  5. 從mergedBeanDefinitions取出了child,mergedBeanDefinitions存放的是合并后的BeanDefinition组去;
  6. 打印出child的attribute鞍陨、scope、構(gòu)造方法的兩個參數(shù)值从隆。

大家可以看到诚撵,childBeanDefinition繼承了parentBeanDefinition。

如果沒有父子關系键闺,單獨作為BeanDefinition寿烟,也可以用GenericBeanDefinition來表示:

       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(AuthorService.class);
        genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        context.registerBeanDefinition("authorService", genericBeanDefinition);

        context.refresh();

        BeanDefinition mergedBeanDefinition = context.getBeanFactory().getMergedBeanDefinition("authorService");
        BeanDefinition beanDefinition = context.getBeanFactory().getMergedBeanDefinition("authorService");

        System.out.println(mergedBeanDefinition);
        System.out.println(beanDefinition);

運行結(jié)果:

Root bean: class [com.codebear.springcycle.AuthorService]; scope=prototype; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
Root bean: class [com.codebear.springcycle.AuthorService]; scope=prototype; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null

可以看到,當沒有父子關系辛燥,beanDefinition依舊會被保存在mergedBeanDefinitions中筛武,只是存儲的內(nèi)容和beanDefinitions中所存儲的內(nèi)容是一模一樣的。

GenericBeanDefinition總結(jié)

GenericBeanDefinition替代了低版本Spring的ChildBeanDefinition挎塌,GenericBeanDefinition比ChildBeanDefinition徘六、RootBeanDefinition更加靈活,既可以單獨作為BeanDefinition榴都,也可以作為父BeanDefinition待锈,還可以作為子GenericBeanDefinition。

RootBeanDefinition

在介紹GenericBeanDefinition的時候缭贡,寫了兩段代碼炉擅。

給第一個代碼打上斷點辉懒,觀察下mergedBeanDefinitions阳惹,會發(fā)現(xiàn)parentBeanDefinition和
childBeanDefinition在mergedBeanDefinitions都變?yōu)榱薘ootBeanDefinition:


image.png

給第二個代碼打上斷點谍失,也觀察下mergedBeanDefinitions,會發(fā)現(xiàn)authorService在mergedBeanDefinitions也變?yōu)榱薘ootBeanDefinition:


image.png

可以看到在mergedBeanDefinitions存放的都是RootBeanDefinition莹汤。

RootBeanDefinition也可以用來充當父BeanDefinition快鱼,就像下面的醬紫:

 public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        RootBeanDefinition genericBeanDefinition = new RootBeanDefinition();
        genericBeanDefinition.setBeanClass(ParentService.class);
        genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        context.registerBeanDefinition("parent", genericBeanDefinition);

        GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
        rootBeanDefinition.setBeanClass(ChildService.class);
        rootBeanDefinition.setParentName("parent");

        context.refresh();
    }

但是RootBeanDefinition不可以充當子BeanDefinition:

  public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        RootBeanDefinition genericBeanDefinition = new RootBeanDefinition();
        genericBeanDefinition.setBeanClass(ParentService.class);
        genericBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        context.registerBeanDefinition("parent", genericBeanDefinition);

        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
        rootBeanDefinition.setBeanClass(ChildService.class);
        rootBeanDefinition.setParentName("parent");

        context.refresh();
    }

運行結(jié)果:

Exception in thread "main" java.lang.IllegalArgumentException: Root bean cannot be changed into a child bean with parent reference
    at org.springframework.beans.factory.support.RootBeanDefinition.setParentName(RootBeanDefinition.java:260)
    at com.codebear.springcycle.Main.main(Main.java:20)

拋出了異常。

查詢源碼:

    @Override
    public void setParentName(@Nullable String parentName) {
        if (parentName != null) {
            throw new IllegalArgumentException("Root bean cannot be changed into a child bean with parent reference");
        }
    }

發(fā)現(xiàn)調(diào)用RootBeanDefinition的setParentName方法纲岭,直接拋出了異常抹竹。

RootBeanDefinition總結(jié)

RootBeanDefinition可以作為其他BeanDefinition的父BeanDefinition,也可以單獨作為BeanDefinition止潮,但是不能作為其他BeanDefinition的子BeanDefinition窃判,在mergedBeanDefinitions存儲的都是RootBeanDefinition。

ScannedGenericBeanDefinition

@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class AuthorService {
}
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBeanDefinition("authorService").getClass());
    }
}

運行結(jié)果:

class org.springframework.context.annotation.ScannedGenericBeanDefinition

通過注解掃描出來的Bean的BeanDefinition用ScannedGenericBeanDefinition來表示喇闸。

AnnotatedGenericBeanDefinition

 public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBeanDefinition("appConfig").getClass());
    }

運行結(jié)果:

class org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition

配置類的BeanDefinition用AnnotatedGenericBeanDefinition來表示袄琳。

ConfigurationClassBeanDefinition

public class AuthorService {
}
@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    public AuthorService authorService() {
        return null;
    }
}
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
 System.out.println(context.getBeanDefinition("authorService").getClass());
    }

運行結(jié)果:

  class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition

用@Bean聲明的Bean的BeanDefinition用ConfigurationClassBeanDefinition來表示。

是不是完全沒想到燃乍,一個BeanDefinition可以牽涉到這么多的內(nèi)容唆樊,這些內(nèi)容說沒用,確實沒什么用刻蟹;說有用逗旁,也有用。不明白這些內(nèi)容舆瘪,閱讀Spring源碼會比較懵逼片效,為什么會有那么多的BeanDefinition。這個時候英古,你就會卡殼淀衣,拼命的想弄懂這些BeanDefinition都是用來干嘛的,但是網(wǎng)上關于BeanDefinition的博客不算太多哺呜,比較好的博客就更少了舌缤,希望此篇文章可以填充這塊空白。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末某残,一起剝皮案震驚了整個濱河市国撵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌玻墅,老刑警劉巖介牙,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異澳厢,居然都是意外死亡环础,警方通過查閱死者的電腦和手機囚似,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來线得,“玉大人饶唤,你說我怎么就攤上這事」峁常” “怎么了募狂?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長角雷。 經(jīng)常有香客問我祸穷,道長,這世上最難降的妖魔是什么勺三? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任雷滚,我火速辦了婚禮,結(jié)果婚禮上吗坚,老公的妹妹穿的比我還像新娘祈远。我一直安慰自己,他們只是感情好刻蚯,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布绊含。 她就那樣靜靜地躺著,像睡著了一般炊汹。 火紅的嫁衣襯著肌膚如雪礁蔗。 梳的紋絲不亂的頭發(fā)上荧琼,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天托呕,我揣著相機與錄音演熟,去河邊找鬼。 笑死霸褒,一個胖子當著我的面吹牛伴找,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播废菱,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼技矮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了殊轴?” 一聲冷哼從身側(cè)響起衰倦,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旁理,沒想到半個月后樊零,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡孽文,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年驻襟,在試婚紗的時候發(fā)現(xiàn)自己被綠了夺艰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡沉衣,死狀恐怖郁副,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情厢蒜,我是刑警寧澤霞势,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布烹植,位于F島的核電站斑鸦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏草雕。R本人自食惡果不足惜巷屿,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望墩虹。 院中可真熱鬧嘱巾,春花似錦、人聲如沸诫钓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菌湃。三九已至问拘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惧所,已是汗流浹背骤坐。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留下愈,地道東北人纽绍。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像势似,于是被迫代替她去往敵國和親拌夏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345