spring中Bean的創(chuàng)建流程

spring中的Bean是如何創(chuàng)建的?

帶著這個問題,從一個簡單的例子開始一探究竟咐吼。

  public class TestSpring {

    public static void main(String[] args){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = (UserService) context.getBean("userService");
        userService.test();
    }
}

這是一個基于JavaConfig方式來創(chuàng)建spring容器,spring容器創(chuàng)建完之后就開始嘗試獲取UserService對象商佑,接著調(diào)用userService的test方法锯茄,很簡單的一個例子。
先貼出UserService的代碼:

@Service
@LogAround(bizTag = "UserService")
public class UserService implements BeanNameAware,InitializingBean {

    @Autowired
    private OrderService orderService;

    private User adminUser;

    private String beanName;

    /**
     * 構(gòu)造函數(shù)
     */
    public UserService(){
        System.out.println("創(chuàng)建普通對象-構(gòu)造函數(shù)");
    }

    /**
     * BeanNameAware自動給UserService設置beanName
     * @param beanName
     */
    @Override
    public void setBeanName(String beanName) {
        System.out.println("aware自動設值-BeanNameAware");
        this.beanName = beanName;
    }

    /**
     * 初始化前
     */
    @PostConstruct
    public void loadAdminUser(){
        System.out.println("初始化前-PostConstruct");
        User user = new User();
        user.setId(1L);
        user.setName("admin");
        this.adminUser = user;
    }

    /**
     * 初始化
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始化-afterPropertiesSet");
    }

    public String getBeanName(){
        System.out.println(beanName);
        return beanName;
    }

    public User getAdminUser() {
        return adminUser;
    }
    public void test(){
        System.out.println("test");
    }
}

該如何入手分析UserService這個Bean是如何創(chuàng)建的呢茶没?
第一步肌幽,先想下spring幫我們創(chuàng)建的Bean和我們自己在程序中new UserService()有什么區(qū)別?可能區(qū)別有很多抓半,比如spring幫我們創(chuàng)建的bean是由spring管理的喂急,默認是單例的等。但是不管怎么樣spring要想創(chuàng)建Bean笛求,第一步肯定是要拿到構(gòu)造函數(shù)通過反射的方式在Spring容器中先new出這么一個對象廊移,我們暫且叫它普通對象糕簿,此時和我們自己在程序中new UserService()并沒有什么區(qū)別,就是創(chuàng)建一個普通的對象狡孔。
那第二歩會做什么呢懂诗?此時這個普通對象里面的屬性是沒有值的。bean和new出的對象還有一個區(qū)別就是spring會幫我們的bean中的屬性做注入(依賴注入)苗膝,這樣bean中的屬性才會有值殃恒,第二歩就是會對@Autowired注解修飾的屬性進行依賴入。
依賴注入之后辱揭,還會aware接口設值离唐,有初始化前,初始化和初始化后的動作界阁,這些步驟都做完之后才會生成一個真正的Bean而不是普通的對象侯繁。
實踐是檢驗真理的唯一標準,run一下上面的main方法泡躯,先看下bean創(chuàng)建的主要步驟后在逐個解析贮竟。


控制臺輸出

如上圖:

  1. 創(chuàng)建普通對象-構(gòu)造函數(shù)
  2. 對@Autowired注解的屬性進行賦值(依賴注入)
  3. aware自動設值-BeanNameAware
  4. 初始化前-PostConstruct
  5. 初始化-afterPropertiesSet
  6. 其實還有一個初始后的動作

Bean的創(chuàng)建流程

通過上面的分析和代碼的運行結(jié)果可以大致的看出spring中bean的創(chuàng)建有以下幾個主要流程:


創(chuàng)建流程
  1. 利用該類的構(gòu)造方法來實例化得到一個對象(但是如果一個類中有多個構(gòu)造方法, Spring則會進行選擇较剃,這個叫做推斷構(gòu)造方法咕别,下文在詳細介紹)
  2. 得到一個對象后,Spring會判斷該對象中是否存在被@Autowired注解了的屬 性写穴,把這些屬性找出來并由Spring進行賦值(依賴注入)
  3. 依賴注入后惰拱,Spring會判斷該對象是否實現(xiàn)了BeanNameAware接口、 BeanClassLoaderAware接口啊送、BeanFactoryAware接口偿短,如果實現(xiàn)了,就表示當前 對象必須實現(xiàn)該接口中所定義的setBeanName()馋没、setBeanClassLoader()昔逗、 setBeanFactory()方法,那Spring就會調(diào)用這些方法并傳入相應的參數(shù)
  4. Aware回調(diào)后篷朵,Spring會判斷該對象中是否存在某個方法被@PostConstruct注解 了勾怒,如果存在,Spring會調(diào)用當前對象的此方法(初始化前)声旺,上面的代碼中初始化了一個adminUser的數(shù)據(jù)笔链。
  5. 緊接著,Spring會判斷該對象是否實現(xiàn)了InitializingBean接口腮猖,如果實現(xiàn)了鉴扫,就 表示當前對象必須實現(xiàn)該接口中的afterPropertiesSet()方法,那Spring就會調(diào)用當
    前對象中的afterPropertiesSet()方法(初始化)
  6. 最后缚够,Spring會判斷當前對象需不需要進行AOP幔妨,如果不需要那么Bean就創(chuàng)建完 了鹦赎,如果需要進行AOP谍椅,則會進行動態(tài)代理并生成一個代理對象做為Bean(初始化 后)
    關(guān)于第6步控制臺沒辦法打出日志误堡,因為初始后涉及到spring的源碼操作,但是可以通過斷點看一下雏吭,提一句例子中UserService是被LogAspect切面切的锁施。


    AOP最終生成代理對象

    如果UserService類@LogAround(bizTag = "UserService")注解去掉,就說明UserService類不需要AOP杖们,那最終生成的Bean就是構(gòu)造函數(shù)所得到的對象悉抵。
    有AOP的話那么Bean就是UserService的代理類所實例化得到的對象。

這樣摘完,一個Bean就創(chuàng)建完了姥饰,創(chuàng)建完之后呢?
如果當前Bean是單例Bean孝治,那么會把該Bean對象存入一個Map<String, Object>列粪,Map的key為beanName,value為Bean對象谈飒。這樣下次getBean時就可 以直接從Map中拿到對應的Bean對象了岂座。(實際上,在Spring源碼中杭措,這個Map就 是單例池)
如果當前Bean是原型Bean费什,那么后續(xù)沒有其他動作,不會存入一個Map手素,下次 getBean時會再次執(zhí)行上述創(chuàng)建過程鸳址,得到一個新的Bean對象。

推斷構(gòu)造方法

至此泉懦,我們清楚了Bean的創(chuàng)建流程稿黍,那如果UserService中有多個構(gòu)造函數(shù)呢?第一步還能順利的創(chuàng)建一個普通對象嗎祠斧?這里面涉及到一個概念推斷構(gòu)造方法闻察,就是spring會去推斷用哪個構(gòu)造方法來創(chuàng)建出普通對象。
做幾個小實驗:

public UserService(){
        System.out.println("創(chuàng)建普通對象-構(gòu)造函數(shù)");
    }
    public UserService(OrderService orderService){
        System.out.println("創(chuàng)建普通對象-構(gòu)造函數(shù)有orderService參數(shù)");
    }
    public UserService(OrderService orderService,String beanName){
        System.out.println("創(chuàng)建普通對象-構(gòu)造函數(shù)有orderService和beanName參數(shù)");
    }

初始有這三個構(gòu)造方法琢锋,一個是無參構(gòu)造方法辕漂,一個是只有一個參數(shù),一個是有兩個參數(shù)吴超。
場景1:和初始情況一樣钉嘹,Bean能夠正常創(chuàng)建,并且打印"創(chuàng)建普通對象-構(gòu)造函數(shù)"
場景2:注釋掉第1個構(gòu)造方法鲸阻,創(chuàng)建Bean的時候就報異常了No default constructor found具體如下
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in file [D:\work\project\test\target\classes\com\test\service\UserService.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.test.service.UserService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.test.service.UserService.<init>()
場景3:注釋掉第1個和第3個構(gòu)造方法跋涣,能創(chuàng)建成功并打印"創(chuàng)建普通對象-構(gòu)造函數(shù)有orderService參數(shù)"
場景4:注釋掉第1個和第2個構(gòu)造方法缨睡,創(chuàng)建失敗,第二個參數(shù)String類型沒地方拿到值陈辱。
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' defined in file [D:\work\project\test\target\classes\com\test\service\UserService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
以上幾個場景總結(jié)下:

  1. 如果一個類只有一個構(gòu)造方法奖年,那么沒得選擇,只能用這個構(gòu)造方法沛贪。但是有參的構(gòu)造方法陋守,參數(shù)必須是spring的Bean這樣spring才能拿到進行賦值。
  2. 如果一個類存在多個構(gòu)造方法利赋,Spring不知道如何選擇水评,就會看是否有無參的構(gòu) 造方法,因為無參構(gòu)造方法本身表示了一種默認的構(gòu)造方法媚送。
  3. 如果都沒有構(gòu)造方法中燥,就是用默認的無參構(gòu)造方法來創(chuàng)建。
    其實多個構(gòu)造函數(shù)塘偎,也可以手動指定告訴spring用哪個構(gòu)造函數(shù)來創(chuàng)建疗涉,那就是加了@Autowired注解


    指定構(gòu)造方法

    我們通常說的依賴注入,
    屬性注入
    @Autowired
    private OrderService orderService;
    這邊的構(gòu)造方法
    public UserService(OrderService orderService){
    System.out.println("創(chuàng)建普通對象-構(gòu)造函數(shù)有orderService參數(shù)");
    }
    就是構(gòu)造函數(shù)注入

依賴注入流程

不管是屬性注入還是構(gòu)造方法注入式塌,能提供的信息只有兩個一個是類型OrderService 博敬,一個是名字orderService。那到底是根據(jù)類型注入的還是根據(jù)名字注入的呢峰尝?
假設根據(jù)名字注入的那剛好有一個其他類型的Bean名字也叫orderService那注入的時候豈不是會類型不匹配異常偏窝。比如說剛好有一個OrderBaseService類但是beanName也叫orderService,如果根據(jù)名字注入的話拿到的是OrderBaseService對象顯然類型不匹配武学。所以注入通常是先根據(jù)類型來查找的:

  1. 先根據(jù)入?yún)㈩愋驼壹劳绻徽业揭粋€不用管name,那就直接用來作為入?yún)?/li>
  2. 如果根據(jù)類型找到多個火窒,則再根據(jù)入?yún)⒚謥泶_定唯一
  3. 最終如果沒有找到硼补,則會報錯,無法創(chuàng)建當前Bean對象

代理對象生成

代理對象通常是AOP的時候會生成代理對象還有一種就是開啟事務的時候也會生成代理對象熏矿。否則的話Bean都是直接根據(jù)構(gòu)造函數(shù)生成對象在進行依賴注入和初始化等流程已骇。

AOP代理對象生成

AOP就是進行動態(tài)代理,在創(chuàng)建一個Bean的過程中票编,Spring在最后一步會去判斷當前正在 創(chuàng)建的這個Bean是不是需要進行AOP褪储,如果需要則會進行動態(tài)代理。

如何判斷當前Bean對象需不需要進行AOP:

  1. 找出所有的切面Bean
  2. 遍歷切面中的每個方法慧域,看是否寫了@Before鲤竹、@After等注解
  3. 如果寫了,則判斷所對應的Pointcut是否和當前Bean對象的類是否匹配
  4. 如果匹配則表示當前Bean對象有匹配的的Pointcut昔榴,表示需要進行AOP

利用cglib進行AOP的大致流程:

  1. 生成代理類UserServiceProxy辛藻,代理類繼承UserService
  2. 代理類中重寫了父類的方法碘橘,比如UserService中的test()方法
  3. 代理類中還會有一個target屬性,該屬性的值為被代理對象(也就是通過 UserService類推斷構(gòu)造方法實例化出來的對象吱肌,進行了依賴注入痘拆、初始化等步驟的 對象)
  4. 代理類中的test()方法被執(zhí)行時的邏輯如下:
    a. 執(zhí)行切面邏輯(@Before)
    b. 調(diào)用target.test()
    當我們從Spring容器得到UserService的Bean對象時,拿到的就是UserServiceProxy所生 成的對象岩榆,也就是代理對象错负。
    UserService代理對象.test()--->執(zhí)行切面邏輯--->target.test()坟瓢,注意target對象不是代理 對象勇边,而是被代理對象。

事務代理對象生成

Spring事務 當我們在某個方法上加了@Transactional注解后折联,就表示該方法在調(diào)用時會開啟Spring事 務粒褒,而這個方法所在的類所對應的Bean對象會是該類的代理對象。
Spring事務的代理對象執(zhí)行某個方法時的步驟:

  1. 判斷當前執(zhí)行的方法是否存在@Transactional注解
  2. 如果存在诚镰,則利用事務管理器(TransactionMananger)新建一個數(shù)據(jù)庫連接
  3. 修改數(shù)據(jù)庫連接的autocommit為false
  4. 執(zhí)行target.test()奕坟,執(zhí)行程序員所寫的業(yè)務邏輯代碼,也就是執(zhí)行sql
  5. 執(zhí)行完了之后如果沒有出現(xiàn)異常清笨,則提交月杉,否則回滾
    注意:Spring事務是否會失效的判斷標準:某個加了@Transactional注解的方法被調(diào)用時,要判 斷到底是不是直接被代理對象調(diào)用的抠艾,如果是則事務會生效苛萎,如果不是則失效。

總結(jié)

Spring中Bean的創(chuàng)建過程其實就是從一個普通對象蛻變成Bean的一個過程检号,蛻變包括依賴注入腌歉,初始化等步驟。最后在看下這個類是否有被AOP或開啟事務有的話會額外生成代理對象作為Bean齐苛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翘盖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子凹蜂,更是在濱河造成了極大的恐慌馍驯,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玛痊,死亡現(xiàn)場離奇詭異汰瘫,居然都是意外死亡,警方通過查閱死者的電腦和手機卿啡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門吟吝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人颈娜,你說我怎么就攤上這事剑逃≌阋耍” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵蛹磺,是天一觀的道長粟瞬。 經(jīng)常有香客問我,道長萤捆,這世上最難降的妖魔是什么裙品? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮俗或,結(jié)果婚禮上市怎,老公的妹妹穿的比我還像新娘。我一直安慰自己辛慰,他們只是感情好区匠,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帅腌,像睡著了一般驰弄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上速客,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天戚篙,我揣著相機與錄音,去河邊找鬼溺职。 笑死岔擂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的辅愿。 我是一名探鬼主播智亮,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼点待!你這毒婦竟也來了阔蛉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤癞埠,失蹤者是張志新(化名)和其女友劉穎状原,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苗踪,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡颠区,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了通铲。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毕莱。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朋截,到底是詐尸還是另有隱情蛹稍,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布部服,位于F島的核電站唆姐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏廓八。R本人自食惡果不足惜奉芦,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剧蹂。 院中可真熱鬧声功,春花似錦、人聲如沸国夜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽车吹。三九已至,卻和暖如春醋闭,著一層夾襖步出監(jiān)牢的瞬間窄驹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工证逻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乐埠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓囚企,卻偏偏與公主長得像丈咐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子龙宏,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容