第五屆中國國際新能源汽車論壇2015

第五屆中國國際新能源汽車論壇2015強勢回歸,5月18、19日重聚上海

2014年5月18-19日∣中國•上海

新能源汽車“智”造時代

為了全面促進我國新能源汽車產業的快速發展,給全球新能源汽車產業的參與者提供一個溝通交流的平臺,在連續四屆精彩紛呈的新能源汽車論壇的基礎上,由中國產業海外發展和規劃協會、德國電動車協會、上海交大密西根學院主辦,希邁商務諮詢承辦,並得到了亞太電動車協會的傾情指導的“第五屆中國國際新能源汽車論壇2015”將於2015年5月18日至19日在上海隆重召開。屆時將有三百位行業領導出席本次論壇。

互聯網智慧時代強勢來襲,且正悄然改變著汽車這個傳統的行業。汽車互聯網時代的到來也促進著新能源汽車、汽車租賃等新形態汽車業務有效開展。汽車智慧化、新能源、輕量化成為汽車發展的新趨勢。

在過去的四屆新能源汽車論壇上,主辦方成功的邀請了包括國家發改委能源研究所、世界電動車協會、亞太電動車協會、世界氫能協會、世界分散式能源聯盟、中國工程院、美國工程師學會、國家863重大項目組、北京公共交通集團、上海交通大學、清華大學、國家電網、中國南方電網、普天新能源等在內的政府單位與研究機構,以及包括寶馬、賓士、奇瑞捷豹路虎、大眾、奇瑞、奧迪、比亞迪、上汽、北汽、東風、本田、豐田、現代、吉利、宇通客車等在內的知名整車商,共同參與和討論,對新能源汽車行業政策趨勢、技術路線、技術難點、基礎設施建設、商業模式等進行了積極的探討,取得了豐碩的成果,獲得了業內外人士的一致好評。

在嶄新的2015年,組委會為感謝業內外人士對系列論壇長期以來的關注和支援,將傾情奉上第五屆中國國際新能源汽車論壇2015,本次論壇將以新能源汽車“智”造時代為主題。現誠摯邀請您參加本次會議共創輝煌。屆時將邀請全球範圍內的整車製造商、電網公司、電力公司、電池廠商、零部件供應商、核心技術提供商和政府官員近三百位行業嘉賓一起,就新能源汽車產業面臨的機遇、挑戰和對策進行為期兩天的富有建設性和戰略性的討論。

往屆會議精彩回顧

第一屆中國國際新能源汽車論壇 第二屆中國國際新能源汽車論壇
第三屆中國國際新能源汽車論壇 第四屆中國國際新能源汽車論壇

部分往屆知名發言人:

會議亮點

  • 參會嘉賓:
    270+高度滿意的企業決策者,90+業內知名企業,20+國家和地區
  • 參會嘉賓分析:
    17%+來自各國政府部門及權威機構,25%+來自知名整車商
  • 演講嘉賓:
    30+世界新能源汽車行業知名發言嘉賓
  • 交流機會:
    16+小時的交流機會:圓桌討論、VIP午宴和開放式問答
  • 會議形式:
    2天高品質的乾貨分享,整車廠商親臨電池企業對接洽談會

會議結構

  上午 下午
大會第一天

“智”造時代政策預測、製造趨勢及試點城市推廣情況

工信部、科技部、交通部、發改委及地方政府的政策支持
汽車智慧化、新能源、輕量化發展趨勢
國內外整車商新能源新動向
試點城市推廣情況介紹及遇到問題解決措施

新能源汽車動力總成電氣化及商業模式

新能源汽車開發特點及供應配套要求
動力總成電氣化
無人駕駛及無線充電

大會第二天

電池及儲能的研發、製造趨勢

動力電池未來商業模式、技術路線探索
燃料電池及材料研發
動力電池最新技術工藝及典型生產設備選型

微型電動車市場准入

政策准入前景
市場格局變化
電池技術路線選擇

整車廠商-電池企業

對接洽談會

第五屆中國國際新能源汽車論壇2015特色

新能源汽車“智”造時代,第五屆中國國際新能源汽車論壇2015將會邀請政府主管機構、整車商及行業各個領域的領先者,對行業現有的發展訴求及發展願景規劃做出積極的探討。

想瞭解詳細內容,請登陸官方網站:
連絡人:Hill ZENG(曾先生)
電話:+86-21-6045 1760
傳真:+86-21-6047 5887
郵箱:

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

Tesla 預計 2017 年推新車 要讓中國消費者負擔得起

電動車厰特斯拉(Tesla)主管法律事務的全球副總裁 James Chen 說,雖然在中國建廠實現本地化生產已在規劃之中,但目前並沒有時間表,要等中國市場擴大到一定程度,才會邁出那一步。他表示,「盡可能擴大在華銷量,是特斯拉汽車今年在中國的核心任務。」   目前在中國銷售的 Model S 價格在 70 至 100 萬人民幣之間。特斯拉期望通過規模化生產降低成本,預計在 2017 年推出價格在 30 萬至 40 萬人民幣間的 Model 3,讓更多中國消費者能負擔得起。  

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

東京計畫奧運前 6,000 輛燃料電池車上路

  東京籌備 2020 年奧運會,擬砸 452 億日圓(台幣 122 億元)補助燃料電池車(FCV)和興建加氫站,目前正和豐田(Toyota)、本田(Honda)汽車協商合作,目標在東京奧運前,至少讓 6,000 輛燃料電池車上路趴趴走。   東京都廳能源部門規劃團隊主管 Makoto Fujimoto 表示,將在東京打造 35 座加氫站,正和豐田汽車、本田汽車協商,希望 2020 年前能讓 6,000 輛這種氫燃料電池車上路。Fujimoto 說,購買燃料電池車的日本民眾將獲得 300 萬日圓補助,其中 100 萬日圓由東京都廳補貼,200 萬日圓由中央政府補貼;到 2025 年時,盼在東京的氫燃料電池車將達 10 萬輛小客車、100 輛公車,且具有 80 個加氫站的目標。   此外,Fujimoto 指出,東京都廳將補助興建加氫站的費用 80%,業者負擔費用最高 1 億日圓,相當於一般蓋加油站的費用;若小企業想蓋加氫站,政府可能支付全部興建費用。   日本對燃料電池車的補助,超過中國、美國和歐洲提供購買電動車的補貼誘因,也較現行日本提供三菱汽車純電動車 i-MiEV 的補助 95 萬日圓,多出 2 倍以上。     (Source:)

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

【其他文章推薦】

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

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

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

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

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

第十六屆上海國際汽車工業展覽會

新能源展區

展會日期:2015年4月22日至29日
媒體日:2015年4月20-21日
展覽會地點:國家會展中心(上海)(盈港東路168號)

展覽會面積:30萬平方米(預計)
新能源展覽面積:5萬平方米(預計)
展覽會觀眾:100萬人次(預計)
新能源專業觀眾:3萬人次(預計)
展覽會宣傳:3,000家媒體及10,000名記者的熱情報導(預計)

主辦單位:
中國汽車工業協會
中國國際貿易促進委員會上海市分會
中國國際貿易促進委員會汽車行業分會

承辦單位:
上海市國際展覽有限公司
特別支援單位:
中國機械工業聯合會
歐洲地區協辦單位:
德國慕尼克國際博覽集團
IMAG國際交易會展覽會有限公司
支援單位:
中國汽車工程學會
新能源展區招展:
北京盛大超越國際展覽有限公司

展會概況:

Auto Shanghai 上海國際汽車展,創辦於1985年,至今已連續成功舉辦了十五屆。30年來,見證了中國汽車工業的欣欣向榮和中國汽車市場的迅猛發展,更是為中外車企構築起廣泛交流與合作的平臺,同時也為汽車愛好者打造了汽車文化的盛會。而上海國際汽車展本身也逐步成長為中國乃至全球最具讚譽的汽車大展之一。在我們迎來第十六屆上海國際汽車展之際,主承辦單位由衷地感謝中外汽車界、新聞界以及廣大熱情的觀眾對展會的厚愛與支持。

Auto Shanghai 上海國際汽車展,一貫秉承“以人為本”的服務理念,凝聚創新與突破,主承辦各方將銳意進取,不斷豐富展會內涵,樹立展會個性,提升展會品質,使上海國際汽車展成為中國乃至國際汽車工業最具品牌價值與影響力的展示、發佈及貿易平臺之一。

Auto Shanghai 上海國際汽車展,始終關注汽車工業的發展步伐,在汽車誕生逾一個世紀以來,科技不斷推動著汽車設計開啟新的篇章。智慧化、新能源、多功能將成為未來汽車發展的趨勢,我們相信科技的不斷進步將使未來的汽車更安全、更便捷、更舒適。這新一輪的技術變革,也讓未來的汽車工業及人類的汽車生活有了更多的可能。中國乃至全球汽車產業也因此將面臨新的機遇與創造。將于明年4月揭開序幕的第十六屆上海國際汽車展以“創新 ▪ 升級”為主題,屆時我們將再次領略汽車工業所迸發的無窮魅力,汽車科技所推動的技術變革,汽車文化所帶來的生活理念。同時,2015年4月的第十六屆上海國際汽車展將移師國家會展中心(上海)這一全新的舞臺舉辦。我們將在中外汽車鉅子攜手搭建的全新舞臺上感受到未來正在發生!

本屆車展是舉辦30年以來首次設立新能源展區,數家車企現場角力。新能源車包括混合動力汽車、電動汽車、燃料電池汽車等新能源汽車、驅動系統、充電設施、相關零部件、汽車設計等。

主承辦單位在長期的辦展歷程中,始終以簡潔、高效、創新的風格精心策劃和運營展會,使廣大觀眾、眾多媒體在車展現場感受到汽車文化的魅力與底蘊。我們熱情邀請您參與和光臨第十六屆上海國際汽車展。

讓我們共同期待2015上海國際汽車工業展覽會成為“創新 ▪ 升級”的舞臺!

新能源展區參展範圍:
節能汽車、純電動車,混合動力車,燃料電池車,輕型電動車,天然氣(液化氣)車,醇類燃料車及其他代用燃料車;
先進內燃機、高效變速器、整車優化設計等節能技術和產品;
電池,燃料電池,電池管理系統,電池的回收和包裝;
電力電容器,飛輪,能源管理系統;
電機,電機保護與控制技術;
充電器及充電站設備及相關配套專案企業、機構;
電動車及其他替代能源汽車的零部件及零部件總成;
加氣設備,儲運設備及技術;
網路管理,可再生能源發電,新型元器件和材料,輕量化;
智慧社區和電網,汽車共用和通訊服務;
檢測,維修,監控,實驗,安全防護裝備;
城市推廣應用示範展示、製造設備、工具及媒體等.

展會諮詢:
北京盛大超越國際展覽有限公司
連絡人:嶽巍                           
手  機:+86 135 5286 5285                         
電  話: +86 10 6329 0215                  
E-mail:                    

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

【其他文章推薦】

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

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

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

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

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

與特斯拉比拼!BMW 及福斯將在美國東西岸設 100 座快速充電站

  為了拉抬自家電動車買氣,BMW、福斯(Volkswagen)宣布與美國充電設施營運商 ChargePoint 合作,將在美國東岸與西岸設置共 100 座快速充電站。除了要藉此建立更完善的充電站網絡外,更希望能讓消費者不被充電便利性受限,而提高購買電動車的意願。   ChargePoint 是目前全美最大的充電設施營運商,在全國一共設置超過 2 萬座充電站,BMW、福斯與其合作,計畫在 2015 年底前,於美國東岸與西岸設置 100 座快速充電站,相當於平均每 50 英里(約 80.5 公里)就有一座快速充電站。   每座充電站配有 2 個輸出功率分別為 50 kW DC 或 24 kW DC 的 2 款高速充電樁,駕駛只要花 20 至 30 分鐘,就能讓電動車充滿 80% 的電量,相較於需花費好幾個小時充電的 ChargePoint 其他充電站,明顯快上許多。  
 

  (Source:)   據紐約時報報導,有別於特斯拉(Telsa)在全美設置的 358 座免費超級充電站(Supercharger),只能提供給自家車款使用,BMW 與福斯設置的充電站,則是與大多數電動車都相容,不過,使用這些充電站的駕駛就沒像特斯拉車主那麼好康了,得自掏腰包付「充電費」。   根據統計,2014 年美國電動車銷量約為 12 萬輛,較 2013 年成長 20%,但2014 年全美各類車種總銷量一共為 1,650 萬輛,電動車占比仍少得可憐。   或許是意識到快速充電樁數量稀少是影響電動車銷售量的一大因素,近來不少電動車製造商設法填補充電樁不足的缺口,希望能讓消費者不被充電便利性受限,好替電動車銷量帶來生機。   (首圖來源:)

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

簡介

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

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

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

前篇回顧

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

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

beanFactory的設計

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

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

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

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

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

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

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

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

從getBean(requiredType)方法開始

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

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

存在多個beanName怎麼辦

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

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

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

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

根據beanName和beanType獲取bean

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

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

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

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

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

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

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

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

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

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

獲取單例bean

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

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

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

創建單例bean

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

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

		RootBeanDefinition mbdToUse = mbd;

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

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

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

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

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

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

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

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

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

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

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

		return exposedObject;
	}

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

實例化

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

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

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

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

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

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

ConstructorArgumentValues和ArgumentsHolder

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

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

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

		@Nullable
		private Object value;

		@Nullable
		private String type;

		@Nullable
		private String name;

		@Nullable
		private Object source;

		private boolean converted = false;

}

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

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

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

    public final Object[] rawArguments;

    public final Object[] arguments;

    public final Object[] preparedArguments;

    public boolean resolveNecessary = false;

}

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

有參構造實例化

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

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

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

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

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

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

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

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

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

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

屬性裝配

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

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

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

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

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

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

幾個重要的知識點

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

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

propertyName的幾種形式

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

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

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

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

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

PropertyAccessor

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

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

在上面的例子中,

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

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

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

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

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

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

PropertyTokenHolder

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

	protected static class PropertyTokenHolder {

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

		public String actualName;

		public String canonicalName;

		@Nullable
		public String[] keys;
	}

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

applyPropertyValues

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

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

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

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

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

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

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

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

setPropertyValue

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

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

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

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

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

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

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

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

最後補充

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

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

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

最後,感謝閱讀。

相關源碼請移步: spring-beans

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

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

【其他文章推薦】

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

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

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

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

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

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

動態追蹤技術之SystemTap

SystemTap

從春哥(章亦春)那看到的關於SystemTap的介紹,讓我對動態追蹤這個功能頗為感興趣,覺得這是一個非常有用的功能。

SystemTap對內核及用戶態程序提供了動態追蹤功能,用戶可以自定探測事件來跟蹤程序的運行情況,如函數的調用路徑、CPU佔用和磁盤IO等一系列可以探測的情況。有了systemtap,可以在程序不修改代碼,甚至不用重啟就能分析出程序的運行情況。

配合火焰圖的可視化,對程序的性能分析極其有利。(放一個FlameGraph的官方圖片)

原理

SystemTap 基本思想是命名事件,併為它們提供處理程序。每當發生指定的事件時,內核都會將處理程序視為子例程運行,然後繼續運行。有一系列的事件,例如進入或退出函數,計時器到期或整個SystemTap會話的開始和停止。處理程序是一系列腳本語言語句,用於指定事件發生時要完成的工作。這項工作通常包含從事件上下文中提取數據,將其存儲到內部變量或打印結果。

SystemTap 的工作原理是將腳本翻譯成C語言,執行C編譯器創建一個內核模塊。當模塊被加載后,通過掛載到內核來激活所有的探測事件。然後,當事件發生再任何處理器上時,編譯后的處理程序就運行,最終,SystemTap會話停止,Hook取消,內核模塊被移除,整個過程由命令行程序stap驅動。

原理圖如下:

安裝

需要內核級別的支持,依賴了三個rpm下載鏈接,

  1. kernel-debuginfo-common
  2. kernel-debuginfo
  3. kernel-devel

這三個包的版本必須匹配當前內核的版本,比如我自己的內核版本是 3.10.0-327, 那麼以上三個包版本都必須保持一致。

安裝 SystemTap:

$ yum install systemtap

測試是否成功安裝:

$ stap -ve ‘probe begin{printf(“Hello, World\n”); exit();}’

正常的話會輸出 Hello, World,但是不出意外會出現版本不一致的情況:

ERROR: module version mismatch (#1 SMP Fri Nov 20 11:12:42 CST 2015 vs #1 SMP Thu Nov 19 22:10:57 UTC 2015), release 3.10.0-327.el7.x86_64

出現這個的情況是版本相同但是打包事件不相同的情況,修改這個時間和uname -a 中的時間保持一致。

$ rpm -ql kernel-devel | xargs grep UTS_VERSION 2>/dev/null

/usr/src/kernels/3.10.0-327.el7.x86_64/include/generated/compile.h:#define UTS_VERSION "#1 SMP Fri Nov 20 11:12:42 CST 2015"

再次運行那個hello測試,出現緩存的錯誤,刪除緩存文件

  1. /root/.systemtap/cache/34/stap_34443d4ad1fe1d37c0352b7b8c691aee_975.c
  2. /root/.systemtap/cache/34/stap_34443d4ad1fe1d37c0352b7b8c691aee_975.ko

追蹤

最簡單的探測類型就是跟蹤事件。Systemtap支持許多內置事件,所有的事件家族見 tapset

可以探測的的常用事件:

  • begin, systemtap 會話開始
  • end, systemtap 會話結束
  • kernel.function(“sys_xxx”), 系統調用xx的入口
  • kernel.function(“sys_xxx”).return, 系統調用xx的返回
  • timer.ms(300), 每300毫秒的定時器
  • timer.profile, 每個CPU上周期觸發的定時器
  • process(“a.out”).function(“foo*”), a.out 中函數名前綴為foo的函數信息
  • process(“a.out”).statement(“*@main.c:200”), a.out中文件main.c 200行處的狀態

常用的可打印值(具體見 tapset):

  • tid(), 當前線程id
  • pid(), 當前進程id
  • uid(), 當前用戶id
  • execname(), 當前進程名稱
  • cpu(), 當前cpu編號
  • gettimeofday_s(), 秒時間戳
  • get_cycles(), 硬件周期計數器快照
  • pp(), 探測點事件名稱
  • ppfunc(), 探測點觸發的函數名稱
  • $$var, 上下文中存在 $var,可以使用該變量
  • print_backtrace(), 打印內核棧
  • print_ubacktrace(), 打印用戶空間棧

SystemTap 腳本

stap 腳本簡單,語法類似C;

  • 註釋
# fuck
// fuck
/* fuck */
  • 函數
function foo() {
    // exit(); // 退出 systemtap 會話
}
  • 基本的 if/else/while/for 控制結構
function if_expr() {
    i = 0
    if (i == 1)
        printf("[if] i = %d\n", i);
    else
        printf("[else] i = %d\n", i);
}

function while_expr() {
    i = 0;
    while (i != 2)
        printf("[while] i = %d\n", i++);
}

function for_expr() {
    for (i = 0; i < 2; i++)
        printf("[for] i = %d\n", i);
}

  • 字符串比較,拼接,轉換
function str() {
    uid = uid();
    s_uid = sprint(uid);
    f_uid = "fuck" . s_uid
    printf("uid: %d-%s-%s\n", uid, s_uid, f_uid); // uid: 0-0-fuck0

    // exit();
}
  • 元組
global t; // 聲明元組
global tpl[400]; // 聲明一個400容量的元組

t["fuck"]++;  // t["fuck"] 初始值默認為0, ++ 變成 1
t["fuck"] = 4396; // 賦值為4396

tpl["fuck", pid()]++; // 兩個元素
tpl["shit", tid()]++;
  • 聚集統計
// 包含4個維度 @count @avg @min @max
global t;

t["fuck", tid()] <<< 1
t["fuck", pid()] <<< 1
t[execname(), tid()] <<< 1
t["fuck", 5487] <<< 2
t["fuck", 5487] <<< 3
t["fuck", 5487] <<< 1

具體結構如下:
t["fuck",5487] @count=3 @min=1 @max=3 @sum=6 @avg=2
t["fuck",26060] @count=2 @min=1 @max=1 @sum=2 @avg=1
t["stapio",26060] @count=1 @min=1 @max=1 @sum=1 @avg=1


// 遍歷(升序), 限制5次循環
foreach([key, value] in t+ limit 5)
    printf("%s: %d\n", key, value)

// 結果
stapio: 2571
fuck: 2571
fuck: 5487

應用

stap 常用命令

Usage: stap [options] FILE                    Run script in file.
   or: stap [options] -e SCRIPT               Run given script.
   or: stap [options] -l PROBE                List matching probes.
   or: stap [options] -L PROBE                List matching probes and local variables.
[options]
   -T TIME    terminate the script after TIME seconds

除了直接執行腳本文件外,另外一個比較有用的功能 -L -l 現象,列出可探測點及局部變量

  • 列出程序中的可探測點
// 截取部分~
[root@localhost stp]# stap -l 'process("/tmp/limlog/build/tests/LogTest").function("*")'
process("/tmp/limlog/build/tests/LogTest").function("write@/tmp/limlog/limlog/Log.cpp:107")
process("/tmp/limlog/build/tests/LogTest").function("~LimLog@/tmp/limlog/limlog/Log.cpp:213")
process("/tmp/limlog/build/tests/LogTest").function("~LogLine@/tmp/limlog/limlog/Log.cpp:341")
process("/tmp/limlog/build/tests/LogTest").function("~LogSink@/tmp/limlog/limlog/Log.cpp:59")
process("/tmp/limlog/build/tests/LogTest").function("~_Impl@/usr/include/c++/4.8.2/thread:107")
process("/tmp/limlog/build/tests/LogTest").function("~_Impl_base@/usr/include/c++/4.8.2/thread:97")
  • 列出程序中的可探測點及局部變量(前綴為$)
[root@localhost stp]# stap -L 'process("/tmp/limlog/build/tests/LogTest").function("*")'
process("/tmp/limlog/build/tests/LogTest").function("id@/usr/include/c++/4.8.2/thread:73") $this:class id* const
process("/tmp/limlog/build/tests/LogTest").function("incConsumable@/tmp/limlog/limlog/Log.cpp:313") $this:class LimLog* const $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("incConsumablePos@/tmp/limlog/limlog/Log.cpp:135") $this:class BlockingBuffer* const $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("incConsumablePos@/tmp/limlog/limlog/Log.cpp:460") $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("insert@/usr/include/c++/4.8.2/bits/basic_string.h:1319") $__c:char $__n:size_type $__pos:size_type $this:class basic_string<char, std::char_traits<char>, std::allocator<char> >* const

有這個功能,我們就可以看到函數所在源文件中的位置及可以根據的局部變量。

systemtap 腳本

探針事件的關鍵字是 probe, 由 probe 引出需要跟蹤的事件

// 腳本執行后显示 === begin ===
probe begin {
    printf("=== begin ===\n");
}

根據上述的可跟蹤的事件

  • begin/end, 分別是systemtap會話的起始和結尾
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap

probe begin {
    printf("=== begin ===\n");
}

probe end {
    printf("=== end ===\n");
}

// 執行腳本
[root@localhost stp]# stap foo.stp -T 1
=== begin ===
=== end ===
  • kernel.function(“sys_xxx”), 系統調用
// 調用open系統調用
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap

probe kernel.function("sys_open").call {
    printf("%s call %s\n", execname(), ppfunc());
}

// open系統調用返回
probe kernel.function("sys_open").call {
    printf("%s call %s over\n", execname(), ppfunc());
}

[root@localhost stp]# stap foo.stp -T 1
sh call SyS_open
sh call SyS_open over
sh call SyS_open
sh call SyS_open over
sh call SyS_open
sh call SyS_open over
  • 定時器調用
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap

// 定時調用函數
probe timer.ms(500) {
    printf("now: %d\n", gettimeofday_s());
}

[root@localhost stp]# stap foo.stp -T 3
now: 1593141081
now: 1593141081
now: 1593141082
now: 1593141082
now: 1593141083
  • 定時cpu採樣
// 取自春哥的sample-bt示例代碼,定時調用棧取樣
global bts;

probe timer.profile {
    if (pid() == 5291)
        bts[backtrace(), ubacktrace()] <<< 1
}

probe timer.s(10) {
    foreach([k, u] in bts-) {
        print_stack(k);
        print_ustack(u);
        printf("\\t%d\\n", @count(bts[k, u]));
    }
    exit();
}

// 結果為16進制地址,截取一部分數據,這部分數據需要進一步加工統計
[root@localhost stp]# stap foo.stp
 0xffffffff810d6244 : 0xffffffff810d6244
 0xffffffff810475fa : 0xffffffff810475fa
\t1\n 0xffffffffa0139f0d : 0xffffffffa0139f0d [xfs]
  • 指定程序中的函數事件(需要運行程序)
// 打印程序 LogTest 所有執行的函數
[root@localhost stp]# cat foo.stp
probe process("/tmp/limlog/build/tests/LogTest").function("*") {
    printf("function: %s\n", ppfunc());
}

// 庫函數也加載進來了
[root@localhost stp]# stap foo.stp
function: offsetOfPos
function: __distance<char*>
function: operator+<long int, std::ratio<1l, 1000000000l>, long int, std::ratio<1l, 1000000l> >
function: operator<<
  • 指定程序指定文件中的可追蹤事件
//打印 LogTest 程序屬於 Log.cpp 中的函數信息
[root@localhost stp]# cat foo.stp
probe process("/tmp/limlog/build/tests/LogTest").statement("*@Log.cpp") {
    printf("function: %s\n", ppfunc());
}

// 過濾了庫函數,只留下Log.cpp中的函數執行
[root@localhost stp]# stap foo.stp
function: singleton
function: produce
function: produce
function: produce
function: produce
function: used
function: used
function: consumable
function: operator<<
function: ~LogLine
  • 上下文變量
// 獲取上下文變量
[root@localhost stp]# stap -L 'process("/tmp/limlog/build/tests/LogTest").statement("*@Log.cpp")'
process("/tmp/limlog/build/tests/LogTest").statement("LimLog@/tmp/limlog/limlog/Log.cpp:185") $this:class LimLog* const
process("/tmp/limlog/build/tests/LogTest").statement("LogLine@/tmp/limlog/limlog/Log.cpp:336") $this:class LogLine* const $level:enum LogLevel $loc:struct LogLoc const&
process("/tmp/limlog/build/tests/LogTest").statement("append@/tmp/limlog/limlog/Log.cpp:346") $this:class LogLine* const $data:char const* $n:size_t
process("/tmp/limlog/build/tests/LogTest").statement("consumable@/tmp/limlog/limlog/Log.cpp:140") $this:class BlockingBuffer const* const
process("/tmp/limlog/build/tests/LogTest").statement("consume@/tmp/limlog/limlog/Log.cpp:146") $this:class BlockingBuffer* const $to:char* $n:uint32_t

// 這裏我們追蹤 consume 函數中的兩個參數
[root@localhost stp]# cat foo.stp
probe process("/tmp/limlog/build/tests/LogTest").function("consume") {
    printf("func = %s, $var(n) = %d, $var(to) = %p\n", ppfunc(), $n, $to);
}

[root@localhost stp]# stap foo.stp
func = consume, $var(n) = 406, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 203, $var(to) = 0x7f902a94d1a6
func = consume, $var(n) = 790, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 3319, $var(to) = 0x7f902a94d326
func = consume, $var(n) = 4235, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 4235, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 2326, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 8470, $var(to) = 0x7f902a94d010

示例分析

在探測點被執行時打印這種簡單的用法外,還能進一步做一些其它的事情。

  1. 統計某一時間段內運行次數top k的函數
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap

global top_funcs;

probe begin {
    printf("=== begin ===\n");
}

probe process("/tmp/limlog/build/tests/LogTest").statement("*@Log.cpp") {
    top_funcs[ppfunc()]++;
}

probe end {
    foreach(func in top_funcs- limit 10)
        printf("%s: %d\n", func, top_funcs[func]);

    printf("=== end ===\n");
}

// LogTest 在3秒內執行最多的10個函數及次數
[root@localhost stp]# stap foo.stp -T 3
=== begin ===
produce: 1152
append: 720
singleton: 646
operator<<: 608
produceLog: 576
used: 576
consumable: 359
incConsumablePos: 64
getLogLevel: 32
LogLine: 32
=== end ===
  1. 統計系統調用某一時間段內被調用最頻繁的k個程序及次數
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap

global top_exec;

probe begin {
    printf("=== begin ===\n");
}

probe kernel.function("sys_write") {
    top_exec[execname()]++;
}

probe end {
    foreach(exec in top_exec- limit 10)
        printf("%s: %d\n", exec, top_exec[exec]);

    printf("=== end ===\n");
}

[root@localhost stp]# stap foo.stp -T 3
=== begin ===
qemu-kvm: 2948
sync: 2174
virsh: 673
libvirtd: 305
route: 28
grep: 21
ps: 21
ssh: 16
python: 16
nginx: 16
=== end ===
  1. 打印函數調用層次(用戶程序)

調用層次需要藉助 thread_indent 來打印合適的縮進,另外需要設置函數入口(call)和返回(return)的探測點。函數命名不足以使用function通配符來匹配,而 statement 雖然可以匹配我們想要的結果(過濾第三方函數),但是不支持 call 和return。雖然有一個比較傻但是的確可行的方式是,在腳本裏面手動寫入這些函數。

// 先取出需要關注的函數名稱
stap -L 'process("/tmp/limlog/build/tests/LogTest").statement("*@*.cpp")' |  sed 's/.*statement(\"\(.*\)@.*\".*/\1/g'  | sort -u

腳本如下:

#!/usr/bin/env stap

global f;

function init() {
    f["append"] = 1;
    f["benchmark"] = 1;
    f["consumable"] = 1;
    f["consume"] = 1;
    f["date"] = 1;
    f["datetime"] = 1;
    f["formatTimestamp"] = 1;
    f["getLogLevel"] = 1;
    f["gettid"] = 1;
    f["i16toa"] = 1;
    f["i2a"] = 1;
    f["i32toa"] = 1;
    f["i64toa"] = 1;
    f["incConsumable"] = 1;
    f["incConsumablePos"] = 1;
    f["LimLog"] = 1;
    f["~LimLog"] = 1;
    f["listStatistic"] = 1;
    f["log_10_diff_element_len"] = 1;
    f["log_10_diff_element_str"] = 1;
    f["log_10_diff_element_x1"] = 1;
    f["log_16_same_element_x6"] = 1;
    f["log_1_same_element_x6"] = 1;
    f["log_4_same_element_x6"] = 1;
    f["LogLine"] = 1;
    f["~LogLine"] = 1;
    f["LogSink"] = 1;
    f["~LogSink"] = 1;
    f["main"] = 1;
    f["now"] = 1;
    f["operator<<"] = 1;
    f["produce"] = 1;
    f["produceLog"] = 1;
    f["rollFile"] = 1;
    f["setLogFile"] = 1;
    f["setLogLevel"] = 1;
    f["setRollSize"] = 1;
    f["singleton"] = 1;
    f["sink"] = 1;
    f["sinkThreadFunc"] = 1;
    f["stringifyLogLevel"] = 1;
    f["test_blocking_buffer"] = 1;
    f["test_itoa"] = 1;
    f["test_timestamp"] = 1;
    f["time"] = 1;
    f["u16toa"] = 1;
    f["u2a"] = 1;
    f["u32toa"] = 1;
    f["u64toa"] = 1;
    f["used"] = 1;
    f["write"] = 1;
}

probe begin {
    printf("=== begin ===\n");
    init();
}

probe process("/tmp/limlog/build/tests/LogTest").function("*").call {
    fn = ppfunc()
    if (f[fn] == 1)
        printf("%s -> %s\n", thread_indent(4), ppfunc());
}

probe process("/tmp/limlog/build/tests/LogTest").function("*").return {
    fn = ppfunc()
    if (f[fn] == 1)
        printf("%s <- %s\n", thread_indent(-4), ppfunc());
}

probe end {
    printf("=== end ===\n");
}

函數的部分調用樹如下所示:

=== begin ===
     0 LogTest(25381):    -> main
   101 LogTest(25381):        -> setLogFile
   111 LogTest(25381):            -> singleton
   121 LogTest(25381):                -> LimLog
   129 LogTest(25381):                    -> LogSink
   270 LogTest(25381):                        -> now
   282 LogTest(25381):                        <- now
   288 LogTest(25381):                        -> date
   296 LogTest(25381):                            -> datetime
   453 LogTest(25381):                            <- datetime
   472 LogTest(25381):                        <- date
   478 LogTest(25381):                    <- LogSink
   576 LogTest(25381):                <- LimLog
   586 LogTest(25381):            <- singleton
   603 LogTest(25381):            -> rollFile
     0 LogTest(25382):    -> sinkThreadFunc
   650 LogTest(25381):            <- rollFile
   ···

樹狀調用可以很清楚的展示出來程序的執行邏輯,甚至可以看到多線程的處理:sinkThreadFunc 和 rollFile 之間的跨度。

  1. 開源庫對systemtap的集成
    像libguestfs 和libvirt都有支持,恰好工作環境這兩個庫也經常有一些難以排查的問題出現

觀察 libguestfs 的啟動過程及耗時,libvirt 支持function 和mark(有5個,大致邏輯上的埋點)

#! /usr/bin/env stap

global start_time, prev;

function display_time () {
  now = gettimeofday_us ()
  delta = (prev > 0 ) ? now - prev : 0
  printf ("%18d (+%10d): ", now - start_time, delta)
  prev = now
}

probe begin {
  start_time = gettimeofday_us ()
  prev = 0
  printf ("%-18s (+%10s): %s\n", "# time_usec",
          "delta usec", "event")
}

probe process("/usr/lib*/libguestfs.so.0*").function("*")
{
  display_time()
  printf ("%s\n", ppfunc())
}

打印的結果,如果使用 libguestfs-test-tools 執行失敗,可以根據產生的函數進入源代碼中進行排查,縮小排查的範圍

[root@localhost stp]# stap libguestfs.stp
# time_usec        (+delta usec): event
           1880754 (+         0): _init
           1880765 (+        11): frame_dummy
           1880768 (+         3): register_tm_clones
           1880772 (+         4): compile_regexp_re_hostname_port
           1880804 (+        32): init_libguestfs
           1881074 (+       270): guestfs_int_init_direct_backend
           1881079 (+         5): guestfs_int_register_backend
           1881083 (+         4): guestfs_int_init_libvirt_backend
           1881085 (+         2): guestfs_int_register_backend
           1881088 (+         3): guestfs_int_init_uml_backend
           ···

而 libvirt 中 example/ 目錄中就有包含一些關於 systemtap 的腳本文件。

火焰圖的生成

在函數調用樹之上更進一步,加入樣本的頻率得到svg格式的火焰圖,用戶可以點擊不同的圖層來查看更詳細的信息。

火焰圖工具為brendangregg開發的開源項目FlameGraph,配合春哥寫的systemtap工具,用一段簡單的shell腳本如下,可以得到一張svg的圖片

#!/bin/bash

# 火焰圖工具的路徑
FlameGraph_PATH="/FlameGraph"

# 春哥的棧收集工具
OR_Stap_PATH="/home/zxh/dev/openresty-systemtap-toolkit"

if [ $# != 1 ] ; then
        echo "Usage: sh $0 PID"
        echo "  e.g.: sh $0 4396"
        exit 1;
fi

${OR_Stap_PATH}/sample-bt -p $1 -t 180 -u > /tmp/bt-sample
${FlameGraph_PATH}/stackcollapse-stap.pl /tmp/bt-sample > /tmp/bt_stap.out
${FlameGraph_PATH}/flamegraph.pl /tmp/bt_stap.out > backtrace.svg

rm -rf /tmp/bt-sample
rm -rf /tmp/bt_stap.out

CPU定時採樣,根據採樣生成的 backtrace.svg 如下,橫軸為CPU佔用的時間比例,縱軸為函數調用的深度:

這是一個日誌庫的benchmark(博客園不支持上傳svg格式),而主要耗時也在阻塞隊列的入隊上,符合預期。

rpm 鏈接

  1. https://mirrors.ocf.berkeley.edu/centos-debuginfo/7/x86_64/kernel-debuginfo-common-x86_64-3.10.0-327.el7.x86_64.rpm
  2. https://mirrors.ocf.berkeley.edu/centos-debuginfo/7/x86_64/kernel-debuginfo-3.10.0-327.el7.x86_64.rpm
  3. ftp://ftp.pbone.net/mirror/ftp.scientificlinux.org/linux/scientific/7.0/x86_64/updates/security/kernel-devel-3.10.0-327.el7.x86_64.rpm

參考

  1. systemtap 原理圖, https://www.ibm.com/developerworks/cn/linux/l-cn-systemtap3/index.html?ca=drs-
  2. systemtap 官方教程(推薦),https://sourceware.org/systemtap/tutorial/1_Introduction.html
  3. systemtap 內置函數庫 tapset,https://sourceware.org/systemtap/tapsets/index.html
  4. 春哥的一系列stap工具,https://github.com/openresty/openresty-systemtap-toolkit
  5. 火焰圖工具,https://github.com/brendangregg/FlameGraph

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

設計模式系列之代理模式(Proxy Pattern)——對象的間接訪問

說明:設計模式系列文章是讀劉偉所著《設計模式的藝術之道(軟件開發人員內功修鍊之道)》一書的閱讀筆記。個人感覺這本書講的不錯,有興趣推薦讀一讀。詳細內容也可以看看此書作者的博客https://blog.csdn.net/LoveLion/article/details/17517213

模式概述

近年來,代購已逐步成為电子商務的一個重要分支。何謂代購,簡單來說就是找人幫忙購買所需要的商品,當然你可能需要向實施代購的人支付一定的費用。代購通常分為兩種類型:一種是因為在當地買不到某件商品,又或者是因為當地這件商品的價格比其他地區的貴,因此託人在其他地區甚至國外購買該商品,然後通過快遞發貨或者直接攜帶回來;還有一種代購,由於消費者對想要購買的商品相關信息的缺乏,自已無法確定其實際價值而又不想被商家宰,只好委託中介機構幫其講價或為其代買。

在軟件開發中,也有一種設計模式可以提供與代購類似的功能。由於某些原因,客戶端無法直接訪問某個對象或訪問某個對象存在困難時,可以通過一個稱之為“代理”的第三者來實現間接訪問,該方案對應的設計模式被稱為代理模式。

模式定義

代理模式(Proxy Pattern):給某一個對象提供一個代理或佔位符,並由代理對象來控制對原對象的訪問。

代理模式是一種對象結構型模式。在代理模式中引入了一個新的代理對象,代理對象在客戶端對象和目標對象之間起到中介的作用,它去掉客戶不能看到的內容和服務或者增添客戶需要的額外的新服務。

模式結構圖

代理模式的結構比較簡單,其核心是代理類,為了讓客戶端能夠一致性地對待真實對象和代理對象,在代理模式中引入了抽象層,代理模式結構如下圖所示:

代理模式包含如下三個角色:

  • Subject(抽象主題角色):它聲明了真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。

  • Proxy(代理主題角色):它包含了對真實主題的引用,從而可以在任何時候操作真實主題對象;在代理主題角色中提供一個與真實主題角色相同的接口,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真實主題的使用,負責在需要的時候創建和刪除真實主題對象,並對真實主題對象的使用加以約束。通常,在代理主題角色中,客戶端在調用所引用的真實主題操作之前或之後還需要執行其他操作,而不僅僅是單純調用真實主題對象中的操作。

  • RealSubject(真實主題角色):它定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接調用真實主題角色中定義的操作。

模式偽代碼

代理模式的結構圖比較簡單,但是在真實的使用和實現過程中要複雜很多,特別是代理類的設計和實現。

抽象主題類聲明了真實主題類和代理類的公共方法,它可以是接口、抽象類或具體類,客戶端針對抽象主題類編程,一致性地對待真實主題和代理主題,典型的抽象主題類代碼如下:

public interface Subject {
    void request();
}

真實主題類實現了抽象主題類,提供了業務方法的具體實現,其典型代碼如下:

public class RealSubject implements Subject {
    @Override
    public void request() {
        //業務方法具體實現代碼
    }
}

代理類也是抽象主題類的子類,它維持一個對真實主題對象的引用,調用在真實主題中實現的業務方法,在調用時可以在原有業務方法的基礎上附加一些新的方法來對功能進行擴充或約束,最簡單的代理類實現代碼如下:

public class Proxy implements Subject {
    // 維持一個對真實主題對象的引用
    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    public void preRequest() {
        // ...
    }

    public void postRequest() {
        // ...
    }

    @Override
    public void request() {
        preRequest();
        // 調用真實主題對象的方法
        realSubject.request();
        postRequest();
    }
}

模式應用

像上面代理類所實現的接口和所代理的方法都在代碼中寫死,被稱之為靜態代理。如果要為不同類的不同方法生成靜態代理,代理類的數量將會發生爆炸。Java中也提供了對動態代理的支持。所謂動態代理(Dynamic Proxy),是指系統運行時動態生成代理類。

JDK中提供的動態代理只能代理一個或者多個接口,如果需要動態代理具體類或者抽象類,可以使用CGLib(Code Generation Library)等工具。CGLib是一個功能較為強大、性能也較好的代碼生成包,在許多AOP框架中得到廣泛應用。後面我會專門寫一篇探究動態代理實現的博客,將全面細緻地介紹動態代理。

模式總結

代理模式是常用的結構型設計模式之一,它為對象的間接訪問提供了一個解決方案,可以對對象的訪問進行控制。代理模式類型較多,其中遠程代理、虛擬代理、保護代理等在軟件開發中應用非常廣泛。

主要優點

(1) 能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度。

(2) 客戶端可以針對抽象主題角色進行編程,增加和更換代理類無須修改源代碼,符合開閉原則,系統具有較好的靈活性和可擴展性。

此外,不同類型的代理模式也具有獨特的優點,例如:

(1) 遠程代理為位於兩個不同地址空間對象的訪問提供了一種實現機制,可以將一些消耗資源較多的對象和操作移至性能更好的計算機上,提高系統的整體運行效率。

(2) 虛擬代理通過一個消耗資源較少的對象來代表一個消耗資源較多的對象,可以在一定程度上節省系統的運行開銷。

(3) 緩衝代理為某一個操作的結果提供臨時的緩存存儲空間,以便在後續使用中能夠共享這些結果,優化系統性能,縮短執行時間。

(4) 保護代理可以控制對一個對象的訪問權限,為不同用戶提供不同級別的使用權限。

主要缺點

(1) 由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢,例如保護代理。

(2) 實現代理模式需要額外的工作,而且有些代理模式的實現過程較為複雜,例如遠程代理。

適用場景

代理模式的類型較多,不同類型的代理模式有不同的優缺點,它們應用於不同的場合:

(1) 當客戶端對象需要訪問遠程主機中的對象時可以使用遠程代理。

(2) 當需要用一個消耗資源較少的對象來代表一個消耗資源較多的對象,從而降低系統開銷、縮短運行時間時可以使用虛擬代理,例如一個對象需要很長時間才能完成加載時。

(3) 當需要為某一個被頻繁訪問的操作結果提供一個臨時存儲空間,以供多個客戶端共享訪問這些結果時可以使用緩衝代理。通過使用緩衝代理,系統無須在客戶端每一次訪問時都重新執行操作,只需直接從臨時緩衝區獲取操作結果即可。

(4) 當需要控制對一個對象的訪問,為不同用戶提供不同級別的訪問權限時可以使用保護代理。

(5) 當需要為一個對象的訪問(引用)提供一些額外的操作時可以使用智能引用代理。

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

數據結構與算法(七):迷宮回溯和八皇后問題

一、迷宮回溯問題

1.問題

一個7*8的數組模擬迷宮,障礙用1表示,通路使用0表示,給定起點(1,1)和終點(6,5),要求給出起點到終點的通路

2.解題思路

  1. 首先,我們需要給程序一個尋向的基本策略,我們先假定尋向順序為“下-右-上-左”,也就是說從起點出發,先往下走,往下走不通就往右…..以此類推
  2. 然後我們需要給走過的路一個標記,暫記為2
  3. 而當從一個方向走到一個只能原路返回的死衚衕時,就給這段路標記為3
  4. 當抵達終點坐標(6,5)時程序結束

3.代碼實現

3.1生成地圖

/**
 * 創建一個二維數組,用於模擬8*7迷宮
 * 使用1表示不可通過的實心方塊,0表示可通過磚塊
 * (6,5)為默認終點,(1,1)為默認起點
 * @return
 */
public static int[][] getMap(){
    int[][] map = new int[8][7];
    //上下全置為1
    for(int i = 0;i <7 ;i++){
        map[0][i] = 1;
        map[7][i] = 1;
    }
    //左右全置為1
    for(int i = 0;i < 8;i++){
        map[i][0] = 1;
        map[i][6] = 1;
    }
    //設置擋板
    map[3][1] = 1;
    map[3][2] = 1;

    //輸出地圖
    System.out.println("地圖的初始情況:");
    showMap(map);

    return map;
}

/**
 * 展示地圖
 * @param map
 */
public static void showMap(int[][] map) {
    for(int i = 0;i < 8;i++){
        for(int j = 0;j < 7;j++){
            System.out.print(map[i][j] + " ");
        }
        System.out.println();
    }
}

3.2 尋路邏輯的實現

對於這個尋路程序,我們可以看見,往四個方向走的過程實際上除了方向外動作上是一樣的;而具體分析同一個方向,每走過一個坐標的動作也是一樣的,我們對流程進行分析:

  1. 出發,先往下走,判斷下一格有沒有障礙(int[x][y]==1
  2. 如果沒有障礙,就繼續往下走,然後重複步驟1到碰到障礙為止
  3. 如果有障礙,就按“下-右-上-左”的順序,換個方向,然後重複步驟1到碰到障礙為止
  4. 如果找到了(6,5)就結束

表現為代碼實際上就是一個遞歸的過程:

  • 找路是方法體
  • 找到了(6,5)或者死衚衕是終止條件
/**
 * 給定起始點,根據地圖找路
 * 使用2表示可以走通的路,使用3表示走過但是不通的路
 * @param map 地圖二維數組
 * @param x 起始點橫坐標
 * @param y 起始點縱坐標
 * @return
 */
public static boolean findWay(int[][] map, int x, int y) {
    //如果走到了終點就終止
    if (map[6][5] == 2){
        return true;
    }else {
        //只有為0的路才能通過
        if (map[y][x] == 0) {
            //如果該點可以走通就打上標記
            map[y][x] = 2;
            if (findWay(map, x, y + 1)) {
                //向下遞歸
                return true;
            } else if (findWay(map, x + 1, y)) {
                //向右遞歸
                return true;
            } else if (findWay(map, x, y - 1)) {
                //向上遞歸
                return true;
            } else if (findWay(map, x - 1, y)) {
                //向左遞歸
                return true;
            } else {
                //都走不通說明是死衚衕
                map[y][x] = 3;
                return false;
            }
        }else {
            //不為0說明要麼是死路要麼是障礙
            return false;
        }
    }
}

3.3 運行結果

findWay()方法中的終止條件從map[6][5] == 2換成其他坐標即可更換終點位置,

棋盤大小和障礙物位置不影響findWay()方法尋路。

二、八皇后問題

1.問題

皇后問題,一個古老而著名的問題,是回溯算法的典型案例。該問題由國際西洋棋棋手馬克斯·貝瑟爾於 1848 年提出:

在 8×8 格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,求有多少種擺法?

2.解題思路

  1. 首先,我們先使用一個長度為8數組來表示八皇后的擺放位置,數組下標+1即表示棋盤的第幾行數組下標對應的存放的数字+1即為棋盤的第幾列。舉個例子:

    arr = {0,2,3,8,4,6,2,7}

    其中,元素0下標為0,即表示第一行第一列;元素2下標為1,即表示第二行第三列……以此類推。

  2. 任意假設任意坐標分標為(x1,y1),(x2,y2),也就是用數組表示為arr[x1]=y1,arr[x2]=y2的兩個皇后不允許在同一列,我們可以理解為:

    arr[x1] != arr[x2];

    而任意坐標的皇后不允許在同一斜線,即(x2-x1)=(y2-y1),也就是斜率不應當相同,我們可以理解為:

    Math.abs(x2-x1) != Math.abs(arr[x2]-arr[x1])

    (注:Math.abs()為求絕對值方法)

3.代碼實現

3.1 檢查擺放位置的代碼實現

在前面明確了如何用數組表示位置,以及如何檢查皇后是否允許擺放后,我們有如下代碼:

//表示皇后位置的數組
int[] arr = new int[8];

/**
 * 檢查第n個皇后是否與前面擺放的皇后衝突
 * @param n
 * @return
 */
public boolean check(int n) {
    //檢查第n層之前的皇后位置
    for (int i = 0; i < n; i++) {
        // arr[i] == arr[n] 檢查是否同一列
        // Math.abs(n - i) == Math.abs(arr[n] - arr[i]) 檢查是否同一斜線
        if (arr[i] == arr[n] ||
            Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
            return false;
        }
    }
    return true;
}

3.2 完整代碼

接着我們需要考慮如何使用遞歸方法來做到以下效果:

使用一個方法遍歷第n行的每一列,檢查每一列是否可以放置皇后:

  1. 如果可以放置皇后,將位置出入arr[n]中,然後遞歸調用自己,傳入n+1開始遍歷下一行…..以此類推
  2. 如果不可以放置皇后,就跳過該列檢查下一列,如果可以就重複步驟1
  3. 若n行中全部位置都不合適,則結束本層返回上一層n-1層,重複步驟1
  4. 如果最後n=8,即八個皇后全部放置完畢,記一次完成擺放,然後結束遞歸返回第一層,繼續檢查第一層的下一列

最終代碼實現結果如下:

/**
 * @Author:黃成興
 * @Date:2020-06-26 20:53
 * @Description:八皇后問題
 */
public class EightQueens {

    public static void main(String[] args) {
        EightQueens eightQueens = new EightQueens();
        eightQueens.set(0);
        System.out.println("共有擺法:" + eightQueens.count);
    }

    //記錄八皇後有幾種擺法
    int count = 0;

    //表示皇后位置的數組
    int[] arr = new int[8];

    /**
     * 擺放皇后
     * @param n 第幾個皇后
     */
    private void set(int n) {
        //如果放置好了第8個皇后
        if (n == 8){
            show();
            //記錄一種擺放方式
            count++;
            //回到第一層繼續遞歸
            return;
        }

        //遍歷第n行的每一列
        for (int i = 0; i < 8; i++) {
            //將該皇後放置在第n行第i列
            arr[n] = i;
            //檢查放置位置是否合適
            if (check(n)){
                //如果位置合適,就遞歸找下一個(n+1)皇后的擺放位置
                set(n + 1);
            }
            //如果位置不合適,就跳過這一列檢查下一列
        }
    }

    /**
     * 檢查第n個皇后是否與前面擺放的皇后衝突
     * @param n
     * @return
     */
    public boolean check(int n) {
        //檢查第n層之前的皇后位置
        for (int i = 0; i < n; i++) {
            // arr[i] == arr[n] 檢查是否同一列
            // Math.abs(n - i) == Math.abs(arr[n] - arr[i]) 檢查是否同一斜線
            if (arr[i] == arr[n] ||
                Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
                return false;
            }
        }
        return true;
    }
    

    /**
     * 展示某一擺法中八皇后的擺放位置
     */
    public void show() {
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();
    }
}

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

【其他文章推薦】

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

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

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

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

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

狀態機模式 與 ajax 的結合運用

太神奇了,昨晚做了個夢,夢中我悟出一個道理:凡是涉及到異步操作而且需要返回值的函數,一定要封裝成 Promise 的形式,假如返回值取決於多個異步操作的結果,那麼需要對每個異步操作進行狀態的設計,而且需要封裝一個 next 函數。,到了晚上才覺到很有意思,所以結合 ajax 設置最短返回時間和最大返回時間進行實踐:

  const PENDING = 'PENDING'
  const RESOLVED = 'RESOLVED'
  const REJECTED = 'REJECTED'
  const FULLFILLED = 'FULLFILLED'

  /**
   * @desc 異步操作模擬
   * @param time 響應時間
   * @param isError 是否拋錯
   */
  const mock = (time, isError) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (!isError) {
          resolve({ user: 'ManbaX' })
        } else {
          reject('request error')
        }
      }, time)
    })
  }

  /**
   * @desc 生產不同類型請求的工廠函數
   * @param time 響應時間
   * @param isError 是否拋錯
   */
  var RequestFactory = function (time, isError) {
    var request = function () {
      return new Promise((resolve, reject) => {
        var min = PENDING
        var max = PENDING
        var state = PENDING
        var res = null

        var next = function (name) {
          const cb = function () {
            if (state === RESOLVED) {
              resolve(res)
            } else {
              reject(res)
            }
          }
          if (name === 'res' && min === FULLFILLED) {
            cb()
          }

          if (name === 'min' && (state === RESOLVED || state === REJECTED)) {
            cb()
          }

          if (name === 'max' && state === PENDING) {
            reject('timeout')
          }
        }

        setTimeout(() => {
          min = FULLFILLED
          next('min')
        }, 500)

        setTimeout(() => {
          max = FULLFILLED
          next('max')
        }, 1000)

        mock(time, isError).then(data => {
          res = data
          state = RESOLVED
          next('res')
        }).catch(error => {
          res = error
          state = REJECTED
          next('res')
        })
      })
    }

    return request
  }

  // 不超時, 不返回錯誤
  console.time('r1')
  RequestFactory(200)().then(res => {
    console.log('data: ', res)
  }).finally(() => {
    console.timeEnd('r1')
  })
  
  // 不超時, 返回錯誤
  console.time('r2')
  RequestFactory(200, true)().catch(err => {
    console.log('error', err)
  }).finally(() => {
    console.timeEnd('r2')
  })

  // 超時
  console.time('r3')
  RequestFactory(2000)().catch(res => {
    console.log('error: ', res)
  }).finally(() => {
    console.timeEnd('r3')
  })

上面的運行結果符合預期,本來夢中還有另外一個有意思的東西,但是太模糊了就搞忘記了,下次一定早點記錄。

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

【其他文章推薦】

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

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

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

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

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