為吸引蘋果投資電池廠 特斯拉擬秀大眾車款 Model 3

美國豪華電動車製造商特斯拉(Tesla)執行長 Elon Musk 透過 Twitter 暗示要在 10 月 9 日發表與字母「D」有關的神秘商品,究竟會有什麼樣的新品出爐,網路謠言為之沸騰。   Global Equities Research 分析師 Trip Chowdhry 發表研究報告指出,特斯拉可能會在 10 月 9 日發表三件大事,分別是「Model S」電動轎車將增添四輪驅動的新車款、新增半自動自動駕駛甫助系統(semi-autonomous driver-assistance system,簡稱 SADAS),還可能推出大眾車款「Model 3」。   barron`s.com、MarketWatch 3 日報導,Chowdhry 指出,特斯拉超大電池廠「GigaFactory」目前仍有三大潛在投資人──LG Chem、三洋電機(Sanyo)以及蘋果(Apple Inc.),倘若特斯拉能夠秀出需要 GigaFactory 供應電池的 Model 3,那麼這 3 家潛在投資者決定投入的機率也會大為上升。從以上跡象來看,特斯拉很可能會發表 Model 3。   另外,Chowdhry 也預期特斯拉到時候會推出具有四輪驅動系統的新版 Model S,而新增添的半自動自動駕駛甫助系統還將使用以色列自駕車相機防撞感測器開發商 Mobileye N.V. 製作的相機鏡頭。     相關閱讀:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

Day10-微信小程序實戰-交友小程序-創建friendList字段實現好友關係(添加好友功能)–內附代碼

回顧:之前我們進行了刪除的功能,以及對message消息的增刪,下面實現添加好友的功能

我們先在數據庫中,在message這個字段的list裏面,添加上測試號的id,就是模擬這個兩個測試號要加我主號的效果“

 

 

 

就可以達到這個效果了

 

下面我們正式開始實現

1、在removeList的wxml的昵稱結構處添加一個點擊事件handleAddFriend

2、在removeList.js中來實現這個點擊事件即可的,並且它也是要提示(讓用戶選擇確認的這種,所以就可以直接copy前面的刪除按鈕的代碼

 直接把hanleDelMessage函數裏面的:

   wx.showModal({
        title: '提示信息',
        content: '刪除消息',
        confirmText: "刪除",
        success: (res) => {
          if (res.confirm) {
            
          } else if (res.cancel) {
            console.log('用戶點擊取消')
          }
        }
      })
    }

也就是只用設計在點擊了確定之後的一些列操作即可了

3、因為要構建好友之間的關係,所以要在user裏面加一個friendlist字段才行了,並且這個字段的數據類型是數組的,因為好友肯定不只是一個

4、在數據庫中給每個人都添加一個friendList字段

 

 但是不要忘記了在原來的程序中 初始化 用戶的時候也要加上讀i這個字段的初始化才行的

打開user.js:

 

5、後面的事情大概的邏輯就是:同意了好友申請的話,這個用戶的id就會出現在這個用戶的friendList數組裡面了

所以就可以回到removeList.js文件中去了

6、通過在開頭的時候 設置 const _ = db.command 就可以讓這個文件有了運算的能力了

handleAddFriend(){
      wx.showModal({
        title: '提示信息',
        content: '申請好友',
        confirmText: "同意",
        success: (res) => {
          if (res.confirm) {
              db.collection('users').doc(app.userInfo._id).update({
                data:{
                    friendList : _.unshift(this.data.messageId)
                }
              }).then((res)=>{});
          } else if (res.cancel) {
            console.log('用戶點擊取消')
          }
        }
      })
    }
  }

 

 

在點擊消息之後,就可以選擇“確定”,點擊了之後,就可以在數據庫上面看到添加的用戶好友了

(以上就是第一步,把要申請的用戶的id給添加過來了)—-但是問題來了,我的主號裏面的friendList裏面有了這個申請人的id,但是這個申請人在我

同意了之後,它的friendList字段裏面也應該有我的主號的id才對的—也就是同時添加他們兩個的好友關係即可

 

 但是能不能通過上面的這種方式,把兩個變量之間的值作為交換就可以的,普通的數據庫裏面是可以這樣的。但是在小程序中式不可以這樣進行操作的

(因為在小程序裏面對數據庫的訪問式有權限的,在客戶端是組偶到這樣的操作

(也就是對其他的賬號進行更新操作的話在客戶端裏面是不允許的—同理也是要在服務端裏面來實現的))

–也就是要用雲函數來實現了

7、如果要像的客戶端中調用unshift一樣的話在服務端裏面進行調用的話,之前也搞過就是用一個模板字符串的寫法 用Esc下面的那個 “類單引號”的符號

來進行包裹就好了

 wx.cloud.callFunction({
            name : 'update',
            data : {
              collection : 'users',
              doc : this.data.messageId,
              data : {
                friendList: ` _.unshift('${app.userInfo._id}')`
              }
            }
          });

 

 出現的錯誤就是,我們把unshift也一起搞過來了,也就是我們傳過去的字符串沒有解析成功

 主要就是下面這裏寫錯了

 data : `{ friendList:  _.unshift('${app.userInfo._id}')}`

是把後面整個的串都用一個`   ….   `來圍住的(也就是後面整個的json對象都直接被這個符號扣住的)

 

修改了之後就成功了

 

8、新建一個removeMessage 函數,可以直接copy上面的handledelMessage方法裏面的:這個函數主要是為了點擊這個消息加好友的時候,可以選擇是

添加它為好友,就是在點擊了添加好友之後,就要把這個消息刪掉了,所以這兩個地方都用到了這個功能的話,就可以把這個功能封裝在removeMesage函數裏面,如何直接用this.removeMessage來調用即可了

效果就是:點擊了添加它為好友之後,這個申請為好友的消息就會被刪掉了

 

 

db.collection('message').where({
            userId : app.userInfo._id
          }).get().then((res)=>{
              // console.log(res);
              let list = res.data[0].list;
            console.log(list);
              list = list.filter((val , i)=>{
                  return val != this.data.messageId
              });
              // console.log(list);
              wx.cloud.callFunction({
                name : 'update',
                data : {
                  collection : 'message',
                  where : {
                    userId : app.userInfo._id
                  },
                  data : {
                    list : list
                  }
                }
              }).then((res)=>{
                this.triggerEvent('myevent',list) 
              });
          });

 

 整體邏輯:

1、在removeList.wxml 中的昵稱中添加一個點擊事件 

<movable-view bindtap="handleAddFriend" direction="horizontal" class="view">{{ userMessage.nickName }}</movable-view>

2、在removeList.js中隊這個點擊事件進行渲染

 handleAddFriend(){
      wx.showModal({
        title: '提示信息',
        content: '申請好友',
        confirmText: "同意",
        success: (res) => {
          if (res.confirm) {
              db.collection('users').doc(app.userInfo._id).update({
                data:{
                    friendList : _.unshift(this.data.messageId)
                }
              }).then((res)=>{});

          //其他用戶和我構成好友的關係,用到客戶端來實現(也就是用雲函數來實現)
          wx.cloud.callFunction({
            name : 'update',
            data : {
              collection : 'users',
              doc : this.data.messageId,
              data : `{ friendList:  _.unshift('${app.userInfo._id}')}`
            }
          }).then((res)=>{});
          this.removeMessage();
          } else if (res.cancel) {
            console.log('用戶點擊取消')

          }
        }
      })
    }

3、點擊了接受邀請之後,把這個申請好友的消息刪除(和之前實現刪除功能一樣,就可以直接把這個功能封裝到removeMessage這個函數裏面

removeMessage(){
      //也就是點擊了確定的話,就不提示這個了,而是刪除信息
      //  目前沒有直接更新的,智能是這個過程就變成了先查詢然後再更新
      db.collection('message').where({
        userId: app.userInfo._id
      }).get().then((res) => {
        // console.log(res);
        let list = res.data[0].list;
        console.log(list);
        list = list.filter((val, i) => {
          return val != this.data.messageId
        });
        // console.log(list);
        wx.cloud.callFunction({
          name: 'update',
          data: {
            collection: 'message',
            where: {
              userId: app.userInfo._id
            },
            data: {
              list: list
            }
          }
        }).then((res) => {
          this.triggerEvent('myevent', list)
        });
      });

注意:因為我們開通了一個friendList 給每一個用戶數據庫字段,所以在user.js初始化用戶數據庫的時候也要加上初始化這個friendList數組才行

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

「敢檢討核電海嘯對策就開除你喔!」 解析處處矛盾的福島核災判決

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

2020-為什麼換了工作

摘要

經歷了一個特殊的2020上半年,疫情出乎意料的持續了半年之久,還是沒有看到結束的趨勢。雖然外部環境很惡劣,還是做出了個人的重大選擇,換工作。期間糾結了很久,畢竟工作就是生活,換工作就是對未來的期待,對過去的總結,對自己的人生的深度思考。這裏回顧下當時的個人思考,供後續復盤參考。

當前的狀況

2020,本科畢業的第六年,不再像剛畢業那會,覺得換公司是輕而易舉的事,考慮的事情越來越多。
畢業五年開始就越發的焦慮,這是當時的心境
2019年春-當前的困境、
2019秋-走的太久忘記了為什麼出發

總結起來

  1. 在擁抱變化的過程中,沒有匹配上自己的個人目標
  2. 工作多年還依然在糾結擅長和喜歡

老馬說的員工想要離職無非兩個原因

  1. 錢沒給到位
  2. 心委屈了
    分別是物質和精神方面。算是高度概括了。細分起來就是三方面錢、人、事。

錢的計算相對比較複雜了。

  1. 時薪和月薪
    月薪20K 的996,和15K的965相比,時薪還要低不少。但是如何選擇,每個人的情況不一樣,選擇不一樣
  2. 月薪和總包
    互聯網公司的總包除了現金部分還包括股票或者期權。一般分4年歸屬,待滿兩年才能拿到一半。股票的價值不好估算,畢竟二級市場波動很大。對比股票,期權價值就更不好估算了。因為沒上市的公司估值的水分很大,另外不一定能兌現,畢竟上市沒那麼容易。但是機會和風險並存,創業公司會給很多期權來吸引(忽悠)人才加入。你想獲得高額的回報甚至是財務自由,就得冒很大的風險。

所以總收入: 現金 + 風險係數 * 股票/期權價值。 同時考慮你的時間比。

那對於當前的我來說,缺錢,但是不是缺工作跳槽的錢。怎麼說呢,就是生活基本物質得到了保障,但是更高的需求無法滿足,而這些多出來的需求靠換工作是滿足不了。所以錢不是重點考慮的。

互聯網的從業人員的個人素質相對比較高,加上工作專業度比較高,所以都比較簡單,沒那麼多亂七八糟的人情世故需要去處理。所以相處起來還比較愉快。

但是人與人之間是有磁場的,不是每個人都能和你對脾氣、遇到合適的領導,相近的同事也沒那麼容易,另外建立關係也需要時間。這個對於跳槽是減分項。

事包含兩部分,一部分是成就感,一部分個人成長機會。也就是通常說的借人成事、借事修人。

馬斯洛需求理論最高層級就是自我實現。對於互聯網人,每天除了睡覺,70%以上時間都給了工作,那自我實現肯定要在工作中實現。

那對於我個人而已,相對穩定的業務,確定性比較高,沒什麼發展前景的就沒什麼吸引力。希望是有挑戰性的工作,能把事情做成功,並且自己能夠在其中發揮重要的作用。

個人成長其實就是未來。個人成長不是一蹴而就的,短期內不易覺察。但是非常重要,互聯網更新很快,你沒跟上就被淘汰了。對於廣泛流傳的程序員成長路線,3年高工,7年架構,10年外賣。

雖然是調侃,但是也說明了幾點內容

  1. 更新迭代很快的互聯網對大齡人不是很友好,你必須要持續成長。恍惚中我就工作6年,在互聯網行業中都算一個老兵了。
  2. 互聯網人的職業發展與其他行業發展曲線不一樣。一個發展很快,新知識很多的領域,個體是永遠追不上行業的發展水平的。

總結來說,發展是當前跳槽的主要考慮因素。

跳槽的期望

有了換工作的想法,是對現狀的不滿,但是換一個環境並不意味着問題的解決。這也是為什麼很多人經常抱怨公司的不好,但是不挪窩的原因。我們不能寄希望於未知的事情。尤其是從18年底開始互聯網整體再走下坡路,就業環境並不好,加上今年疫情的原因,外部環境更加惡化。

但是大環境不好,不代表個體就不好。不確定性很多不代表沒有確定性的東西。人的一生就是在不斷選擇中度過的,我們必須要了解自己,抓住重點,匹配自己與環境。

那麼我的個人期望是怎麼樣的,新的工作能滿足我的期望嗎?

職業發展

之前迷茫過還要不要做程序員,能不能轉行到產品,諮詢,售前。現在不能說篤定了一直做技術,但是找到了一些發展規律。

  1. 不會再去做偏底層的技術
    以前做過大數據,BI,甚至3年前還拿到過大數據工程師的offer,現在也深入了解了運維、中間件相關的工作內容。徹底打消了偏技術側的技術開發。
  2. 解決問題為驅動
    問題為導向,離業務越近越好。通過技術來驅動業務,非技術手段來解決業務問題。

這個算是近兩年個人的一個不小的突破,排除了哪些事不會去做,哪些事要去嘗試探索。

行業

行業涉及面太廣了,現在toC不好做,巨頭林立把用戶時間都擠佔了。toB 更難了,工具效率型的,國內目前付費意願並不強,能幫助企業帶來收入的才能發展的下去。

個人目前對具體做哪個行業的並沒有那麼執着。更多的期望是能夠以某個點切入到某個行業,然後看到如何通過互聯網技術把問題解決了,形成閉環,把事情做成功了。這種成就感足以讓我興奮。因為這些年,在不同的公司做各種創業項目,都沒有做成功的,都因為各種原因死掉了。正因如此,才知道創業是如何的艱難與不易。

通過自我的剖析和明確當前職業發展目標,在2020年春夏之際,我成功換了工作。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

鯨鯊嘴巴裡驚見新物種 外型像蝦子

摘錄自2019年10月28日中央社報導

日本研究團隊在一隻鯨鯊的嘴巴裡發現外型像是蝦子的新物種,研究人員推測,因鯨鯊嘴巴內有新鮮海水和食物湧入,提供此物種很好的棲息地。

法新社報導,新物種屬鉤蝦亞目。鉤蝦亞目物種在高山或深海等環境中,都有堅強的生存能力。研究人員表示,新物種有長約5毫米的咖啡色身體,還有毛茸茸的腳,這能幫助牠捕食有機物質。富川光表示,這個物種之所以會選擇住在這麼出乎意料的地方,有一些很好的理由。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

傳小米電動車已進入量產 售價新台幣 20 萬有找

小米在智慧型手機及資訊領域攻城掠地後,顯然創辦人雷軍並未因此滿足,打從更早之前就一直有傳聞小米想要打造車子,而且還是高技術門檻的電動車,如今又有進一步消息指稱,小米電動車已經進入量產階段。   身為 Tesla 在中國市場的第一批車主,雷軍也曾多次親自拜訪 Tesla 的執行長 Elon Musk,交流許多關於電動車的未來、以及對智慧車輛的看法,近期中國網路上盛傳,內部代號「米斯拉」的小米牌電動車已經開始量產,甚至連其代工合作夥伴為比亞迪等資訊都被揭露。   消息也指出,米斯拉除了會是台電動車外,還會搭載小米自家的 MIUI 系統,將擁有豐富的智慧聯網功能;更驚人的是,這樣的一台高科技智慧電動車,售價僅要 39,999 人民幣,折合新台幣不過 20 萬有找,若此消息為真,小米將會在車界掀起另一波破壞巨浪。    

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

akka-typed(8) – CQRS讀寫分離模式

 前面介紹了事件源(EventSource)和集群(cluster),現在到了討論CQRS的時候了。CQRS即讀寫分離模式,由獨立的寫方程序和讀方程序組成,具體原理在以前的博客里介紹過了。akka-typed應該自然支持CQRS模式,最起碼本身提供了對寫方編程的支持,這點從EventSourcedBehavior 可以知道。akka-typed提供了新的EventSourcedBehavior-Actor,極大方便了對persistentActor的應用開發,但同時也給編程者造成了一些限制。如手工改變狀態會更困難了、EventSourcedBehavior不支持多層式的persist,也就是說通過persist某些特定的event然後在event-handler程序里進行狀態處理是不可能的了。我這裡有個例子,是個購物車應用:當完成支付后需要取個快照(snapshot),下面是這個snapshot的代碼:

 snapshotWhen { (state,evt,seqNr) => CommandHandler.takeSnapshot(state,evt,seqNr) } ... def takeSnapshot(state: Voucher, evt: Events.Action, lstSeqNr: Long)(implicit pid: PID) = { if (evt.isInstanceOf[Events.PaymentMade] || evt.isInstanceOf[Events.VoidVoucher.type] || evt.isInstanceOf[Events.SuspVoucher.type]) if (state.items.isEmpty) { log.step(s"#${state.header.num} taking snapshot at [$lstSeqNr] ...") true } else
        false
    else
      false

}

判斷event類型是沒有問題的,因為正是當前的事件,但另一個條件是購物車必須是清空了的。這個有點為難,因為這個狀態要依賴這幾個event運算的結果才能確定,也就是下一步,但確定結果又需要對購物車內容進行計算,好像是個死循環。在akka-classic里我們可以在判斷了event運算結果后,如果需要改變狀態就再persist一個特殊的event,然後在這個event的handler進行狀態處理。沒辦法,EventSourcedBehavior不支持多層persist,只有這樣做:

 

      case PaymentMade(acct, dpt, num, ref,amount) => ... writerInternal.lastVoucher = Voucher(vchs, vItems) endVoucher(Voucher(vchs,vItems),TXNTYPE.sales) Voucher(vchs.nextVoucher, List()) ...  

 

我只能先吧當前狀態保存下來、進行結單運算、然後清空購物車,這樣snapshot就可以順利進行了。

好了,akka的讀方編程是通過PersistentQuery實現的。reader的作用就是把event從數據庫讀出來后再恢復成具體的數據格式。我們從reader的調用了解一下這個應用里reader的實現細節:

 

    val readerShard = writerInternal.optSharding.get val readerRef = readerShard.entityRefFor(POSReader.EntityKey, s"$pid.shopId:$pid.posId") readerRef ! Messages.PerformRead(pid.shopid, pid.posid,writerInternal.lastVoucher.header.num,writerInternal.lastVoucher.header.opr,bseq,eseq,txntype,writerInternal.expurl,writerInternal.expacct,writerInternal.exppass)

可以看到這個reader是一個集群分片,sharding-entity。想法是每單完成購買后發個消息給一個entity、這個entity再完成reader功能后自動終止,立即釋放出佔用的資源。reader-actor的定義如下:

object POSReader extends LogSupport { val EntityKey: EntityTypeKey[Command] = EntityTypeKey[Command]("POSReader") def apply(nodeAddress: String, trace: Boolean): Behavior[Command] = { log.stepOn = trace implicit var pid: PID = PID("","") Behaviors.supervise( Behaviors.setup[Command] { ctx => Behaviors.withTimers { timer =>
          implicit val ec = ctx.executionContext Behaviors.receiveMessage { case PerformRead(shopid, posid, vchnum, opr, bseq, eseq, txntype, xurl, xacct, xpass) => pid = PID(shopid, posid) log.step(s"POSReader: PerformRead($shopid,$posid,$vchnum,$opr,$bseq,$eseq,$txntype,$xurl,$xacct,$xpass)")(PID(shopid, posid)) val futReadSaveNExport = for { txnitems <- ActionReader.readActions(ctx, vchnum, opr, bseq, eseq, trace, nodeAddress, shopid, posid, txntype) _ <- ExportTxns.exportTxns(xurl, xacct, xpass, vchnum, txntype == Events.TXNTYPE.suspend, { if(txntype == Events.TXNTYPE.voidall) txnitems.map (_.copy(txntype=Events.TXNTYPE.voidall)) else txnitems }, trace)(ctx.system.toClassic, pid) } yield () ctx.pipeToSelf(futReadSaveNExport) { case Success(_) => { timer.startSingleTimer(ReaderFinish(shopid, posid, vchnum), readInterval.seconds) StopReader } case Failure(err) => log.error(s"POSReader: Error: ${err.getMessage}") timer.startSingleTimer(ReaderFinish(shopid, posid, vchnum), readInterval.seconds) StopReader } Behaviors.same case StopReader => Behaviors.same case ReaderFinish(shopid, posid, vchnum) => Behaviors.stopped( () => log.step(s"POSReader: {$shopid,$posid} finish reading voucher#$vchnum and stopped")(PID(shopid, posid)) ) } } } ).onFailure(SupervisorStrategy.restart) }

reader就是一個普通的actor。值得注意的是讀方程序可能是一個龐大複雜的程序,肯定需要分割成多個模塊,所以我們可以按照流程順序進行模塊功能切分:這樣下面的模塊可能會需要上面模塊產生的結果才能繼續。記住,在actor中絕對避免阻塞線程,所有的模塊都返回Future, 然後用for-yield串起來。上面我們用了ctx.pipeToSelf 在Future運算完成后發送ReaderFinish消息給自己,通知自己停止。

在這個例子里我們把reader任務分成:

1、從數據庫讀取事件

2、事件重演一次產生狀態數據(購物車內容)

3、將形成的購物車內容作為交易單據項目存入數據庫

4、向用戶提供的restapi輸出交易數據

event讀取是通過cassandra-persistence-plugin實現的:

    val query = PersistenceQuery(classicSystem).readJournalFor[CassandraReadJournal](CassandraReadJournal.Identifier) // issue query to journal
    val source: Source[EventEnvelope, NotUsed] = query.currentEventsByPersistenceId(s"${pid.shopid}:${pid.posid}", startSeq, endSeq) // materialize stream, consuming events
    val readActions: Future[List[Any]] = source.runFold(List[Any]()) { (lstAny, evl) => evl.event :: lstAny }

這部分比較簡單:定義一個PersistenceQuery,用它產生一個Source,然後run這個Source獲取Future[List[Any]]。

重演事件產生交易數據:

    def buildVoucher(actions: List[Any]): List[TxnItem] = { log.step(s"POSReader: read actions: $actions") val (voidtxns,onlytxns) = actions.asInstanceOf[Seq[Action]].pickOut(_.isInstanceOf[Voided]) val listOfActions = onlytxns.reverse zip (LazyList from 1)   //zipWithIndex
      listOfActions.foreach { case (txn,idx) => txn.asInstanceOf[Action] match { case Voided(_) =>
          case ti@_ => curTxnItem = EventHandlers.buildTxnItem(ti.asInstanceOf[Action],vchState).copy(opr=cshr) if(voidtxns.exists(a => a.asInstanceOf[Voided].seq == idx)) { curTxnItem = curTxnItem.copy(txntype = TXNTYPE.voided, opr=cshr) log.step(s"POSReader: voided txnitem: $curTxnItem") } val vch = EventHandlers.updateState(ti.asInstanceOf[Action],vchState,vchItems,curTxnItem,true) vchState = vch.header vchItems = vch.txnItems log.step(s"POSReader: built txnitem: ${vchItems.txnitems.head}") } } log.step(s"POSReader: voucher built with state: $vchState, items: ${vchItems.txnitems}") vchItems.txnitems }

重演List[Event],產生了List[TxnItem]。

向數據庫里寫List[TxnItem]:

 

 def writeTxnsToDB(vchnum: Int, txntype: Int, bseq: Long, eseq: Long, txns: List[TxnItem])( implicit system: akka.actor.ActorSystem, session: CassandraSession, pid: PID): Future[Seq[TxnItem]] = ???

注意返回結果類型Future[Seq[TxnItem]]。我們用for-yield把這幾個動作串起來:

  val txnitems: Future[List[Events.TxnItem]] = for { lst1 <- readActions    //read list from Source
      lstTxns <- if (lst1.length < (endSeq -startSeq))    //if imcomplete list read again
 readActions else FastFuture.successful(lst1) items <- FastFuture.successful( buildVoucher(lstTxns) ) _ <- JournalTxns.writeTxnsToDB(vchnum,txntype,startSeq,endSeq,items) _ <- session.close(ec) } yield items

注意返回結果類型Future[Seq[TxnItem]]。我們用for-yield把這幾個動作串起來:

  val txnitems: Future[List[Events.TxnItem]] = for { lst1 <- readActions    //read list from Source
      lstTxns <- if (lst1.length < (endSeq -startSeq))    //if imcomplete list read again
 readActions else FastFuture.successful(lst1) items <- FastFuture.successful( buildVoucher(lstTxns) ) _ <- JournalTxns.writeTxnsToDB(vchnum,txntype,startSeq,endSeq,items) _ <- session.close(ec) } yield items

注意:這個for返回的Future[List[TxnItem]],是提供給restapi輸出功能的。在那裡List[TxnItem]會被轉換成json作為post的包嵌數據。

現在所有子任務的返回結果類型都是Future了。我們可以再用for來把它們串起來:

             val futReadSaveNExport = for { txnitems <- ActionReader.readActions(ctx, vchnum, opr, bseq, eseq, trace, nodeAddress, shopid, posid, txntype) _ <- ExportTxns.exportTxns(xurl, xacct, xpass, vchnum, txntype == Events.TXNTYPE.suspend, { if(txntype == Events.TXNTYPE.voidall) txnitems.map (_.copy(txntype=Events.TXNTYPE.voidall)) else txnitems }, trace)(ctx.system.toClassic, pid) } yield ()

說到EventSourcedBehavior,因為用了cassandra-plugin,忽然想起配置文件里新舊有很大區別。現在這個application.conf是這樣的: 

akka { loglevel = INFO actor { provider = cluster serialization-bindings { "com.datatech.pos.cloud.CborSerializable" = jackson-cbor } } remote { artery { canonical.hostname = "192.168.11.189" canonical.port = 0 } } cluster { seed-nodes = [ "akka://cloud-pos-server@192.168.11.189:2551"] sharding { passivate-idle-entity-after = 5 m } } # use Cassandra to store both snapshots and the events of the persistent actors persistence { journal.plugin = "akka.persistence.cassandra.journal" snapshot-store.plugin = "akka.persistence.cassandra.snapshot" } } akka.persistence.cassandra { # don't use autocreate in production
  journal.keyspace = "poc2g" journal.keyspace-autocreate = on journal.tables-autocreate = on snapshot.keyspace = "poc2g_snapshot" snapshot.keyspace-autocreate = on snapshot.tables-autocreate = on } datastax-java-driver { basic.contact-points = ["192.168.11.189:9042"] basic.load-balancing-policy.local-datacenter = "datacenter1" }

akka.persitence.cassandra段落里可以定義keyspace名稱,這樣新舊版本應用可以共用一個cassandra,同時在線。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

皮草業者毫無改善 芬蘭組織最新調查 「怪物狐狸」慘況依舊

環境資訊中心記者 鄒敏惠 報導

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

中國工業設計「奧斯卡獎」由陽光電源電動車控制器摘取

2014年12月12日,中國工業設計最高榮譽、被媲美為中國工業設計界之「奧斯卡獎」的中國創新設計紅星獎頒獎典禮在北京舉行。陽光電源電動車控制器產品從全球千餘家企業、6000多件參評作品中脫穎而出,獲得紅星獎,也是唯一獲得該獎項的電動車控制器類產品。  

  陽光電源自主研發的電動車控制器系列產品,結合了陽光電源近20年電力電子領域的技術研發和產業應用經驗,相比同類產品,該型號體積更小、重量更輕,適應範圍更廣也更環保。它採用模組化設計功能完善、能耗低、續駛里程長、可靠性高,目前成功應用於合肥、大連、舒城等多個城市電動公車。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

Spring源碼系列(二)–bean組件的源碼分析

簡介

spring-bean 組件是 Spring IoC 的核心,我們可以使用它的 beanFactory 來獲取所需的對象,對象的實例化、屬性裝配和初始化等都可以交給 spring 來管理。 本文將從DefaultListableBeanFactory.getBean(Class)方法開始分析獲取 bean 的過程,主要內容如下,由於篇幅較長,可以根據需要選擇閱讀:

  1. beanFactory 的設計
  2. 多個 beanName 的處理
  3. 獲取單例 bean
  4. 創建單例 bean
  5. bean 的實例化
  6. bean 的屬性裝配
  7. bean 的初始化(省略)

spring-bean 的源碼比較多,有些不影響整體分析思路的代碼會被省略掉(代碼中會註明),另外,想要分析所有的代碼可能不大現實,所以,針對部分內容,我會點到為止,例如,本文只分析單例 bean 而不分析多例 bean。

前篇回顧

上篇博客Spring源碼系列(一)–詳細介紹bean組件介紹了 bean 組件的一些重要理論概念,並通過例子演示如何使用 bean 組件。這裏回顧下,這幾個概念非常重要,是 bean 組件的理論基礎:

  1. 實例化、屬性裝配和初始化的概念。 實例化指創建出一個新的對象;屬性裝配指給對象的成員屬性賦值; 初始化指調用對象的初始化方法。
  2. 什麼是 bean:某個類的實例或描述對象,被註冊到了 Spring IoC 容器,這時通過 Spring IoC 容器獲取的這個類的對象就是 bean。
  3. 什麼是 beanFactory:一個工廠,用於註冊 bean 和獲取 bean。
  4. 什麼是 beanDefinition:一個描述對象,用來描述 bean 的實例化、屬性裝配、初始化等信息。

beanFactory的設計

從客戶端來看,一個完整的 beanFactory 工廠包含以下基本功能:

  1. 註冊別名。對應下圖的AliasRegistry接口。
  2. 註冊單例對象。對應下圖的SingletonBeanRegistry接口。
  3. 註冊BeanDefinition對象。對應下圖的BeanDefinitionRegistry接口。
  4. 獲取 bean。對應下圖的BeanFactory接口。

在 spring-bean 組件中,DefaultListableBeanFactory就是一個完整的 beanFactory 工廠,也可以說是一個 IoC 容器。

BeanFactory還有幾個擴展接口,用的比較多的可能是ConfigurableBeanFactoryAutowireCapableBeanFactory

  1. HierarchicalBeanFactory用於提供父子工廠的支持。例如,當前 beanFactory 找不到 bean 時,會嘗試從 parent beanFactory 中獲取。
  2. ConfigurableBeanFactory用於提供配置 beanFactory 的支持。例如,註冊BeanPostProcessor、註冊 TypeConverter、註冊OrderComparator等。
  3. ListableBeanFactory用於提供批量獲取 bean 的支持(不包含父工廠的 bean)。例如,我們可以根據類型獲取 beanName-bean 的 map。
  4. AutowireCapableBeanFactory用於提供實例化、屬性裝配、初始化等一系列管理 bean 生命周期的支持。 例如,該接口包含了 createBean、autowireBean、initializeBean、destroyBean 等方法。

當我們註冊 bean 時,根據註冊方式的不同,bean 的註冊信息會被放入兩個不同的地方。

class DefaultSingletonBeanRegistry {
	// beanName=singletonObject鍵值對
    // 除了registerSingleton的會放在這裏,registerBeanDefinition生成的單例bean實例也會放在這裏
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
}
class DefaultListableBeanFactory {
	// beanName=beanDefination鍵值對
	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
}

接下來開始分析源碼,註冊 bean 比較簡單,這裏就不看了,我們直接看 getBean(Class) 的代碼。

從getBean(requiredType)方法開始

進入到 DefaultListableBeanFactory.getBean(Class)方法,並逐漸展開。在DefaultListableBeanFactory.resolveBean(ResolvableType, Object[], boolean)方法中,如果當前 beanFactory 中獲取不到這個 bean,將嘗試從 parent beanFactory 中獲取,這也說明了一點:父子 beanFactory 中允許存在相同 beanName 的 bean,只是獲取時當前 beanFactory 的優先級更高一些

	public <T> T getBean(Class<T> requiredType) throws BeansException {
        // 適配入參
        // 可以看到,我們獲取bean時還可以指定構造參數
		return getBean(requiredType, (Object[]) null);
	}
	public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException {
		Assert.notNull(requiredType, "Required type must not be null");
        // 繼續適配入參
        // 這裏的第三個參數表示,如果指定類型對應的beanName不唯一時,true為返回null, false為拋出異常
		Object resolved = resolveBean(ResolvableType.forRawClass(requiredType), args, false);
        // 如果獲取不到這個bean,拋出異常
		if (resolved == null) {
			throw new NoSuchBeanDefinitionException(requiredType);
		}
		return (T) resolved;
	}
	private <T> T resolveBean(ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) {
        // 這裏的NamedBeanHolder就是簡單的對bean實例封裝了一層,不用太關注
		NamedBeanHolder<T> namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull);
        // 如果獲取得到bean實例,則返回
		if (namedBean != null) {
			return namedBean.getBeanInstance();
		}
        // 如果沒有,嘗試從parent beanFactory中獲取
        // 這部分代碼省略······
		return null;
	}

存在多個beanName怎麼辦

通過 beanType 來獲取 bean,可能會存在一個類型關聯了多個 beanName 的情況,使用例子中我們說過,可以通過指定 beanDefination 的 isPrimary = true 或者註冊比較器的方式來解決。接下來我們看下具體的處理過程。

進入到DefaultListableBeanFactory.resolveNamedBean(ResolvableType, Object[], boolean)方法。如果指定類型匹配到了多個 beanName,會進行以下處理:

  1. 如果存在通過registerSingleton註冊的 beanName,或者通過registerBeanDefinition註冊且 autowireCandidate = true 的 beanName,則僅保留它們,並剔除其他的 beanName;
  2. 如果還是存在多個 beanName,檢查是否存在唯一一個通過registerBeanDefinitionisPrimary = true的(存在多個會報錯),存在的話將它作為匹配到的唯一 beanName;
  3. 如果還是存在多個 beanName,通過我們註冊的OrderComparator來確定優先值最小的作為唯一 beanName,注意,通過registerSingleton註冊的和通過registerBeanDefinition註冊的,比較的對象是不一樣的;
  4. 如果還是存在多個 beanName,根據 nonUniqueAsNull,為 true 是返回 null,為 false 拋出NoUniqueBeanDefinitionException異常。
	private <T> NamedBeanHolder<T> resolveNamedBean(
			ResolvableType requiredType, @Nullable Object[] args, boolean nonUniqueAsNull) throws BeansException {

		Assert.notNull(requiredType, "Required type must not be null");
        // 獲取指定類型的所有beanName,可能匹配到多個
		String[] candidateNames = getBeanNamesForType(requiredType);
        
		// 如果指定類型匹配到了多個beanName,進行以下操作:
        // 如果存在通過registerSingleton註冊的beanName,或者通過registerBeanDefinition註冊且 autowireCandidate = true的beanName,則僅保留它們,並剔除其他的beanName;
		if (candidateNames.length > 1) {
			List<String> autowireCandidates = new ArrayList<>(candidateNames.length);
			for (String beanName : candidateNames) {
				if (!containsBeanDefinition(beanName) || getBeanDefinition(beanName).isAutowireCandidate()) {
					autowireCandidates.add(beanName);
				}
			}
			if (!autowireCandidates.isEmpty()) {
				candidateNames = StringUtils.toStringArray(autowireCandidates);
			}
		}
        
		// 如果只剩下一個beanName,那就根據beanName和beanType獲取bean
		if (candidateNames.length == 1) {
			String beanName = candidateNames[0];
			return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args));
		}
        
        // 如果存在多個,則還要進一步處理
		else if (candidateNames.length > 1) {
			Map<String, Object> candidates = new LinkedHashMap<>(candidateNames.length);
            // 遍歷候選的beanName
			for (String beanName : candidateNames) {
                // 如果該beanName是通過registerSingleton註冊的,且傳入構造參數為空
                // 則獲取該bean實例,並放入candidates
				if (containsSingleton(beanName) && args == null) {
					Object beanInstance = getBean(beanName);
					candidates.put(beanName, (beanInstance instanceof NullBean ? null : beanInstance));
				}
				else {
                    // 其他情況下,則獲取該beanName對應的類型,並放入candidates
                    // 注意,這裏的類型不一定是我們入參指定的類型,例如,如果我指定的是UserServiceFactoryBean.class,這裏返回的卻是UserService.class
					candidates.put(beanName, getType(beanName));
				}
			}
            // 如果裏面存在唯一一個通過registerBeanDefinition註冊的且isPrimary=true(存在多個會報錯),則將它作為匹配到的唯一beanName
			String candidateName = determinePrimaryCandidate(candidates, requiredType.toClass());
            // 如果還是確定不了,則通過我們註冊的OrderComparator來判斷candidates中value的優先數,挑選優先數最小的value對應的key作為唯一的beanName
			if (candidateName == null) {
				candidateName = determineHighestPriorityCandidate(candidates, requiredType.toClass());
			}
			if (candidateName != null) {
				Object beanInstance = candidates.get(candidateName);
                // 如果candidates中的value本身就是一個bean實例,那麼直接返回就好了
                // 如果不是,則根據beanName和beanType獲取bean
				if (beanInstance == null || beanInstance instanceof Class) {
					beanInstance = getBean(candidateName, requiredType.toClass(), args);
				}
				return new NamedBeanHolder<>(candidateName, (T) beanInstance);
			}
            // 如果還是確定不了唯一beanName,且設置了nonUniqueAsNull=false(默認為false),則會拋錯
			if (!nonUniqueAsNull) {
				throw new NoUniqueBeanDefinitionException(requiredType, candidates.keySet());
			}
		}
		
		return null;
	}

根據beanName和beanType獲取bean

進入AbstractBeanFactory.getBean(String, Class<T>, Object...)。這個方法里包括四個步驟:

  1. 轉義name。主要指的是當 name 是別名或者是 “&” + factory beanName 形式時進行轉義;
  2. 如果是單例 bean 且構造參數為空,則會從 singletonObjects 中獲取已生成的 bean,或者從 earlySingletonObjects/singletonFactories 中獲取已經實例化但可能還沒裝配或初始化的 bean。如果獲取到的不是 null,直接返回對應的 bean 實例;
  3. 如果當前 beanFactory 沒有指定的 beanName,則會去 parent beanFactory 中獲取;
  4. 如果當前 bean 需要依賴其他 bean,則會先獲取依賴的 bean;
  5. 根據 scope 選擇生成單例 bean 還是多例 bean;
  6. 進行類型檢查,如果獲取的 bean 不匹配,會先用我們註冊的類型轉換器轉換,如果還是不匹配就拋出BeanNotOfRequiredTypeException
	public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
			throws BeansException {
		// 適配入參
        // 這裏最後一個參數指獲取的bean是否純粹用於類型檢查,如果是的話,beanFactory不會標記這個bean正在生成中,僅對單例bean有用
		return doGetBean(name, requiredType, args, false);
	}
	@SuppressWarnings("unchecked")
	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		// 轉義我們傳入的name,這裏包括兩個內容:
        // 1. 如果是別名,需要轉換為別名對象的beanName;
        // 2. 如果是“&”+factoryBeanName,則需要去掉前面的“&”
		final String beanName = transformedBeanName(name);
		Object bean;

		// 獲取單例
        // 注意,這裏獲取到的有可能是已經初始化,也有可能是還沒初始化,甚至還沒裝配的bean
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			// 省略日誌部分······
            
            // 獲取bean,因為sharedInstance有可能是factoryBean,如果我們要的是factoryBean對應的bean,則還要getObject
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			// 如果當前線程已經在生成beanName對應的bean,就會拋錯
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// 如果當前beanFactory沒有指定的beanName,則會去parent beanFactory中獲取
            // 這部分省略······
			
			// 這裏標記指定bean正在創建中,一般對單例bean才有意義
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}

			try {
                // 獲取指定beanName對應的RootBeanDefinition對象
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                // 檢查RootBeanDefinition,目前就是檢查是否對應的類型為抽象類,是的話拋錯
				checkMergedBeanDefinition(mbd, beanName, args);

				// 如果當前bean需要依賴其他bean,則會先獲取依賴的bean
				// 這部分省略······

				// 創建單例bean
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
                            // 進入創建bean或factoryBean
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
                    // 獲取bean實例
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
				// 創建多例bean
				else if (mbd.isPrototype()) {
					Object prototypeInstance = null;
					try {
                        // 標記當前線程正在創建這個bean
						beforePrototypeCreation(beanName);
                        // 進入創建bean或factoryBean
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
                        // 去掉當前線程中這個bean正在創建的標記
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}
				// 接下來這種一般是自定義Scope的情況,這裏省略不討論
				else {
					// ·······
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}

		// 如果獲取到的bean實例不是我們指定的類型
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
                // 使用我們註冊的類型轉換器進行轉換
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
                // 如果轉換不了,則會拋錯
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

由於單例 bean 和多例 bean 的創建差不多,本文只選擇單例的來分析。

獲取單例bean

進入DefaultSingletonBeanRegistry.getSingleton(String, ObjectFactory)。這個方法包括幾個過程,主要就是處理一些多線程問題:

  1. 獲取指定 beanName 的 bean,如果已經存在,就不去創建,這時為了處理多線程同時創建 bean 的問題;
  2. 如果當前 bean 已經在創建中,會拋出 BeanCurrentlyInCreationException,創建單例 bean 之前是有加鎖的,按理不會出現這種情況;
  3. 創建單例 bean;
  4. 如果創建成功,將 bean 實例加入 singletonObjects,並且刪除掉 singletonFactories 和 earlySingletonObjects 中對應的鍵值對。
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
        // 這裏我不是很理解,為什麼使用singletonObjects作為鎖
        // 因為從earlySingletonObjects/singletonFactories中獲取已經實例化但可能還沒裝配或初始化的 bean時,用的鎖也是singletonObjects,這樣的話,提前暴露的機制好像就廢掉了啊???TODO
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
                // 如果當前beanFactory的單例正在銷毀,則不允許創建單例
				if (this.singletonsCurrentlyInDestruction) {
					// 省略拋錯······
				}
				
				// 判斷當前bean是不是已經在創建中,是的話拋出BeanCurrentlyInCreationException
                // 由於加了鎖,這種情況應該是不會發生的
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
                
                // 省略部分代碼······
                
				try {
                    // 這裏的執行的是createBean方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
                // 這種情況我不是很理解,singletonObjects的操作不應該被鎖住了嗎?TODO
				catch (IllegalStateException ex) {
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						throw ex;
					}
				}
                // 如果拋出的是BeanCreationException,
				catch (BeanCreationException ex) {
                    // 省略部分代碼······
                        
					throw ex;
				}
				finally {
                    // 省略部分代碼······
                    
                    // 如果當前bean不處於創建狀態中,會拋出IllegalStateException
					afterSingletonCreation(beanName);
				}
            	// 如果創建成功,將bean實例加入singletonObjects,並且刪除掉singletonFactories和earlySingletonObjects中對應的鍵值對
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

以上方法中,如果獲取不到已生成的單例 bean,則會開始創建 bean。

創建單例bean

進入AbstractAutowireCapableBeanFactory.createBean(String, RootBeanDefinition, Object[])。這個方法包括以下過程:

  1. 解析 beanType,並且再次包裝RootBeanDefinition
  2. 執行我們註冊的InstantiationAwareBeanPostProcessorpostProcessBeforeInstantiation方法,如果返回了非空對象,則將其返回。也就是說我們可以在該方法中自定義完成 bean 的實例化、裝配和初始化。
  3. 創建 bean。
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		RootBeanDefinition mbdToUse = mbd;

		// 解析當前RootBeanDefinition對應生成的bean類型,並進行再次包裝
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}

		// 省略部分代碼······

		try {
			// 執行我們註冊的InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法。也就是說我們可以在該方法中自定義完成 bean 的實例化、裝配和初始化。
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
            // 如果該方法返回bean,那就直接返回
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}

		try {
            // 創建bean
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			return beanInstance;
		}
		catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}

進入AbstractAutowireCapableBeanFactory.doCreateBean(String, RootBeanDefinition, Object[])。這個方法主要包含以下過程:

  1. 實例化 bean;
  2. 執行我們註冊的MergedBeanDefinitionPostProcessorpostProcessMergedBeanDefinition方法;
  3. 如果是單例,將還沒裝配和初始化的 bean 先暴露出去,即放在singletonFactories中,如果其他線程進來獲取,可以將這個 bean 或 factoryBean 返回,而不需要等待;
  4. 屬性裝配;
  5. 初始化;
  6. 將生成的 bean 放入 disposableBeans 中。
	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		BeanWrapper instanceWrapper = null;
        // 實例化
        // 如果是單例,嘗試從factoryBeanInstanceCache中獲取
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
        // 實例化bean
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// 執行我們註冊的MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法
		synchronized (mbd.postProcessingLock) {
			if (!mbd.postProcessed) {
				try {
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
				mbd.postProcessed = true;
			}
		}

		// 單例的可以將還沒裝配和初始化的bean先暴露出去,即放在singletonFactories中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
        
		Object exposedObject = bean;
		try {
            // 屬性裝配
			populateBean(beanName, mbd, instanceWrapper);
            // 初始化
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		// 省略部分代碼······

		// 將生成的bean或factoryBean放入disposableBeans中
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
		}

		return exposedObject;
	}

接下來將展開 bean 的實例化、屬性裝配和初始化。其中,實例化和屬性裝配的代碼比較複雜,我們重點分析,至於初始化部分,則留給讀者自行閱讀。

實例化

進入AbstractAutowireCapableBeanFactory.createBeanInstance(String, RootBeanDefinition, Object[])。這個方法主要過程如下:

  1. 解析 beanType,並對 beanType 進行一些必要的檢查;
  2. 通過我們設置的 InstanceSupplier 或 FactoryMethod 來直接獲取 bean,如果有的話,直接返回該對象;
  3. 如果構造參數為空,則可以復用已經解析好的構造對象(如果有的話);
  4. 執行我們註冊的SmartInstantiationAwareBeanPostProcessordetermineCandidateConstructors獲取構造對象數組;
  5. 如果得到的數組不是空,或者 beanDefination 的裝配模式為構造注入,或者 beanDefination 包含構造參數,或者我們傳入的構造參數非空,則進入實例化 bean
  6. 其他情況,使用無參構造來實例化。
	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// 解析bean類型
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		
        // 如果bean類型不是public的,則拋錯
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}
		
        // 通過RootBeanDefinition中定義的Supplier來獲取實例化bean
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		// 通過RootBeanDefinition中定義FactoryMethod來實例化bean
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}

		// 如果構造參數為空,則可以復用已經解析好的構造對象(如果有的話)
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}

		// 執行我們註冊的SmartInstantiationAwareBeanPostProcessor的determineCandidateConstructors獲取Constructor對象數組(如果有的話)
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
        // 如果得到的數組不是空,或者beanDefination的裝配模式為構造注入,或者beanDefination包含構造參數,或者我們傳入的構造參數非空,則進入實例化bean或factoryBean
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}
		
		// 省略部分代碼······

		// 使用無參構造實例化bean或factoryBean
		return instantiateBean(beanName, mbd);
	}

實例化的方法包括有參構造實例化和無參構造實例化兩種,本文只討論有參構造實例化的情況。

ConstructorArgumentValues和ArgumentsHolder

在繼續分析之前,有必要了解下ConstructorArgumentValuesArgumentsHolder這兩個類。

首先,ConstructorArgumentValues用於定義構造方法的參數列表的值。spring 中,ConstructorArgumentValues一般被定義在 BeanDefinition對象中,它影響着 bean 的實例化,是 bean 實例化時選擇構造對象的依據。

public class ConstructorArgumentValues {
	// 索引+參數值
    // 例如,對應new User(int age, String name, String address)的構造方法,可以包含元素:0=new ValueHolder(18),2=new ValueHolder("北京")
	private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<>();
	// 通用參數值
    // 例如,對應new User(int age, String name, String address)的構造方法,如果indexedArgumentValues中不包含name的值,則可以在genericArgumentValues中查找,我們只需要添加元素:new ValueHolder("zzs001", String.class)
	private final List<ValueHolder> genericArgumentValues = new ArrayList<>();
    
    // 內部類,代表一個參數的值
    public static class ValueHolder implements BeanMetadataElement {

		@Nullable
		private Object value;

		@Nullable
		private String type;

		@Nullable
		private String name;

		@Nullable
		private Object source;

		private boolean converted = false;

}

ArgumentsHolderConstructorResolver的內部類,和ConstructorArgumentValues一樣,它也是用來定義構造方法的參數列表的值,區別在於,ConstructorArgumentValues的值是“未解析的”,而ArgumentsHolder包含了“未解析”(preparedArguments)、“解析未完成”(rawArguments)和”解析完成”(arguments)三種值。

為什麼會這樣呢?因為ConstructorArgumentValues中的參數值的類型不一定和構造方法中的匹配,包括兩種情況:

  1. 類型不同,但可以通過TypeConverter轉換的類型。例如,在new User(int age, String name, Address address)的構造方法中,我可以在ConstructorArgumentValues添加2=new AddressVO(),這個時候只要 spring 能找到合適的轉換器就能轉換,這個轉換過程為:“解析未完成”(rawArguments) –》 “解析完成”(arguments)
  2. 類型不同,參數的值指向其他 bean ,當然也可以是其他 spring 可識別的引用。例如,new User(int age, String name, Address address)的構造方法中,我可以在ConstructorArgumentValues添加2=new RootBeanDefinition(Address.class),這個轉換過程為:“未解析”(preparedArguments) –》“解析未完成”(rawArguments)
private static class ArgumentsHolder {

    public final Object[] rawArguments;

    public final Object[] arguments;

    public final Object[] preparedArguments;

    public boolean resolveNecessary = false;

}

理解完這兩個類之後,我們繼續分析實例化的源碼。

有參構造實例化

進入到AbstractAutowireCapableBeanFactory.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[])方法。這裏創建了一個ConstructorResolver對象並直接調用它的 autowireConstructor 方法。

	protected BeanWrapper autowireConstructor(
			String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {

		return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
	}

進入ConstructorResolver.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[])。這個方法代碼比較多,為了更好地理解,可以分成兩種場景來看:

  1. 入參里顯式指定構造參數。這種場景的參數值默認都是解析過的,所以不需要解析,該場景要求對應的構造對象的參數數量必須和指定的一樣。
  2. BeanDefinition對象中指定ConstructorArgumentValues。這種場景的參數值需要經過兩步轉換,該場景要求對應的構造對象的參數數量不小於指定的數量。
	public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd,
			@Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) {

		BeanWrapperImpl bw = new BeanWrapperImpl();
		this.beanFactory.initBeanWrapper(bw);
		
        // 定義最終用於實例化對象的構造器
		Constructor<?> constructorToUse = null;
        // 定義存放(“未解析”、“解析未完成”、“解析完成”)構造參數的對象
		ArgumentsHolder argsHolderToUse = null;
        // 定義最終用於實例化對象的構造參數
		Object[] argsToUse = null;
		
        // 入參顯式聲明了構造參數(場景一),則不需要解析參數列表值,但需解析構造對象
		if (explicitArgs != null) {
			argsToUse = explicitArgs;
		}
		else {
			Object[] argsToResolve = null;
            // BeanDefinition對象中指定ConstructorArgumentValues(場景二),如果參數列表值或構造對象已經解析,則不需要再解析
			synchronized (mbd.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse != null && mbd.constructorArgumentsResolved) {
					// Found a cached constructor...
					argsToUse = mbd.resolvedConstructorArguments;
					if (argsToUse == null) {
						argsToResolve = mbd.preparedConstructorArguments;
					}
				}
			}
			if (argsToResolve != null) {
				argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve, true);
			}
		}
		
        // 進入解析參數列表值和構造對象
		if (constructorToUse == null || argsToUse == null) {
			// 如果入參里沒有顯式指定構造對象的數組,使用反射方式獲取
			Constructor<?>[] candidates = chosenCtors;
			if (candidates == null) {
				Class<?> beanClass = mbd.getBeanClass();
				try {
                    // BeanDefinition中可以定義是否包括非public的方法
					candidates = (mbd.isNonPublicAccessAllowed() ?
							beanClass.getDeclaredConstructors() : beanClass.getConstructors());
				}
				catch (Throwable ex) {
					// 省略代碼······
				}
			}
			
            // 如果數組中只有一個無參構造,且入參和BeanDefinition中都未指定參數列表值,則標記該BeanDefinition對象的構造參數已解析,並實例化bean
			if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
				// 省略代碼······
			}

			// 判斷是否需要解析構造
			boolean autowiring = (chosenCtors != null ||
					mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
            // 這裏存放“解析未完成”的參數列表值
			ConstructorArgumentValues resolvedValues = null;
			
            // 獲取要求構造參數的最小數量
			int minNrOfArgs;
            // 入參顯式聲明了構造參數(場景一),minNrOfArgs即為指定數組的長度
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			}
			else {
                // BeanDefinition對象中指定ConstructorArgumentValues(場景二),則需要計算minNrOfArgs,並進行“未解析” --> “解析未完成”的轉換
				ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
				resolvedValues = new ConstructorArgumentValues();
				minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
			}
			// 根據參數數量從小到大排列
			AutowireUtils.sortConstructors(candidates);
			int minTypeDiffWeight = Integer.MAX_VALUE;
			Set<Constructor<?>> ambiguousConstructors = null;
			LinkedList<UnsatisfiedDependencyException> causes = null;
			
            // 遍歷候選的構造對象
			for (Constructor<?> candidate : candidates) {
				// 獲取當前構造對象的參數數量
				int parameterCount = candidate.getParameterCount();
				//  如果上一個循環已經找到匹配的構造對象,則跳出循環1
				if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) {
					break;
				}
                
                // 如果當前構造對象的參數數量小於minNrOfArgs,則遍歷下一個
                // 注意,入參里顯式指定構造參數(場景一)要求對應的構造對象的參數數量必須和指定的一樣。BeanDefinition對象中指定ConstructorArgumentValues(場景二)要求對應的構造對象的參數數量不小於指定的數量
				if (parameterCount < minNrOfArgs) {
					continue;
				}

				ArgumentsHolder argsHolder;
                // 獲取當前構造對象的參數類型數組
				Class<?>[] paramTypes = candidate.getParameterTypes();
                // BeanDefinition對象中指定ConstructorArgumentValues(場景二)的情況
				if (resolvedValues != null) {
                    // 進行“解析未完成”->“解析完成”的轉換
					try {
                        // 這裡是為了處理JDK6的ConstructorProperties註解,其他情況都會返回null。
						String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount);
						if (paramNames == null) {
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
							if (pnd != null) {
                                // 獲取當前構造對象的參數名數組
								paramNames = pnd.getParameterNames(candidate);
							}
						}
                        // 創建ArgumentsHolder對象
						argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
					}
					catch (UnsatisfiedDependencyException ex) {
						// 省略代碼······
						continue;
					}
				}
                // 入參里顯式指定構造參數(場景一)的情況
				else {
                    // 如果當前構造參數的數量小於指定參數的數量,則繼續循環
					if (parameterCount != explicitArgs.length) {
						continue;
					}
                    // 創建ArgumentsHolder對象,因為不需要解析參數,所以,這種情況raw、prepared、resolved都是一樣的
					argsHolder = new ArgumentsHolder(explicitArgs);
				}
				// 計算指定參數和當前構造的參數類型的差異值
				int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
						argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
				// 差異值小於閾值
				if (typeDiffWeight < minTypeDiffWeight) {
					// 得到匹配的構造對象和構造參數
					constructorToUse = candidate;
					argsHolderToUse = argsHolder;
					argsToUse = argsHolder.arguments;
					minTypeDiffWeight = typeDiffWeight;
					ambiguousConstructors = null;
				}
                // 差異值大於閾值,這種不考慮
				else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) {
					// 省略代碼······
				}
			}
			// 如果找不到合適的構造對象,則會拋錯
			if (constructorToUse == null) {
				// 省略代碼······
			}
			else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) {
				// 省略代碼······
			}
			// BeanDefinition對象中指定ConstructorArgumentValues(場景二),為了復用解析好的構造和參數列表,需要標記當前BeanDefinition的構造參數已解析
			if (explicitArgs == null && argsHolderToUse != null) {
				argsHolderToUse.storeCache(mbd, constructorToUse);
			}
		}

		Assert.state(argsToUse != null, "Unresolved constructor arguments");
        // 接下來就是使用構造對象和參數來實例化對象,就不往下看了。
		bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse));
		return bw;
	}

實例化部分比較難,主要還得先理解一些抽象概念,例如:兩個場景、參數的轉換等。

屬性裝配

進入AbstractAutowireCapableBeanFactory.populateBean(String, RootBeanDefinition, BeanWrapper)。這個方法包括以下過程:

  1. 執行我們註冊的InstantiationAwareBeanPostProcessorpostProcessAfterInstantiation方法,如果返回了 false,則不進行屬性裝配,直接返回;
  2. 獲取 beanDefinition 中的PropertyValues對象,根據 beanDefinition 設置的注入類型,填充PropertyValues對象;
  3. 執行我們註冊的InstantiationAwareBeanPostProcessorpostProcessProperties方法,可以對PropertyValues對象進行修改;
  4. 依賴檢查(如果設置了);
  5. 進行屬性裝配。
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
        // 如果實例對象為空,則拋出異常或直接返回
		if (bw == null) {
			if (mbd.hasPropertyValues()) {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
			}
			else {
				return;
			}
		}

		// 執行我們註冊的InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法,如果返回了false,則不進行屬性裝配,直接返回
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
						return;
					}
				}
			}
		}
		
        // 獲取BeanDefinition對象中的PropertyValues,包含了name=value的PropertyValue對象的列表
		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
		
        // 根據我們設置的注入方式,填充
		int resolvedAutowireMode = mbd.getResolvedAutowireMode();
		if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
			// 按名字裝配
			if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}
			// 按類型裝配
			if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}
			pvs = newPvs;
		}
		// beanFactory中是否註冊了InstantiationAwareBeanPostProcessors
		boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
        // BeanDefinition對象中是否設置了依賴檢查
		boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

		PropertyDescriptor[] filteredPds = null;
		if (hasInstAwareBpps) {
			if (pvs == null) {
                // 如果為空,再次從BeanDefinition對象中獲取,TODO?
				pvs = mbd.getPropertyValues();
			}
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    // 執行我們註冊的InstantiationAwareBeanPostProcessor的postProcessProperties方法,可以對PropertyValues對象進行修改
					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
					// 省略部分代碼······
					pvs = pvsToUse;
				}
			}
		}
        // 如果BeanDefinition對象中設置了依賴檢查,則需要檢查依賴設置
		if (needsDepCheck) {
			if (filteredPds == null) {
				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			}
			checkDependencies(beanName, mbd, filteredPds, pvs);
		}

		if (pvs != null) {
            // 執行屬性裝配
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}

這個方法中主要涉及autowireByNameautowireByTypeapplyPropertyValues三個方法,前兩個暫時不展開,只講最後一個方法。

幾個重要的知識點

在分析applyPropertyValues方法之前,我們需要知道一下幾個知識點。這裏以User這個類來展開例子。

public class User {
    
    private String name;
    
    private int age;
    
    private Address address;
    
    private List<String> hobbies;
}
class Address {
    private String region;
    private String desc;
}

propertyName的幾種形式

當我們給 beanDefinition設置屬性值時,一般都會這樣採用這樣的賦值,這裏成為“普通形式”。

rootBeanDefinition.getPropertyValues().add("name", "zzs001");
rootBeanDefinition.getPropertyValues().add("age", 18);
rootBeanDefinition.getPropertyValues().add("address", new Address("", ""));
rootBeanDefinition.getPropertyValues().add("hobbies", new ArrayList());

針對類型為 object、list、array、map 等成員屬性,spring 還支持其他的賦值方式,如下,分別成為“嵌套對象形式”和“索引形式”:

// 嵌套對象形式
rootBeanDefinition.getPropertyValues().add("address.region", "");
rootBeanDefinition.getPropertyValues().add("address.desc", "");
// 索引形式
rootBeanDefinition.getPropertyValues().add("hobbies[0]", "");

正是由於 propertyName 引入了多種的形式,所以,原本簡單的賦值行為被搞得非常複雜。例如,嵌套對象形式還可以是這樣:foo.user.address.region,幾乎可以一直嵌套下去。

PropertyAccessor

propertyAccessor 對象一般綁定了一個實例對象,通過PropertyAccessor接口的方法可以對對象的屬性進行存取操作。屬性裝配中最終對成員屬性賦值就是調用它的setPropertyValue方法。AbstractNestablePropertyAccessor中維護了一個 map,key 為當前綁定對象的屬性名(不包含嵌套和索引),value 就是對於的PropertyAccessor對象。

public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyAccessor {
    private Map<String, AbstractNestablePropertyAccessor> nestedPropertyAccessors;
}

在上面的例子中,

rootBeanDefinition.getPropertyValues().add("name", "zzs001");
rootBeanDefinition.getPropertyValues().add("age", 18);

這種形式共用一個綁定了User類型實例的PropertyAccessor對象。

// 嵌套對象形式
rootBeanDefinition.getPropertyValues().add("address.region", "");
rootBeanDefinition.getPropertyValues().add("address.desc", "");

這種形式共用一個綁定了Address類型實例的PropertyAccessor對象,該對象和”address”這個名字關聯起來維護在 nestedPropertyAccessors 中。

// 索引形式
rootBeanDefinition.getPropertyValues().add("hobbies[0]", "");

這種形式也是一個綁定了User類型實例的PropertyAccessor對象,該對象和”hobbies”這個名字關聯起來維護在 nestedPropertyAccessors 中。

PropertyTokenHolder

PropertyTokenHolderAbstractNestablePropertyAccessor的內部類,它更多的是針對“索引形式”的 propertyName。例如,”hobbies[0]”對於的PropertyTokenHolder中,actualName = hobbies,canonicalName = [0],keys = {0}。

	protected static class PropertyTokenHolder {

		public PropertyTokenHolder(String name) {
			this.actualName = name;
			this.canonicalName = name;
		}

		public String actualName;

		public String canonicalName;

		@Nullable
		public String[] keys;
	}

接下來繼續分析屬性裝配的代碼。

applyPropertyValues

進入AbstractAutowireCapableBeanFactory.applyPropertyValues(String, BeanDefinition, BeanWrapper, PropertyValues)方法。和構造參數一樣,設置成員屬性的參數也需要經過“兩次轉換”,這裏就不詳細講解。這個方法主要包括以下過程:

  1. 獲取屬性對象列表,如果這個列表的屬性對象都已經完成“兩次轉換”,則直接裝配屬性;
  2. 遍歷屬性對象列表,分別進行兩次轉換,如果列表中沒有類似BeanDefinitionBeanDefinitionHolder等的對象,則設置PropertyValues對象已經轉換完成,下次調用這個方法不用再進行轉換;
  3. 屬性裝配。
	protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
        // 如果沒有需要注入的屬性,直接返回
		if (pvs.isEmpty()) {
			return;
		}

		// 省略部分代碼······

		MutablePropertyValues mpvs = null;
        
        // 獲取屬性對象列表
		List<PropertyValue> original;
		if (pvs instanceof MutablePropertyValues) {
			mpvs = (MutablePropertyValues) pvs;
            // 如果所有屬性對象已經完成“兩次轉換”,則直接裝配屬性
			if (mpvs.isConverted()) {
				try {
					bw.setPropertyValues(mpvs);
					return;
				}
				catch (BeansException ex) {
					throw new BeanCreationException(
							mbd.getResourceDescription(), beanName, "Error setting property values", ex);
				}
			}
			original = mpvs.getPropertyValueList();
		}
		else {
			original = Arrays.asList(pvs.getPropertyValues());
		}
		
        // 獲取我們註冊的類型轉換器
		TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;
		}
        // 創建第一次轉換所用的解析器
		BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
		
        // 定義一個列表,用於存放完成“兩次轉換”的屬性對象
		// 這注意,這裏並沒有進行所謂的複製,不要被命名迷惑了
		List<PropertyValue> deepCopy = new ArrayList<>(original.size());
		boolean resolveNecessary = false;
        // 遍歷屬性對象
		for (PropertyValue pv : original) {
            // 當前屬性對象已經完成“兩次轉換”,直接添加到列表
			if (pv.isConverted()) {
				deepCopy.add(pv);
			}
			else {
				String propertyName = pv.getName();
				Object originalValue = pv.getValue();
				// 省略部分代碼······
                // 第一次轉換
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
				Object convertedValue = resolvedValue;
                // 如果當前屬性為可寫屬性,且屬性名不是類似於foo.bar或addresses[0]的形式,則需要進行第二次轉換
				boolean convertible = bw.isWritableProperty(propertyName) &&
						!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
				if (convertible) {
					convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
				}
				// 如果轉換后的屬性對象和初始對象一樣,一般指的是普通對象,而不是BeanDefinition、BeanDefinitionHolder等
				if (resolvedValue == originalValue) {
                    // 如果需要第二次轉換,則設置復用的目標對象
					if (convertible) {
						pv.setConvertedValue(convertedValue);
					}
                    // 將原屬性對象添加到列表
					deepCopy.add(pv);
				}
                // 這種情況不考慮
				else if (convertible && originalValue instanceof TypedStringValue &&
						!((TypedStringValue) originalValue).isDynamic() &&
						!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
					pv.setConvertedValue(convertedValue);
					deepCopy.add(pv);
				}
                // 其他情況
				else {
                    // 標記每次都需要解析
					resolveNecessary = true;
                    // 將原屬性對象添加到列表
					deepCopy.add(new PropertyValue(pv, convertedValue));
				}
			}
		}
        // 如果不包含BeanDefinition、BeanDefinitionHolder等對象,則設置PropertyValues為已轉換,這樣下次調用這個方法,就不需要進行任何的轉換了
		if (mpvs != null && !resolveNecessary) {
			mpvs.setConverted();
		}

		// 屬性裝配
		try {
			bw.setPropertyValues(new MutablePropertyValues(deepCopy));
		}
		catch (BeansException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Error setting property values", ex);
		}
	}

進入AbstractPropertyAccessor.setPropertyValues(PropertyValues)方法。這裏遍歷屬性對象列表,逐個進賦值操作。

	public void setPropertyValues(PropertyValues pvs) throws BeansException {
        // 入參適配
        // 後面兩個參數分別代表:是否忽略NotWritablePropertyException異常、是否忽略NullValueInNestedPathException異常
		setPropertyValues(pvs, false, false);
	}
	public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
			throws BeansException {

        // 獲取屬性對象列表
		List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
				((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
		for (PropertyValue pv : propertyValues) {
            	// 省略try-catch的代碼和其他異常相關的代碼······
				setPropertyValue(pv);
            }
	}

setPropertyValue

進入AbstractNestablePropertyAccessor.setPropertyValue(PropertyValue)。這個方法包括以下過程:

  1. 獲取 propertyName 對應的PropertyAccessor對象,這裏將解析“嵌套對象形式”的 propertyName;
  2. 創建PropertyTokenHolder對象,這裏將解析“索引形式”的 propertyName;
  3. 使用PropertyAccessor對象進行賦值操作。
	public void setPropertyValue(PropertyValue pv) throws BeansException {
        // 適配入參
		setPropertyValue(pv.getName(), pv.getValue());
	}	
	public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
		AbstractNestablePropertyAccessor nestedPa;
		try {
            // 獲取propertyName對應的PropertyAccessor對象,這裏將解析“嵌套對象形式”的propertyName
            // 如果緩存里有的話,將復用
			nestedPa = getPropertyAccessorForPropertyPath(propertyName);
		}
		catch (NotReadablePropertyException ex) {
			throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
					"Nested property in path '" + propertyName + "' does not exist", ex);
		}
        // 創建PropertyTokenHolder對象,這裏將解析“索引形式”的propertyName
		PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
        // 使用PropertyAccessor對象進行賦值操作
		nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
	}

進入AbstractNestablePropertyAccessor.setPropertyValue(PropertyTokenHolder, PropertyValue)方法。這裏根據 propertyName 是否為“索引形式”調用不同的方法。

	protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
		if (tokens.keys != null) {
			processKeyedProperty(tokens, pv);
		}
		else {
			processLocalProperty(tokens, pv);
		}
	}

這裏我們不看 propertyName 為“索引形式”的方法,只看processLocalProperty

	private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
        // 獲取actualName對應的PropertyHandler對象,如果有緩存則復用
		PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
		if (ph == null || !ph.isWritable()) {
			// 省略部分代碼······
		}

		Object oldValue = null;
		try {
			Object originalValue = pv.getValue();
			Object valueToApply = originalValue;
			if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
                // 因為我們的屬性參數都是轉換過的,所以這裏不再看轉換的代碼
				if (pv.isConverted()) {
					valueToApply = pv.getConvertedValue();
				}
				else {
					// 省略部分代碼······
				}
				pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
			}
            // 接下來就是通過反射方式給屬性賦值,後續再展開
			ph.setValue(valueToApply);
		}
		catch (Exception ex) {
			// 省略部分代碼······
        }
	}

屬性裝配的代碼分析就點到為止吧。

最後補充

以上基本看完 spring-bean 的源碼。針對 getBean 的過程,本文未展開的內容包括:

  1. 獲取和創建多例 bean;
  2. 無參構造實例化;
  3. 屬性裝配中,屬性值列表的填充(autowireByName和autowireByType)、屬性名為索引形式的屬性裝配
  4. bean 的初始化。

感興趣的讀者可以自行分析。另外,以上內容如有錯誤,歡迎指正。

最後,感謝閱讀。

相關源碼請移步: spring-beans

本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/13196228.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心