Dell 將於 2040 年全面轉用再生能源

摘錄自2019年11月13日unwire HK報導

隨著更多的企業響應環保,作為知名的電腦公司 DELL 在近日亦宣佈了新的可持續發展計劃,指公司所有設施的 75% 電力將會使用可再生能源,直至 2040 年時將預計可獲 100% 的電力,並同時在計劃在過程中提高公司在生產及供應鏈上的能源效益,以達致低排放的目標為環保出一分力。

DELL 在早前宣佈公司將進行「Progress Made Real」的計劃,計劃內容主要包括在消費者購買的每一項設備時會回收同一污染水平的產品,以及在生產電力方面採用可再生能源。DELL 指出到 2030 年時公司一半以上的設備將會由回收材料或可再生物料而生產,在包裝上則會全面使用可重覆利用的物料。

不過 DELL 就並未透露公司是否會增加旗下所有裝置的使用壽命,但就指出對電子製造商而言,應對氣候變化的最佳方法是避免所有電子產品送往堆填區,而是重覆使用及物盡其用。

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

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

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

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

不敵客訴壓力 澳洲超市延後對塑膠袋收費

摘錄自2018年8月3日蘋果日報澳洲報導

澳洲一間大型連鎖超市原本要實施塑膠袋需收費政策,希望可推動「減塑」為環保出一分力,不料顧客對此大感不滿,政策推出後已經第2次延長收費期限。

連鎖超市科爾斯(Coles)自上月1日起停用一次性塑膠袋,改向顧客提供可重複使用、質料更耐用的塑膠袋作過渡,直至本周三(1日)為止。超市原定顧客此後若索取塑膠袋,每個須收取15澳分(約3.4元台幣)。

然而科爾斯周三發聲明指,自禁用一次性塑膠袋後,有顧客反映需要更多過渡時間,以適應使用可重複使用塑膠袋,集團因此決定在昆士蘭、新南威爾斯、維多利亞及西澳洲等地繼續提供免費塑膠袋至8月29日,而南澳洲、塔斯馬尼亞等地區則仍繼續收費。

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

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

湄公河跨國水資源爭奪戰 寮國沙耶武里大壩爭議中即將啟用

環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:ENS

位於寮國北部湄公河的沙耶武里水壩將在數日內正式啟用。沙耶武里水壩是湄公河主流下游的第一座水壩,它的啟用象徵著湄公河命運的重要轉折點。

湄公河長4,350公里,是世界第12長河,排水量世界第八。發源於青藏高原,流經中國、緬甸、寮國和泰國,接著湧入柬埔寨和越南的沖積平原和三角洲。

沙耶武里水壩的主要目的是水力發電,其95%的發電量將由泰國電力局購買。

沙耶武里水壩打從一開始就是一個爭議性的工程,許多人擔心它對河流系統的作用,包括使湄公河的洄游魚類和沈積物難以往下游移動,可能連鄰國都會受影響。

大壩對環境的影響進而威脅流域內居民的糧食來源、生計和社會文化體系。

沙耶武里水壩即將完工。照片來源:

許多專家認為,湄公河上游中國境內已經建了六座水壩,寮國甚至柬埔寨下游還要再蓋,已經讓湄公河深陷危機。

沙耶武里水壩諮詢過程中,有許多利害關係者表示關切,質疑資料和研究是否充分。

越南政府呼籲暫停所有主流上水壩的建設10年,以進一步研究、更深入地了解河流系統和水壩的可能影響。

泰國湄公河沿岸的社區代表於2012年向泰國行政法院提起訴訟,質疑泰國向沙耶武里水壩購買電力的計畫。此訴訟案別具指標性,但經過數次上訴,七年後的今日仍懸而未決。

儘管如此,沙耶武里水壩的開發並沒有停止,開發商重新設計以減輕疑慮。

後續的大壩工程計畫也持續在進行。本月,湄公河委員會宣布開始對湄公河下游的第五座主流水壩瑯勃拉邦進行事前諮商。

在沙耶武里水壩啟用前,美國非營利組織國際河網(International Rivers)發布了關於水壩的新報告。該組織邀請兩位獨立專家針對湄公河委員會今年稍早發布的沙耶武里水壩設計變更審查報告發表評論。

兩位專家分別是澳洲雪梨大學人文地理學教授賀屈(Philip Hirsch)博士和英格蘭諾桑比亞大學社會科學副教授亨森格斯(Oliver Hensengerth)博士。他們檢視沙耶武里水壩如何成為主流水壩決策模式的基準,強調「迫切需要一個真正的區域性標準程序來保護湄公河的未來。」

國際河網的聲明指出,雖然國際河網自身的立場是認為大壩的開發正在「扼殺」湄公河,但該專家評論的目的並非批評或評估湄公河委員會的管理審查報告,而是在試圖「找出關鍵點,討論它們對沙耶武里水壩和其他規劃中或興建中水壩對湄公河下游主流以及該地區內的影響。」

沙耶武里水壩開發前的地景樣貌。照片來源: (CC BY 2.0)

23日,一場針對國際河網報告的座談會在泰國曼谷外國記者俱樂部舉行,與會學者、社區和民間社團熱烈討論沙耶武里水壩工程的歷史、決策過程缺陷、進行中的活動以及對生態系統與居民的影響。

國際河網認為,沙耶武里水壩興建過程「工程先行,研究後補」的做法很不負責任。(詳見)

湄公河沖積平原和三角洲是全世界農業產量極高、生物多樣性極豐富的水域之一,但是海平面上升、土地沉降、上游超過126個規劃中水壩以及各式各樣三角洲水利基礎設施讓人們不得不對水力發電的潛在問題感到憂心。

國際河網不是唯一一個對湄公河流域感到擔憂的環境組織。

丹麥DHI顧問集團針對湄公河三角洲進行的研究得出的結論是,「即使是現有最佳的魚道技術,也可能無法因應大量的魚類遷徙。在高峰時期,遷徙魚群最多可達每小時300萬條。此外,可能也難以滿足該流域數百種魚類有百百種遷移方式。」

根據22日在寮國首都永珍發布的最新報告《》,負責管理流域水壩開發的湄公河委員會也對此表示關注。

湄公河委員會成立於1995年,是一個政府間組織,直接與柬埔寨、寮國、泰國和越南政府合作,共同管理共享水資源和湄公河的永續發展。

該組織是水務外交的區域平台,也是維持該區域永續發展的水資源管理知識中心。

湄公河委員會報告警告:「主流水流體系明顯永久性改變,沉積物被阻攔造成泥沙流量大量減少,濕地持續喪失,河流生態環境惡化,捕撈漁業的壓力不斷增加以及目前水開發設施和用水資訊共享有限」,是湄公河流域國家面臨的主要挑戰。

「我們現在必須解決這些問題,盡可能減少對環境的損害,並在僅剩的濕地和河邊生態環境消失之前加以保護,同時利用更穩定且有所增加的旱季流量,實現湄公河地區最佳永續發展。」湄公河委員會執行長An Pich Hatda博士在啟用儀式上對來自四個湄公河國家、近100位官員說。

最新的流域狀況報告建議:「必須緊急採取更積極的區域性流域規劃和管理方法,並加強系統性地共享資訊,並嚴格監控河流流量,以因應這些流域挑戰。」

Dam Development Is ‘Silencing’ the Mekong River BANGKOK, Thailand, October 24, 2019 (ENS)

 In five days, the Xayaburi Hydropower Project on the Mekong River in northern Laos will formally begin operations. As the first dam on the lower Mekong mainstream, this marks a turning point for the Mekong River.

The Mekong River is 4,350 kilometers (2,703 miles) long, ranked 12th in length and eighth in water discharge in the world. The river originates in the Tibetan Plateau and flows through China, Myanmar, Laos, and Thailand before pouring into the alluvial floodplains and delta in Cambodia and Vietnam.

The main purpose of the Xayaburi dam is to produce hydroelectric power, 95 percent of which is to be purchased by the Electricity Generating Authority of Thailand.

From the outset, the Xayaburi dam was a controversial project due to widespread concerns over its expected impacts on the river system, including transboundary impacts in neighboring countries.

Major predicted impacts include the destruction of Mekong migratory fisheries and trapping of sediment, preventing it from traveling downstream.

The dam’s environmental impacts, in turn, threaten the food, livelihoods and socio-cultural systems of populations residing within the river basin.

Many experts believe that the Mekong, already suffering from the impacts of six dams installed in China on the Upper Mekong, and with more dams planned downstream in Laos and possibly Cambodia, is in crisis.

During the Xayaburi dam consultation process, many stakeholders raised concerns over the project and questioned the adequacy of the data and studies.

The Vietnamese government called for a project suspension and a 10-year moratorium on all mainstream dams pending further study to better understand the river system and the impacts of planned dams.

In Thailand, community representatives along the Mekong River filed a landmark lawsuit in the Thai Administrative Court challenging Thailand’s power purchase from the project. Originally filed in 2012, following several appeals, the lawsuit remains pending more than seven years later.

Despite this, the Xayaburi dam moved forward, with the developers undertaking a redesign in an effort to mitigate concerns.

Subsequent dam projects have followed. This month, the Mekong River Commission announced the commencement of Prior Consultation for Luang Prabang, the fifth lower Mekong mainstream dam to undergo the process.

In the lead-up to the commissioning of the Xayaburi dam, the U.S.-based nonprofit group International Rivers issued a new report on the dam. The group asked two independent experts to provide comments on the Mekong River Commission’s review of the Xayaburi redesign, released earlier this year.

The report of the experts, Dr. Philip Hirsch, professor of Human Geography at the University of Sydney, Australia; and Dr. Oliver Hensengerth, associate professor of social sciences at Northumbria University, England, examines the pattern of Xayaburi in setting a benchmark for decisions on mainstream dams and highlights “the urgent need for a truly regional approach to safeguard the Mekong’s future.”

Although International Rivers says dam development is “silencing” the Mekong River, this expert commentary is not intended as a critique or assessment of the MRC Review, said the group in a statement. “Rather, it seeks to draw out key points and discuss their implications for Xayaburi and other dams under construction or consideration on the lower Mekong mainstream and within the region.”

On Wednesday, a panel discussion of the International Rivers report with academic, community and civil society speakers at the Foreign Correspondents’ Club of Thailand in Bangkok provoked comments on the project’s history, its flawed decision-making process, the ongoing campaigns, and Xayaburi’s implications for the ecosystems and people of the Mekong Basin.

International Rivers has described the “build first, study later” approach propagated by the Xayaburi Dam process as “a dangerously irresponsible model for dam-building in the Mekong.”

To read the report, “Review of Design Changes Made for the Xayaburi Hydropower Project,” click .

The Mekong floodplains and delta are among the most agriculturally productive and biologically diverse waterscapes of the world, but sea level rise, land subsidence, and the proposed upstream development of over 126 hydropower dams and extensive delta-based water infrastructure have raised concerns about the potential impacts on the hydrology of the region.

International Rivers is not the only environmental group with concerns about the Mekong River Basin.

A Mekong Delta Study conducted by Denmark’s DHI Consulting Group concluded it was likely “that even the best available fish passage technologies’ may not be able to handle either the massive volume of fish migrations, which during peak periods can reach up to three million fish per hour, or the diversity of migration strategies that characterise the hundreds of fish species in the basin.”

The Mekong River Commission, which governs the dam development of the basin, is also concerned, according to the latest report, State of the Basin Report 2018, released on Tuesday in Vientiane, the Laotian capital city.

Established in 1995, the Mekong River Commission, MRC, is an inter-governmental organization that works directly with the governments of Cambodia, Laos, Thailand, and Vietnam to jointly manage the shared water resources and the sustainable development of the Mekong River.

The organization serves as a regional platform for water diplomacy as well as a knowledge hub of water resources management for the sustainable development of the region.

The MRC report warns, “The apparent permanent modification of mainstream flow regime, the substantial reduction in sediment flows due to sediment trapping, the continuing loss of wetlands, the deterioration of riverine habitats, the growing pressures on capture fisheries, and the limited information sharing on current water development facilities and water use,” are some of the major challenges facing countries in the Mekong Basin.

“We need to address these issues now in order to minimize further environmental harm and protect remaining wetlands and riverine habitats before they are gone, while leveraging the benefits of more secure and increased dry season flows and achieving a more optimal and sustainable development of the Mekong basin,” Dr. An Pich Hatda, chief executive officer of the MRC Secretariat, told nearly 100 officials from the four MRC countries at the launch ceremony.

This latest State of the Basin Report advises that “a more proactive regional approach to basin planning and management, with an enhanced and systematic information sharing mechanism and robust monitoring of river flow must be put in place urgently to address these basin-wide challenges.”

※ 全文及圖片詳見:

作者

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

延伸閱讀

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

遊客遽增破壞環境 紐西蘭國會報告示警

摘錄自2019年12月18日中央社威靈頓報導

紐西蘭國會今天(18日)提出報告警示,遽增的遊客數量正危害紐西蘭環境,也讓當地聞名遐邇、極具魅力的寧靜印象受到逐步破壞。

法新社報導,紐西蘭一向標榜「100%純淨」和「乾淨又環保」的形象,但近年來遊客數量遽增,許多人朝聖「魔戒」(The Lord of the Rings)電影拍攝地點大玩自拍,還有登山客、健行者及野生動物愛好人士。

紐西蘭國會環境事務專員厄普頓(Simon Upton)提出報告之際,紐國旅遊業正受到嚴格審查,因為白島(White Island)火山在9日爆發,造成16名外籍旅客和2名導遊喪命。

擁有490萬人口的紐西蘭每年吸引近400萬名外國遊客,厄普頓說,這項數據到了2050年可能增加2倍,厄普頓表示,基礎設施變得吃緊、環境承受壓力,紐西蘭原有的諸多品質正在消失,他說:「大批群眾正逐漸破壞許多外國遊客赴紐西蘭旅遊所尋覓的獨處感、寧靜和親近大自然的感覺,我們必須要問:我們是否正在殺雞取卵?」

厄普頓提到,紐西蘭人也是問題的一部分,在紐西蘭國定假期,著名景點湧現的國內遊客人潮比外國遊客還要多。

他還說,紐西蘭人已經習慣東加里羅步道(Tongariro Crossing)等著名景點「被遊客團團包圍」,而且問題只會持續惡化。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

阿裏面試官最喜歡問的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維修中心

驗證碼原理及驗證

驗證碼的原理

驗證碼的作用:

 驗證碼是是一種區分用戶是計算機還是人的公共全自動程序,可以防止:惡意破解密碼、刷票、論壇灌水、有效防止某個黑客對某一特定註冊用戶,用特定程序暴力破解方式進行不斷的登錄嘗試。實際上驗證碼是現在很多網站通行的方式,我們利用比較簡易的方式實現了這個功能。

生成驗證碼

生成驗證碼這個功能已經特別成熟了 在網上可以找到很多資源

以下是生成驗證碼的相關代碼:

package com._yhnit.randomcode;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 *  生成驗證碼的Servlet
 * @author yhn
 *
 */
@WebServlet("/createRandomcode")
public class RandomCodeServlet extends HttpServlet{

	private static final long serialVersionUID = 1L;
	 public RandomCodeServlet() {
	        super();

	    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 響應頭信息
        response.setHeader("Pragma", "No-Cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expries", 0);

        // 隨機數生成類
        Random random = new Random();

        // 定義驗證碼的位數
        int size = 5;

        // 定義變量保存生成的驗證碼
        String vCode = "";
        char c;
        // 產生驗證碼
        for (int i = 0; i < size; i++) {
            // 產生一個26以內的隨機整數
            int number = random.nextInt(26);
            // 如果生成的是偶數,則隨機生成一個数字
            if (number % 2 == 0) {
                c = (char) ('0' + (char) ((int) (Math.random() * 10)));
                // 如果生成的是奇數,則隨機生成一個字母
            } else {
                c = (char) ((char) ((int) (Math.random() * 26)) + 'A');
            }
            vCode = vCode + c;
        }

        // 保存生成的5位驗證碼
        request.getSession().setAttribute("RANDOMCODE_IN_SESSION", vCode);

        // 驗證碼圖片的生成
        // 定義圖片的寬度和高度
        int width = (int) Math.ceil(size * 20);
        int height = 30;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 獲取圖片的上下文
        Graphics gr = image.getGraphics();
        // 設定圖片背景顏色
        gr.setColor(Color.WHITE);
        gr.fillRect(0, 0, width, height);
        // 設定圖片邊框
        gr.setColor(Color.GRAY);
        gr.drawRect(0, 0, width - 1, height - 1);
        // 畫十條幹擾線
        for (int i = 0; i < 5; i++) {
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            gr.setColor(randomColor());
            gr.drawLine(x1, y1, x2, y2);
        }
        // 設置字體,畫驗證碼
        gr.setColor(randomColor());
        gr.setFont(randomFont());
        gr.drawString(vCode, 10, 22);
        // 圖像生效
        gr.dispose();
        // 輸出到頁面
        ImageIO.write(image, "JPEG", response.getOutputStream());

    }

    // 生成隨機的顏色
    private Color randomColor() {
        int red = r.nextInt(150);
        int green = r.nextInt(150);
        int blue = r.nextInt(150);
        return new Color(red, green, blue);
    }

    private String[] fontNames = { "宋體", "華文楷體", "黑體", "微軟雅黑", "楷體_GB2312" };
    private Random r = new Random();

    // 生成隨機的字體
    private Font randomFont() {
        int index = r.nextInt(fontNames.length);
        String fontName = fontNames[index];// 生成隨機的字體名稱
        int style = r.nextInt(4);
        int size = r.nextInt(3) + 24; // 生成隨機字號, 24 ~ 28
        return new Font(fontName, style, size);
    }
}



上述代碼中 定義了生成5位数字+字母的驗證碼

生成的驗證碼 將存放到兩個地方:

  1. Session中
  2. 放到圖片上去

最重要的是 將驗證碼存入Session,因為後台校驗驗證碼是否正確要依靠這一步

// 保存生成的5位驗證碼
 request.getSession().setAttribute("RANDOMCODE_IN_SESSION", vCode);

前端頁面實現驗證碼的切換

在很多應用中 ,我們都會看見驗證碼的切換操作

比如:點擊圖片切換,或者點擊後面文字(類如 看不清,換一張) 進行切換

其實 切換很簡單 只是將圖片元素 的src 屬性 變換一下就可以完成

這裏給驗證碼圖片 和 換一張文字添加點擊事件

驗證碼:<input type="text" maxlength="5" required="required" name ="randomcode">
	   <img  src="/createRandomcode" style="cursor: pointer;" onclick="change();"  id="randomcodeImg">
		<a href="" onclick="change();">換一張</a><br>

點擊事件 是一個名字為change函數

function change(){
	// 因為有緩存  所以加一個隨機數  表示不同的請求
	document.getElementById("randomcodeImg").src="/createRandomcode?"+new Date().getTime();	
	}

注意這裏:src不能也寫 /createRandomcode,因為瀏覽器有緩存 因為之前的src就是它

所以點擊時不會發生切換,所以我們可以加個隨機數代表每一次都是一個新的請求。

這樣就可以實現驗證碼的切換了。

驗證碼的後台驗證

驗證其實也很簡單,只需要把輸入的和圖片中的驗證碼進行對比即可

獲取輸入的驗證碼:

String code = req.getParameter("randomcode");

獲取圖片中的驗證碼:

(生成的時候 已經存在Session中 這時只需要從Session中取出即可)

String Imgcode = req.getSession().getAttribute("RANDOMCODE_IN_SESSION").toString();

兩者進行對比驗證:

if (!code.equalsIgnoreCase(Imgcode)) {
    // 設置一些錯誤提示  提示用戶輸入錯誤 
	req.getSession().setAttribute("errorMes", "請輸入正確的驗證碼或已經過期");
	req.getRequestDispatcher("randomcode/RandomCodeLogin.jsp").forward(req, resp);
	return;
}
		
// 此時驗證碼成功
System.out.println("驗證碼成功");
// 避免重複提交  去除Session中這一次驗證碼
req.getSession().removeAttribute("RANDOMCODE_IN_SESSION");

// 繼續驗證用戶名和密碼  ....

驗證碼驗證成功之後 要銷毀Session中這次的驗證碼(驗證碼一次性使用) 避免重複提交

// 避免重複提交  去除Session中這一次驗證碼
req.getSession().removeAttribute("RANDOMCODE_IN_SESSION");

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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維修中心

.NET 5 嘗鮮 – 開源項目TerminalMACS WPF管理端支持.NET 5

.NET 5 嘗鮮 – 開源項目TerminalMACS WPF管理端支持.NET 5

一個使用 Prism 作為模塊化框架、基於多個開源控件庫作為UI控件選擇、集成開源 UI 界面設計的 .NET 5 WPF 客戶端項目。

  • 項目名稱:TerminalMACS WPF管理端
  • 項目開源地址:
    • Github:https://github.com/dotnet9/TerminalMACS.ManagerForWPF
    • Gitee:https://gitee.com/dotnet9/TerminalMACS.ManagerForWPF
  • 作者:Dotnet9

1. 特性

  • 使用 .NET 5 開發,體驗最新 .NET 平台(和 .NET Core 3.1 無縫兼容)

.NET 5 是 .NET Framework 和 .NET Core 的未來,最終將成為一個統一平台,.NET5將包含ASP.NET核心、實體框架核心、WinForms、WPF、Xamarin 和 ML.NET。

  • 基於 Prism 8 搭建模塊化框架,方便程序擴展

Prism為程序設計提供指導,旨在幫助用戶更加容易的設計和構建豐富、靈活、易於維護WPF桌面應用程序。Prism使用設計模式(如MVVM,複合視圖,事件聚合器),幫助你創建一個松耦合的程序。遵循這些設計模式原則,將目標程序解耦成獨立的模塊。這些類型的應用程序被稱為複合應用程序。

  • 已使用或即將使用到多個開源WPF控件庫

    • MaterialDesignInXamlToolkit
    • HandyControl
    • PanuonUI.Silver
    • AduSkin。

參考以上多種開源 WPF UI 庫,多個選擇,開發 WPF 項目更方便。

  • ECharts

界面設計有使用到ECharts,使用WPF WebBrowser控件加載html的方式

ECharts:pie-doughnut

  • 本地化支持

  • 動態國際化支持

  • 支持主題色動態切換

2. 支持環境

  • .NET 5.0。

3. 當前版本

0.1

4. 鏈接

  • 官方網站:Dotnet9

5. 項目界面截圖

5.1. 關於

5.2. 首頁模塊

正在開發中…

5.3. 服務端模塊

正在開發中…

5.4. 客戶端模塊

正在開發中…

5.5. 測試案例

收集全球優秀的開源WPF界面設計,實時收集、實時添加更新,下面是部分實例截圖:

登錄註冊分類 1

  1. 簡單登錄窗體設計1

參考視頻:C# WPF Material Design UI: Login Window

參考源碼:Login2

  1. 簡單登錄窗體設計2

參考視頻:C# WPF Material Design UI: Login Window

參考源碼:Login1

  1. 美食應用登錄

參考視頻:WPF Food App Login UI Material Design [Speed Design]

菜單類 2

  1. 抽屜式菜單

參考視頻:C# WPF Material Design UI: Animated Colorful Navigation Drawer

參考源碼:AnimatedColorfulMenu

  1. 菜單切換用戶控件

參考視頻:C# WPF Material Design UI: Fast Food Sales

參考源碼:Pizzaria1

  1. 菜單切換動畫

參考視頻:C# WPF Material Design UI: Animated Menu

參考源碼:AnimatedMenu1

其他界面設計 3

  1. 移動應用儀錶盤

參考視頻:WPF Dashboard UI – Material Design [Speed Design]

參考源碼:WPF-Dashboard-UI-Material-Design-Concept

  1. 簡易儀錶盤2

參考視頻:WPF Dashboard UI – Material Design [Speed Design]

參考源碼:WPF-Dashboard-UI-Material-Design-Concept

ECharts:pie-doughnut

  1. Instagram重新設計

參考視頻:C# WPF Material Design UI: Redesign Instagram

參考源碼:Instagram

  1. LoLGoal

參考視頻:dotnet9

參考源碼:dotnet9

  1. 簡易音樂播放器1

參考視頻:C# WPF Material Design UI: Dashboard

參考源碼:Dashboard

  1. 百度地圖

通過WPF WebBrowser控件加載html5文件的形式加載百度地圖,使用JavaScript與C#互操作實現地圖交互。

  1. 聊天界面設計

參考視頻:

  • C# WPF Design UI – 1/3 – Contact List
  • C# WPF Design UI – 2/3 – Profile
  • C# WPF Design UI – 3/3 – Chat

參考源碼:Chat

  1. 計算器

參考視頻:

  • Calcalator

關注Dotnet9,分享更多好文
如果本文對你有用,歡迎轉載,Dotnet9對應原文有markdown格式原文分享下載哦。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

RabbitMQ入門,我是動了心的

人一輩子最值得炫耀的不應該是你的財富有多少(雖然這話說得有點違心,呵呵),而是你的學習能力。技術更新迭代的速度非常快,那作為程序員,我們就應該擁有一顆擁抱變化的心,积極地跟進。

在 RabbitMQ 入門之前,我已經入門了 Redis、Elasticsearch 和 MongoDB,這讓我感覺自己富有極客精神,非常良好。

小夥伴們在繼續閱讀之前,我必須要聲明一點,我對 RabbitMQ 並沒有進行很深入的研究,僅僅是因為要用,就學一下。但作為一名負責任的技術博主,我是動了心的,這篇入門教程,小夥伴們讀完后絕對會感到滿意,忍不住無情地點贊,以及赤裸裸地轉發。

當然了,小夥伴們遇到文章中有錯誤的地方,不要手下留情,可以組團過來捶我,但要保證一點,不要打臉,我怕毀容。

01、RabbitMQ 是什麼

首先,我知道,Rabbit 是一隻兔子(哎呀媽呀,忍不住秀了一波自己的英語功底),可愛的形象已經躍然於我的腦海中了。那 MQ 又是什麼呢?是 Message Queue 的首字母縮寫,也就是說 RabbitMQ 是一款開源的消息隊列系統。

RabbitMQ 的主要特點在於健壯性好、易於使用、高性能、高併發、集群易擴展,以及強大的開源社區支持。反正就是很牛逼的樣子。

九年前我做大宗期貨交易的時候,也需要消息推送,那時候還不知道去找這種現成的中間件,就用自定義的隊列實現,結果搞了不少 bug,有些到現在還沒有解決,真的是不堪回首的往事啊。

下圖是 RabbitMQ 的消息模型圖(來源於網絡,侵刪),小夥伴們來感受下。

1)P 是 Producer,代表生產者,也就是消息的發送者,可以將消息發送到 X

2)X 是 Exchange(為啥不是 E,我也很好奇),代表交換機,可以接受生產者發送的消息,並根據路由將消息發送給指定的隊列

3)Q 是 Queue,也就是隊列,存放交換機發送來的消息

4)C 是 Consumer,代表消費者,也就是消息的接受者,從隊列中獲取消息

聽我這樣一解釋,是不是對 RabbitMQ 的印象就很具象化了?小夥伴們,學起來吧!

02、安裝 Erlang

咦,怎麼不是安裝 RabbitMQ 啊?先來看看官方的解釋。

英文看不太懂,沒關係,我來補充兩人話。RabbitMQ 服務器是用 Erlang 語言編寫的,它的安裝包里並沒有集成 Erlang 的環境,因此需要先安裝 Erlang。小夥伴們不要擔心,Erlang 安裝起來沒有任何難度。

Erlang 下載地址如下:

https://erlang.org/download/otp_versions_tree.html

最新的版本是 23.0.1,我選擇的是 64 位的版本,104M 左右。下載完就可以雙擊運行安裝,傻瓜式的。

需要注意的是,我安裝的過程中,電腦重啟了一次,好像要安裝一個什麼庫,重啟之前忘記保存圖片了(sorry)。重啟后,重新雙擊運行 otp_win64_23.0.1.exe 文件完成 Erlang 安裝。

03、安裝 RabbitMQ

Erlang 安裝成功后,就可以安裝 RabbitMQ 了。下載地址如下所示:

https://www.rabbitmq.com/install-windows.html

找到下圖中的位置,選擇紅色框中的文件進行下載。

安裝包只有 16.5M 大小,還是非常輕量級的。下載完后直接雙擊運行 exe 文件就可以傻瓜式地安裝了。

安裝成功后,就可以將 RabbitMQ 作為 Windows 服務啟動,可以從“開始”菜單管理 RabbitMQ Windows 服務。

點擊「RabbitMQ Command Prompt (sbin dir)」,進入命令行,輸入 rabbitmqctl.bat status 可確認 RabbitMQ 的啟動狀態。

可以看到 RabbitMQ 一些狀態信息:

  • 進程 ID,也就是 PID 為 2816
  • 操作系統為 Windows
  • 當前的版本號為 3.8.4
  • Erlang 的配置信息

命令行界面看起來不夠優雅,因此我們可以輸入以下命令來啟用客戶端管理 UI 插件:

rabbitmq-plugins enable rabbitmq_management

看到以下信息就可以確認插件啟用成功了。

在瀏覽器地址欄輸入 http://localhost:15672/ 可以進入管理端界面,如下圖所示:

04、在 Java 中使用 RabbitMQ

有些小夥伴可能會問,“二哥,我是一名 Java 程序員,我該如何在 Java 中使用 RabbitMQ 呢?”這個問題問得好,這就來,這就來。

第一步,在項目中添加 RabbitMQ 客戶端依賴:

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.9.0</version>
</dependency>

第二步,我們來模擬一個最簡單的場景,一個生產者發送消息到隊列中,一個消費者從隊列中讀取消息並打印。

官方對 RabbitMQ 有一個很好的解釋,我就“拿來主義”的用一下。在我上高中的年代,同學們之間最流行的交流方式不是 QQ、微信,甚至短信這些,而是書信。因為那時候還沒有智能手機,況且上學期間學校也是命令禁用手機的,所以書信是情感表達的最好方式。好懷念啊。

假如我向女朋友小巷寫了一封情書,內容如下所示:

致小巷
你好呀,小巷。
你走了以後我每天都感到很悶,就像堂吉訶德一樣,每天想念托波索的達辛妮亞。我現在已經養成了一種習慣,就是每兩三天就要找你說幾句不想對別人說的話。
。。。。。。
王二,5月20日

那這封情書要寄給小巷,我就需要跑到郵局,買上郵票,投遞到郵箱當中。女朋友要收到這封情書,就需要郵遞員盡心儘力,不要弄丟了。

RabbitMQ 就像郵局一樣,只不過處理的不是郵件,而是消息。之前解釋過了,P 就是生產者,C 就是消費者。

新建生產者類 Wanger :

public class Wanger {
    private final static String QUEUE_NAME = "love";
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, falsefalsefalsenull);
            String message = "小巷,我喜歡你。";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [王二] 發送 '" + message + "'");
        }
    }
}

1)QUEUE_NAME 為隊列名,也就是說,生產者發送的消息會放到 love 隊列中。

2)通過以下方式創建服務器連接:

ConnectionFactory factory = new ConnectionFactory();
try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

ConnectionFactory 是一個非常方便的工廠類,可用來創建到 RabbitMQ 的默認連接(主機名為“localhost”)。然後,創建一個通道( Channel)來發送消息。

Connection 和 Channel 類都實現了 Closeable 接口,所以可以使用 try-with-resource 語句,如果有小夥伴對 try-with-resource 語句不太熟悉,可以查看我之前寫的我去文章。

3)在發送消息的時候,必須設置隊列名稱,通過 queueDeclare() 方法設置。

4)basicPublish() 方法用於發布消息:

  • 第一個參數為交換機(exchange),當前場景不需要,因此設置為空字符串;
  • 第二個參數為路由關鍵字(routingKey),暫時使用隊列名填充;
  • 第三個參數為消息的其他參數(BasicProperties),暫時不配置;
  • 第四個參數為消息的主體,這裏為 UTF-8 格式的字節數組,可以有效地杜絕中文亂碼。

生產者類有了,接下來新建消費者類 XiaoXiang:

public class XiaoXiang {
    private final static String QUEUE_NAME = "love";
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, falsefalsefalsenull);
        System.out.println("等待接收消息");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [小巷] 接收到的消息 '" + message + "'");
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

1)創建通道的代碼和生產者差不多,只不過沒有使用 try-with-resource 語句來自動關閉連接和通道,因為我們希望消費者能夠一直保持連接,直到我們強制關閉它。

2)在接收消息的時候,必須設置隊列名稱,通過 queueDeclare() 方法設置。

3)由於 RabbitMQ 將會通過異步的方式向我們推送消息,因此我們需要提供了一個回調,該回調將對消息進行緩衝,直到我們做好準備接收它們為止。

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
    String message = new String(delivery.getBody(), "UTF-8");
    System.out.println(" [小巷] 接收到的消息 '" + message + "'");
};

basicConsume() 方法用於接收消息:

  • 第一個參數為隊列名(queue),和生產者相匹配(love)。

  • 第二個參數為 autoAck,如果為 true 的話,表明服務器要一次性交付消息。怎麼理解這個概念呢?小夥伴們可以在運行消費者類 XiaoXiang 類之前,先多次運行生產者類 Wanger,向隊列中發送多個消息,等到消費者類啟動后,你就會看到多條消息一次性接收到了,就像下面這樣。

等待接收消息
 [小巷] 接收到的消息 '小巷,我喜歡你。'
 [小巷] 接收到的消息 '小巷,我喜歡你。'
 [小巷] 接收到的消息 '小巷,我喜歡你。'
  • 第三個參數為 DeliverCallback,也就是消息的回調函數。

  • 第四個參數為 CancelCallback,我暫時沒搞清楚是幹嘛的。

在消息發送的過程中,也可以使用 RabbitMQ 的管理面板查看到消息的走勢圖,如下所示。

05、鳴謝

好了,我親愛的小夥伴們,以上就是本文的全部內容了,是不是看完后很想實操一把 RabbitMQ,趕快行動吧!如果你在學習的過程中遇到了問題,歡迎隨時和我交流,雖然我也是個菜鳥,但我有熱情啊。

另外,如果你想寫入門級別的文章,這篇就是最好的範例。

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

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

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

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

這篇文章,我們來談一談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維修中心