10-30萬熱門車調查!車展買車真的比4S店更便宜嗎?

10多萬最受消費者關注的車型莫過於思域。目前的銷售情況為:除非加裝精品,否則一車難求。而附近東本4S店當中,在整體的報價方面並沒有明顯出入(反正就是都沒有優惠)。但是不同的4S店之間在後期加裝精品的費用上也有所出入。

相信有不少人想知道:究竟什麼時候買車便宜?於是…

據說…

一年當中總有幾個時間段,是汽車市場的高峰期,譬如:

大節假日(譬如五一、國慶黃金周,放假放太久閑得無事就去買車唄)

金九銀十(不知道為什麼,每年的這個時候車賣得特別好,簡稱旺季)

春節前(回家過年買台車開回家)

除了以上這幾個高峰期以外,每當各個地方的車展,據說在車展買車甚至比在4S店買車更便宜!然而這是真的嗎?

由於車上現場車型太多,我們首先挑出30萬以內各個級別受到廣泛消費者關注的四款轎車車型。

調查報告還是從低開始!

先說說pOLO這款車吧,其實大眾pOLO已經征戰市場多年,而距離全新一代pOLO進入中國還有一段時間。對於現款車型來說,目前它依然保持着極高的關注度,而飛度便是它最大的對手,不過在整體的銷量上pOLO依然穩壓飛度一頭。最主要的原因在於算上優惠pOLO確實要比飛度賣得更便宜,而且它也是一款具備德系血統的精緻小車,除了空間表現較弱以外幾乎哪方面都做得相當不錯。

pOLO在車展上也有不錯的優惠,全系最大17500左右的優惠,算起來車價也不高,而且也沒有太多強制性捆綁的收費,譬如:可以選擇自行上牌減少購車費用等(據說廠家計劃在車展期間要做出1500個訂單,任務還是挺重的)。

而在4S店的暗地採訪當中,由於附近的一家上汽大眾4S店店面比較小,這台可憐的小pOLO也被無情的扔到展廳外,而詢問銷售得知,目前pOLO僅有大概1萬元左右的優惠,另外大眾pOLO目前也享受兩年0利息的貸款優惠。

10多萬最受消費者關注的車型莫過於思域!目前的銷售情況為:除非加裝精品,否則一車難求。

而附近東本4S店當中,在整體的報價方面並沒有明顯出入(反正就是都沒有優惠)。但是不同的4S店之間在後期加裝精品的費用上也有所出入。像在隨機一家4S店僅需要加裝8000塊的精品就能在短時間內提車(還有少量現車!),如果加裝5000元精品的話則需要2-3個月,…如果你不着急要車的話,下個長期訂單大概在明年4月份提車(不加裝精品)。

而20多萬的預算最近關注度比較高的莫過於全新一代別克君威(反正展台就是擠滿了人)。

作為一款新上市不久的車型,在車展上也給我們報出了1萬元左右的購車優惠,而且還承諾在1個月之內便能順利提車,總的來說還是比較划算的。相比之下,在4S店當中優惠就沒有車展上那麼大了,整體來說還是在車展下單,優惠會更大。

你以為這樣就完了?

還有彩蛋環節呢

你們想知道凱美瑞優惠多少嗎?

最想購買的車型之一

而全新的TLX-L也讓我有種蠢蠢欲動的感覺,值得一提的是,雖然謳歌TLX-L的預售價不高於28萬,但你想要買到真正的TLX-L(配備四輪隨動轉向)的版本就必須買到尊享版,而它的裸車價錢大概在30-31萬左右。在車展期間下訂TLX-L,在1月份之前便可以順利提到全新TLX-L的第一批車型,而第一批車型的好處在雖然它雖然已經國產了,但是其中絕大部分配件(發動機、波箱等)都是純原裝進口的(反正差點就交錢定下來了)。

說到最後,其實在車展上銷售們所開出來的優惠幅度確實會比4S店更多,但值得一說的是,本次所詢問的價格僅僅是初步的,簡單來說並不是抄底價,只要你抱着必買的決心去跟銷售砍價,都是可以有更大的購車優惠!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

闢謠!年底買車最便宜都是騙人的!

第二種,距離指標實在差得太多的。這時候就要小心了,因為銷售口中的“巨額優惠”很有可能就是一個誘餌,你想下始終都是無法達標要受罰了,這時候經銷商就處於破罐破摔的狀態,能掙多少是多少,最好把罰款掙出來。所以看到那種還剩很多庫存車的大促銷就要擦亮眼睛了,必須了解清楚是真的在低價清庫存,還是高價掙罰款,不要最後買貴了還被蒙在鼓裡。

大家好我是阿湯鍋!今天的新車準備跟大家聊聊年底買車這件事。

這轉眼還有一個多月就要過年了,通常在這個時候,大家都認為這是一年當中買車的最好時機!其中既有內因,也有外因。

內因是辛苦吃了大半年泡麵了,大家總想買個大物件回老家去展示自己奮鬥一年的成果,所以在主觀上會更願意花這筆錢。再加上購置稅7.5折的優惠政策將於今年12月底結束,所以今年又給自己多了一個買車的理由。

外因則是大家都想當然地以為年底是經銷商優惠力度最大的時候,因為它們要衝銷量完成廠家的業務指標,所以這個時候消費者就有便宜可撿了。

那麼事實真的是如此嗎?

我告訴你還真不是!並且呢,分分鐘讓你以為是買便宜了,但實際上是更貴了。是不是覺得有點懵?且聽我跟大家分析分析。

首先給大家簡單科普一下廠家和經銷商是如何制定價格方案的。

每一個品牌都會在上年年底或下年年初制定一個年度銷量目標,並且會根據銷量淡旺季月份的區別出台相應的銷售政策。具體來講,廠家通常會在每月1號將當月的銷售政策和促銷方案制定出來,併發放給各個經銷商執行,所以每一款車每個月的終端價格基本都是有差異的。

因此在這個環節就有很多變數了。同樣是經銷商宣稱的“年底大促銷”,卻有可能是完全不同的幾種情況!

第一種,已經達到指標的。這時候經銷商只是象徵性的搞個促銷活動,一般不會有什麼實質性的優惠,所以想要撿便宜的你不要被銷售的幾句甜言蜜語迷惑了。

第二種,距離指標實在差得太多的。這時候就要小心了,因為銷售口中的“巨額優惠”很有可能就是一個誘餌,你想下始終都是無法達標要受罰了,這時候經銷商就處於破罐破摔的狀態,能掙多少是多少,最好把罰款掙出來。

所以看到那種還剩很多庫存車的大促銷就要擦亮眼睛了,必須了解清楚是真的在低價清庫存,還是高價掙罰款,不要最後買貴了還被蒙在鼓裡。

第三種,庫存剩的不多,還比較好賣的。這種大家就可以放心跟銷售扯嘴皮子了,優惠能爭取多一點是一點。因為這種時候經銷商通常寧可自己掏腰包,也要確保完成了廠家指標,不然前期努力就白費力氣了。

所以總結完以上三種情況之後,就告訴大家一個道理:賣的永遠比買的精,大家在各種優惠的誘惑面前一定要保持理性,區分“優惠”的真假。

那怎麼樣在這個節點獲取更大的優惠呢?

牢記四個字,貨比三家!

買車不要嫌麻煩(土豪請無視),看準一款車以後多去幾家店跑跑,表明出你買車的誠意。銷售通常會很頭疼這樣的客戶,因為你對行情心中有數,他們在報價時也會非常謹慎。報高了怕你轉身就走,報低了接下來的生意就不好談。只要你耐心對比幾家價格后,保你買不了吃虧,買不了上當。

另外,重要提醒幾點。

第一是談價格不要只是簡單的車型優惠,你要了解下訂后每一筆具體費用以及相關贈品都有哪些等等,尤其是貸款買車的朋友們更要清楚各項費用,因為最終的落地總價才是最關鍵的;

第二,如果當地有多家同品牌4S店,建議去不同集團下的經銷商談價格,這樣才能確保最終優惠價格的可靠性。

第三,年底買車盡量避免“壓哨”,意思就是說不要拖到快過年了才去下訂,這時候銷售通常會抓住你急於提車過年的心理,然後佔據價格談判的上風,甚至會給出各種讓你加價提車的理由。

以上就是我分享給各位的年底買車小技巧,僅供大家參考。還是那句話,賣的永遠比買的精,汽車價格是很多因素決定的,不要期望所謂的最低價,只要你有需求並且有能力,看準了就下手吧!早買早享受。

最後,祝大家都能買到自己心儀的車,開開心心開車回家過大年!

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

如何不讓車管所黃牛占你便宜?

果不其然。之前說好的400塊只是“裸價”,有的是讓你加錢的地方。第一個是因為我是代辦人而非新車主本人,黃牛說需要50塊代辦費,其實也就是拿張紙寫個書面說明再隨便簽個名就可以了。我想着50塊也不多,加就加吧。第二個是加急費,多收了我150塊,我特么當時心裏一頓MMp,我估摸着就是黃牛知道我們是大老遠從廣州過來的,擔心一天搞不完會增加時間成本,所以就名正言順地給我們辦了所謂的“加急”。

大家好,我是阿湯鍋。

今天準備跟大家聊聊很多人在購買新車或二手車過程中跨不過去的一道坎——車管所黃牛!

由於我一個月前剛入了一輛11年的二手雨燕,並且在外遷和遷入的過程中都跟黃牛打過交道,所以也算是交過學費了,希望看完這篇文章的你能省下幾箱油錢。

大致來講,車管所黃牛可以分為兩大類。

第一種,掙跑腿費的。比如車主不熟悉過戶流程,或者就是懶得動,花個一百兩百讓黃牛幫自己跑;還有車外遷,自己懶得跑去外地上牌,然後花錢找人去辦。

這些是可以接受的,因為這屬於一個願打一個願挨,一個花錢一個出力,都是擺在明面上的。

第二種,“神通廣大”的。比如你這車過戶外檢不可能過、年檢排放不合格、辦不了居住證,或者你今天來晚了手續走不完等等,然後你會發現以上這些只要給錢就都立馬解決了。

這些就是不太能接受的,或者說是出於無奈,因為黃牛無非就是利用自己平時疏通的關係,還有不明真相車主的恐慌心理,然後讓你心甘情願地掏錢。

我相信絕大多數車主都至少經歷過以上兩種黃牛的其中一種,並且多數情況下花出去的錢也確實省下了不少人力和時間成本。所以我想告訴大家的是,如何在節省自己人力、時間的同時少花冤枉錢。

以我自己購買的二手雨燕為例,11年的車國四排放,上線檢車不需要擔心。不過這輛車原本是廣東湛江戶,而我準備上廣東梅州戶,所以我必須先和原車主去湛江提檔過戶,然後再自己回去梅州落戶上牌。

另外,因為某種原因我要把車落在我爸名下,所以全程我都是“代辦人”。於是以上的這些細節,也就成為了我花冤枉錢的伏筆。

先說提檔過戶。

我事先已經聯繫好了湛江車管所的黃牛,價錢也已經談好了,我還砍價砍了100塊,從500講到了400,當時我心裏還樂呵還好不貴。不過我再三強調,一定要在一天內給我辦完,黃牛說沒問題我叫人給你加急。

這個400塊我覺得是不得不出的,畢竟從廣州開去湛江有四百公里的路程,並且還人生地不熟的,再加上這來回八百公里的油錢和過路費就快一千了,所以想快去快回一天內搞完的話我還是乖乖掏錢吧。

於是湊巧在我生日當天就和原車主一起開車到四百公裡外的湛江去提檔過戶了,然後到了車管所之後是黃牛的手下,也就是另一個黃牛接了我們這單“生意”,隱約感覺是安排好的。

果不其然!之前說好的400塊只是“裸價”,有的是讓你加錢的地方。

第一個是因為我是代辦人而非新車主本人,黃牛說需要50塊代辦費,其實也就是拿張紙寫個書面說明再隨便簽個名就可以了。我想着50塊也不多,加就加吧。

第二個是加急費,多收了我150塊,我特么當時心裏一頓MMp,我估摸着就是黃牛知道我們是大老遠從廣州過來的,擔心一天搞不完會增加時間成本,所以就名正言順地給我們辦了所謂的“加急”。(其實快慢都是黃牛自己說了算)

不過必須承認,有黃牛代辦是真的快,如果自己去辦的話就乖乖排隊吧,具體你懂的……於是加起來我一共多花了200塊,後來我總結了,我犯的錯誤主要是事先沒有跟黃牛確認好價格讓他們鑽了空子。

所以這裏要告訴大家的是,當你找黃牛前一定要提前確認好“全部總價”,要不然有的是理由讓你加錢。

另外要告訴大家一個二手車過戶很容易弄錯的點:在原車年檢過期的當月是不能辦理過戶的!必須重新年檢才可以。別問我為什麼知道,都是自己踩過的坑,桑心……

再來說說落戶上牌。

自從經歷過提檔過戶給黃牛多賺了200塊之後,我就下決心自己去上牌了,在梅州自己的地盤總不用再依靠黃牛了吧?

結果證明圖樣圖森破,儘管在此之前還特意問過在相關部門上班的同學,可以自己去辦理上牌,但真正去到現場之後就蒙圈了。

我們都知道,單純上牌只要個牌照手續費,沒記錯的話是120塊左右,但你會絕望地發現,要想全程自己辦理真是一踩一個坑。

首先黃牛插隊就不用說了,必然的,所以時間上沒保障;其次是如果你不熟悉辦理流程的話,你會發現該去哪裡驗車哪裡測尾氣哪裡拍照哪裡拓發動機號等等都足夠把弄得你暈頭轉向的。

於是為了節省時間,我還是說服自己找了黃牛,並且這次我留了個心眼,500一口價不再出任何上牌過程中的費用,不過即便如此我還是覺得自己虧了。

所以事後我自己做了個總結,如果想省下這筆給黃牛賺的錢,就要事先上網了解清楚業務辦理流程,以及需要準備哪些證件,包括車輛證件和新車主證件。如果去到現場不知所措的話,最好的辦法就是問工作人員,記住不要問到黃牛那去了。

不過話說回來,根據我的觀察,我發現發動機拓號、拍照洗出照片,這些都是流動人員在處理的,所以即便是自己去辦理的話也真不一定能快速找到這些流動人員,當然這些也都是要另外收費的(複印身份證特么的收我十塊錢!),於是當你自己辛苦排了一天隊之後可能會發現,其實省下來的那幾百塊錢都是用一肚子委屈換來的,所以說黃牛真是讓人又愛又恨。

最後,希望各位看完我以上兩個經歷后,能夠給今後自己的過戶上牌提供點幫助,能花少一點是一點!如果你也有一些車管所小妙招,歡迎在留言區分享出來,精選可置頂哦!

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

最不顯老的三款家用車 最低7萬起!

寶駿將在2018年第一季度帶來一款全新緊湊型SUV——寶駿530。前衛的外觀,分體式大燈,還有時下流行的懸浮式車頂,相信這樣一款高顏值國產車型絕對不會讓你掉面。寶駿530的內飾也同樣顯得年輕時尚,參考寶駿510,相信其做工和用料不會讓人失望。

大家好,我是阿湯鍋!

就在上周跨年的時候,大家都在朋友圈裡曬自己的18歲,嚇得我也趕緊翻出了18歲的靚照,最後發現自己長得太早熟,也就沒好意思曬出來了。

其實這個梗是來自於步入2018年後,就意味着所有的90后都已經成年了,而像我們這些打頭陣的92、93年的叔叔阿姨們也已經不再是“小鮮肉”了,何其傷感吶!

所以為了證明自己,我相信很多人的18年目標清單中都有“買車”這一項。當然老一輩的90后也是最挑剔的,兜里沒什麼錢要求卻很多,比如選車要個性要運動要有面,最好還能兼顧實用性,最後發現自己的積蓄可能連首付都不夠。

不過別擔心!有些車還是能做到兩全其美的,那麼下面就跟大家聊聊2018年都有哪些既便宜,又能滿足以上條件的新車上市,興許它就是你人生中的第一輛車呦。

1、大眾pOLO

預計售價:8萬起

首先想到的還是離不開大眾神車系,相信很多80后對高爾夫和pOLO都有一種特殊的情感,因為這也是他們年輕時候的第一輛車,而到了今天這兩款車依然深受年輕人的喜愛,尤其是定位更接地氣的pOLO,更容易成為年輕人跨向有車一族的首選。

全新的第六代pOLO最吸引人的毫無疑問就是外觀上的大變,相比於老款的圓潤可愛,新pOLO很識時務地開始變得凌厲帥氣起來,不再是從前那副“女人車”的樣子。

內飾也有了天翻地覆的變化,座椅格子花紋的加入很顯逼格。而配置方面不僅推出與耳機品牌Beats合作的特別版車型,就連奧迪車型上的全液晶儀錶盤也被拿了過來,怎麼能叫年輕人不喜歡?

同時,新車尺寸上也有了大幅提升,軸距增加了94mm后已經完全超越了本田飛度,相比現款足足打了一圈,所以預計會有一個不錯的後排空間。

動力上,海外版車型提供1.0L、1.0T、1.5T汽油發動機及1.6T柴油發動機可選,並將推出普通版、R-Line版和GTI三種版本,滿足不同消費者的需求。而國產後的pOLO也將在今年與國內消費者見面,並且售價可能會略高於現款車型,不過如此全面的它我想已經足夠勝任年輕人對第一輛車的所有期待了。

2、寶駿530

預計售價:7萬起

不得不說現在也有越來越多的年輕人想要選購一台SUV車型,畢竟空間大能裝(震)嘛。而經驗告訴我們,寶駿就是兼顧好看好開和實用的高性價比之選。

寶駿將在2018年第一季度帶來一款全新緊湊型SUV——寶駿530。前衛的外觀,分體式大燈,還有時下流行的懸浮式車頂,相信這樣一款高顏值國產車型絕對不會讓你掉面。

寶駿530的內飾也同樣顯得年輕時尚,參考寶駿510,相信其做工和用料不會讓人失望。另外,豐富的配置也是寶駿530的一大亮點,新車將根據車型的不同提供定速巡航、全景天窗、後排空調出風口、一鍵啟動、主駕駛位電動座椅等豐富配置。

動力方面,新車將搭載1.8L和1.5T兩款發動機,變速箱目前還不得知。同時,該車的后懸架與第6代奧迪A6的后懸架在結構上是非常相像的,因此新車的乘坐表現也非常值得期待。

所以如果預算比較有限,而又想買一台性價比高的SUV車型,我覺得寶駿530會是個不錯的選擇。

3、豐田C-HR

預計售價:12萬起

到了第三款車型,就是一個進階的選項了,因為不排除這款車上市後會成為爆款,也就是關注度極高的豐田C-HR。不過目標總是有的嘛,萬一18年財運旺呢!

可以說,C-HR的到來填補了豐田在小型SUV領域的空缺,並很有希望成為該細分市場的有力競爭者。

國產後的C-HR將被打造成兩款車型,一款交由廣汽-豐田生產,定名為C-HR,另一款則交由一汽-豐田生產,定名IZOA 奕澤,這種雙車策略在國內已經非常常見了。

這樣做的好處就是兩款車型在設計上會有所差別,這就給了消費者多一個選擇,不過可以肯定的是兩款車走的都是個性時尚的設計路線,相信會很受年輕人的喜愛。

動力方面,海外C-HR搭載的是1.2T+6MT/CVT和1.8L混動三套動力總成,這三套動力總成目前也用在國產卡羅拉和雷凌上,因此國產C-HR的動力總成也應該會和海外版的保持一致。

另外值得一提的是,該車的外形提供了一個很好的改裝胚子,所以預計新車的小改裝件會很多,追求個性的朋友完全可以根據自己的喜好裝飾自己的愛車,看來C-HR有機會成為小型SUV領域中的“GK5”。

好了!以上就是我推薦給大家的三款新車,如果其中有你心儀的車型,那就趕快給自己的18年定個小目標吧,爭取能在今年實現自己的買車願望!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

8款老闆愛的車最低只要4萬

還有就是形象與同級比起來要更顯莊重,即使去談大買賣,也不至於讓人瞧不起。其實不僅是別克GL8,還有許多定位為商務MpV的車型同樣也充滿了濃郁的商務氣息,而這次推薦GL8主要原因還是因為它空間及乘坐舒適性達到同級一流水平,應有的电子設備都有,相對而言,GL8是現在市面上主流MpV中比較卓越的一款車型。

關於#老闆開什麼車#這樣的話題

相信不少人都有關注過吧

雖然老闆的座駕並沒有什麼標準

可是總有幾款讓人印象深刻

為什麼老闆們都這麼喜歡這些車?

老闆們喜歡的這些車是否適合你?

開上這些車后你是否就能當老闆?

看完這8款老闆車你就會有答案了

首先要排除一個誤區,誰說老闆就一定要開個大奔馳或大賓利的?開五菱宏光的小賣鋪老闆不也是老闆嗎?什麼空間大、油耗低、動力足、操控好、保養便宜、可靠性高等等優點就不多說了,反正銷量說明一切嘛。不過買最便宜的車裝最多的貨,這些簡單的商業意識還是要具備的。

其實能“裝”的確是五菱宏光的核心價值吧,裝人:最多可放置8個座椅,什麼7座SUV都是辣雞;裝貨:只要裝得下,世界就是你的;裝X:方式太多,請自行上論壇選,總有一款適合你。

可能這隻是我的個人印象吧,從小到大,身邊總有一些開着捷達,成天到處跑業務的創業老闆,他們總是會稱讚自己的捷達很省心,很省油,又好開,說這樣的一台車可以完全滿足他們的代步需求,而且外觀低調,卻很有商務范。

其實說到捷達的外觀,雖然現款的捷達也很低調,也很有商務范,同樣也是個“老實”形象,還是以前方頭方腦的造型給我的印象更深刻,可是那個造型放在今天馬路上,也許展現出更多的是“經典”或“復古”吧。

為什麼說開帕薩特的老闆最神秘?首先,一般人根本分不清帕薩特和輝騰,還有就是的確有許多隱形富豪為了低調會選擇帕薩特這款車,首先大眾標就夠低調了,再來就是外觀造型,家族式設計遍布全車身,讓人怎麼看都覺得是一輛不超過20萬的車。

可是20萬要買低調的車選擇很多,為什麼這些隱形富豪要選帕薩特?其實還是圍繞着舒適性吧,德系紮實底盤質感,大空間;還有就是形象與同級比起來要更顯莊重,即使去談大買賣,也不至於讓人瞧不起。

其實不僅是別克GL8,還有許多定位為商務MpV的車型同樣也充滿了濃郁的商務氣息,而這次推薦GL8主要原因還是因為它空間及乘坐舒適性達到同級一流水平,應有的电子設備都有,相對而言,GL8是現在市面上主流MpV中比較卓越的一款車型。

當然,這樣的MpV一般都是公務用車,老闆一般只坐在第二排,坐在舒適的座椅上,有公事要聊的時候,跟助理溝通起來十分方便;沒事聊的時候,放下靠背,也可以睡得很舒服。

作為豐田越野神車蘭德酷路澤的分支車型,普拉多其實已經更傾向於都市SUV,可是在造型方面還是很有越野的味道,這也是許多老闆們喜歡它原因之一,不過實際普拉多還是延續了較好的越野性能。

很多人都說什麼樣的人就會開什麼樣的車,我通過普拉多的車主們就能明顯體現出這點,說不上個個都高大威猛,可是至少也是條硬漢。

雖然說“虎頭奔之後再也沒有S級”,有情懷的車迷都喜歡那個年代的S級,可是現在的S級無疑是更好的S級,動力更好,配置更高,價格更低。雖然造型方面的確是向現在的消費市場妥協了,可是現在的S級仍然是D級車裡最有老闆范的。

但是實際上並不是所有買S級的老闆都會請司機,或者說不是所有買了S級的老闆自己都不開,即使自己開S級的時候的確有點像司機,可是他們還是很願意去駕駛這輛高級車。

與其說開霸道(普拉多)的老闆真霸氣,還不如說開攬勝的人才是真正的“霸道總裁”,說到高大上,縱觀市面上所有高端中大型SUV,好像真沒幾款能媲美路虎攬勝的了。也有人說路虎攬勝根本沒有直接的競爭對手,因為在公路和越野的性能都如此卓越,內飾如此豪華,外觀如此霸氣的SUV屈指可數。

雖然說攬勝也有極高的越野性能,可是車主們更願意把它看作是豪華都市SUV,而且攬勝的形象也似乎更合適出現在都市中。

看到勞斯萊斯,貌似我們的話題就要結束了,我也都相信大家和我一樣,勞斯萊斯才是代表最“尊貴”的老闆車,雖然到了這個等級,選擇還有賓利和邁巴赫,可是這些品牌都不如勞斯萊斯尊貴。

其實到了這個級別的車,更講究的應該就奢華、藝術、品味,多的不說了。

不知道大家看了這麼多老闆車之後,有沒有你心儀的那輛呢?或者在你的印象里,什麼車才算老闆車呢?歡迎把你的所有見解評論到下方留意區哦。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

QNX Message Passing,一個讓人頭禿的 IPC BUG

問題描述

QNX系統中 Client 與 Server 通過 QNX Message Passing 進行進程間通信。正式開發前,寫過測試程序 (Client),和 Server 通信一切正常。但把同樣的代碼拷貝到正式的 Client 中,結果發現調用 MsgSend() 后無響應,pidin 显示 Client 處於 REPLY PENDING 的狀態。

幾番嘗試,發現一個讓人很難接受的事實:只能在 Client 的主線程中調用 MsgSend() 才能收到 Server 的回復,如果在 Client 的對等線程中調用MsgSend(),則會因為收不到 Server 的回復而 Pending。

問題分析

測試程序能正常工作,所以起初懷疑是 Client 的問題。面對龐大的 Client,始終沒想到突破口。甚至曾一度懷疑是否是 QNX 的系統限制,MsgSend() 只能在主線程中調用,官網文檔翻了一圈並沒有發現這樣的限制。自己寫了個 IPC Server 來測試,發現並沒有這個問題。

Client Server 結果
測試程序,主線程中調用 MsgSend() 正式 Server OK
正式 Client,對等線程中調用 MsgSend() 正式 Server REPLY PENDING
正式 Client,對等線程中調用 MsgSend() 簡化版測試 Server OK

最後不得不懷疑起 Server,莫非 Server 端有什麼機制能檢測到消息是發自主線程還是對等線程,然後只回復來自主線程的 IPC 請求?

一個典型的 IPC Server 示例代碼如下:

While(1) {
   int rcvid = MsgReceive(chid, &recvBuf, sizeof(recvBuf), NULL);
   /* … process the request based on recvBuf … */
   MsgReply(rcvid, EOK, &replyBuf, sizeof(replyBuf)); // reply to unblock the IPC client
}

如果是從 Client 的主線程發送來的消息,rcvid 是一個很小的数字,如 1, 3, 5… 如果是從Client 的對等線程發送來的消息,rcvid 是一個大於 65535 的数字,如 65538, 65540, 65542… 

讓人意外的是 Server 用了一個 int16_t 來保存 rcvid,直接導致後續的 MsgReply() 無法正確的將消息回給 Client,從而導致 Client 一直處於 REPLY PENDING 狀態。

Reference

  • http://www.qnx.com/developers/docs/7.0.0/#com.qnx.doc.neutrino.sys_arch/topic/ipc_Robust.html  

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

聽說你的資源被盜用了,那你知道 Nginx 怎麼防盜鏈嗎?

上一篇文章講了 Nginx 中的變量和運行原理,下面就來說一個主要提供變量並修改變量的值的模塊,也就是我們要講的防盜鏈模塊:referer 模塊。

簡單有效的防盜鏈手段

場景

如果做過個人站點的同學,可能會遇到別人盜用自己站點資源鏈接的情況,這就是盜鏈。說到盜鏈就要說一個 HTTP 協議的 頭部,referer 頭部。當其他網站通過 URL 引用了你的頁面,用戶在瀏覽器上點擊 URL 時,HTTP 請求的頭部會通過 referer 頭部將該網站當前頁面的 URL 帶上,告訴服務器本次請求是由誰發起的。

例如,在谷歌中搜索 Nginx 然後點擊鏈接:

在打開的新頁面中查看請求頭會發現,請求頭中包含了 referer 頭部且值是 https://www.google.com/。

像谷歌這種我們是允許的,但是有一些其他的網站想要引用我們自己網站的資源時,就需要做一些管控了,不然豈不是誰都可以拿到鏈接。

目的

這裏目的其實已經很明確了,就是要拒絕非正常的網站訪問我們站點的資源。

思路

  • invalid_referer 變量
    • referer 提供了這個變量,可以用來配置哪些 referer 頭部合法,也就是,你允許哪些網站引用你的資源。

referer 模塊

要實現上面的目的,referer 模塊可得算頭一號,一起看下 referer 模塊怎麼用的。

  • 默認編譯進 Nginx,通過 --without-http_referer_module 禁用

referer 模塊有三個指令,下面看一下。

Syntax: valid_referers none | blocked | server_names | string ...;
Default: —
Context: server, location

Syntax: referer_hash_bucket_size size;
Default: referer_hash_bucket_size 64; 
Context: server, location

Syntax: referer_hash_max_size size;
Default: referer_hash_max_size 2048; 
Context: server, location
  • valid_referers 指令,配置是否允許 referer 頭部以及允許哪些 referer 訪問。
  • referer_hash_bucket_size 表示這些配置的值是放在哈希表中的,指定哈希表的大小。
  • referer_hash_max_size 則表示哈希表的最大大小是多大。

這裏面最重要的是 valid_referers 指令,需要重點來說明一下。

valid_referers 指令

可以同時攜帶多個參數,表示多個 referer 頭部都生效。

參數值

  • none
    • 允許缺失 referer 頭部的請求訪問
  • block:允許 referer 頭部沒有對應的值的請求訪問。例如可能經過了反向代理或者防火牆
  • server_names:若 referer 中站點域名與 server_name 中本機域名某個匹配,則允許該請求訪問
  • string:表示域名及 URL 的字符串,對域名可在前綴或者後綴中含有 * 通配符,若 referer 頭部的值匹配字符串后,則允許訪問
  • 正則表達式:若 referer 頭部的值匹配上了正則,就允許訪問

invalid_referer 變量

  • 允許訪問時變量值為空
  • 不允許訪問時變量值為 1

實戰

下面來看一個配置文件。

server {
	server_name referer.ziyang.com;
    listen 80;

	error_log logs/myerror.log debug;
	root html;
	location /{
		valid_referers none blocked server_names
               		*.ziyang.com www.ziyang.org.cn/nginx/
               		~\.google\.;
		if ($invalid_referer) {
    			return 403;
		}
		return 200 'valid\n';
	}
}

那麼對於這個配置文件而言,以下哪些請求會被拒絕呢?

curl -H 'referer: http://www.ziyang.org.cn/ttt' referer.ziyang.com/
curl -H 'referer: http://www.ziyang.com/ttt' referer.ziyang.com/
curl -H 'referer: ' referer.ziyang.com/
curl referer.ziyang.com/
curl -H 'referer: http://www.ziyang.com' referer.ziyang.com/
curl -H 'referer: http://referer.ziyang.com' referer.ziyang.com/
curl -H 'referer: http://image.baidu.com/search/detail' referer.ziyang.com/
curl -H 'referer: http://image.google.com/search/detail' referer.ziyang.com/

我們需要先來解析一下這個配置文件。valid_referers 指令配置了哪些值呢?

 valid_referers none blocked server_names
        *.ziyang.com www.ziyang.org.cn/nginx/
        ~\.google\.;
  • none:表示沒有 referer 的可以訪問
  • blocked:表示 referer 沒有值的可以訪問
  • server_names:表示本機 server_name 也就是 referer.ziyang.com 可以訪問
  • *.ziyang.com:匹配上了正則的可以訪問
  • www.ziyang.org.cn/nginx/:該頁面發起的請求可以訪問
  • ~\.google\.:google 前後都是正則匹配

下面就實際看下響應:

# 返回 403,沒有匹配到任何規則
  ~ curl -H 'referer: http://www.ziyang.org.cn/ttt' referer.ziyang.com/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
  ~ curl -H 'referer: http://image.baidu.com/search/detail' referer.ziyang.com/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
# 匹配到了 *.ziyang.com
  ~ curl -H 'referer: http://www.ziyang.com/ttt' referer.ziyang.com/
valid
  ~ curl -H 'referer: http://www.ziyang.com' referer.ziyang.com/
valid
# 匹配到了 server name
  ~ curl -H 'referer: http://referer.ziyang.com' referer.ziyang.com/
valid
# 匹配到了 blocked
  ~ curl -H 'referer: ' referer.ziyang.com/
valid
# 匹配到了 none
  ~ curl referer.ziyang.com/
valid
# 匹配到了 ~\.google\.
  ~ curl -H 'referer: http://image.google.com/search/detail' referer.ziyang.com/
valid

防盜鏈另外一種解決方案:secure_link 模塊

referer 模塊是一種簡單的防盜鏈手段,必須依賴瀏覽器發起請求才會有效,如果攻擊者偽造 referer 頭部的話,這種方式就失效了。

secure_link 模塊是另外一種解決的方案。

它的主要原理是,通過驗證 URL 中哈希值的方式防盜鏈。

基本過程是這個樣子的:

  • 由服務器(可以是 Nginx,也可以是其他 Web 服務器)生成加密的安全鏈接 URL,返回給客戶端
  • 客戶端使用安全 URL 訪問 Nginx,由 Nginx 的 secure_link 變量驗證是否通過

原理如下:

  • 哈希算法是不可逆的
  • 客戶端只能拿到執行過哈希算法的 URL
  • 僅生成 URL 的服務器,驗證 URL 是否安全的 Nginx,這兩者才保存原始的字符串
  • 原始字符串通常由以下部分有序組成:
    • 資源位置。如 HTTP 中指定資源的 URI,防止攻擊者拿到一個安全 URI 后可以訪問任意資源
    • 用戶信息。如用戶的 IP 地址,限制其他用戶盜用 URL
    • 時間戳。使安全 URL 及時過期
    • 密鑰。僅服務器端擁有,增加攻擊者猜測出原始字符串的難度

模塊:

  • ngx_http_secure_link_module
    • 未編譯進 Nginx,需要通過 –with-http_secure_link_module 添加
  • 變量
    • secure_link
    • secure_link_expires
Syntax: secure_link expression;
Default: —
Context: http, server, location

Syntax: secure_link_md5 expression;
Default: —
Context: http, server, location

Syntax: secure_link_secret word;
Default: —
Context: location

變量值及帶過期時間的配置示例

  • secure_link
    • 值為空字符串:驗證不通過
    • 值為 0:URL 過期
    • 值為 1:驗證通過
  • secure_link_expires
    • 時間戳的值

命令行生成安全鏈接

  • 生成 md5
echo -n '時間戳URL客戶端IP密鑰' | openssl md5 -binary | openssl base64 | tr +/ - | tr -d =
  • 構造請求 URL
/test1.txt?md5=md5生成值&expires=時間戳(如 2147483647)

Nginx 配置

  • secure_link $arg_md5,$arg_expires;
    • secure_link 後面必須跟兩個值,一個是參數中的 md5,一個是時間戳
  • secure_link_md5 “$secure_link_expires$uri$remote_addr secret”;
    • 按照什麼樣的順序構造原始字符串

實戰

下面是一個實際的配置文件,我這裏就不做演示了,感興趣的可以自己做下實驗。

server {
	server_name securelink.ziyang.com;
    listen 80;
	error_log  logs/myerror.log  info;
	default_type text/plain;
	location /{
		secure_link $arg_md5,$arg_expires;
        secure_link_md5 "$secure_link_expires$uri$remote_addr secret";

        if ($secure_link = "") {
            return 403;
        }

        if ($secure_link = "0") {
            return 410;
        }

		return 200 '$secure_link:$secure_link_expires\n';
	}

	location /p/ {
        secure_link_secret mysecret2;

        if ($secure_link = "") {
            return 403;
        }

        rewrite ^ /secure/$secure_link;
	}

	location /secure/ {
		alias html/;
    	internal;
	}
}

僅對 URI 進行哈希的簡單辦法

除了上面這種相對複雜的方式防盜鏈,還有一種相對簡單的防盜鏈方式,就是只對 URI 進行哈希,這樣當 URI 傳

  • 將請求 URL 分為三個部分:/prefix/hash/link
  • Hash 生成方式:對 “link 密鑰” 做 md5 哈希
  • secure_link_secret secret; 配置密鑰

命令行生成安全鏈接

  • 原請求
    • link
  • 生成的安全請求
    • /prefix/md5/link
  • 生成 md5
    • echo -n 'linksecret' | openssl md5 –hex

Nginx 配置

  • secure_link_secret secret;

這個防盜鏈的方法比較簡單,那麼具體是怎麼用呢?大家都在網上下載過資源對吧,不管是电子書還是軟件,很多網站你點擊下載的時候往往會彈出另外一個頁面去下載,這個新的頁面其實就是請求的 Nginx 生成的安全 URL。如果這個 URL 被拿到的話,其實還是可以用的,所以需要經常的更新密鑰來確保 URL 不會被盜用。

今天這篇文章詳細講了防盜鏈的具體用法,最近的這兩篇文章都是說的已有的變量用法,下一篇文章講一下怎麼生成新的變量。

本文首發於我的個人博客:iziyang.github.io,所有配置文件我已經放在了 Nginx 配置文件,大家可以自取。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

04 . Docker安全與Docker底層實現

Docker安全

Docker安全性時,主要考慮三個方面

# 1. 由內核的名字空間和控制組機制提供的容器內在安全
# 2. Docker程序(特別是服務端)本身的抗攻擊性
# 3. 內核安全性的加強機制對容器安全性的影響
內核命名空間

Docker容器和LXC容器很相似,所提供的安全特性也差不多。當用docker run啟動一個容器時,在後台Docker為容器創建了一個獨立的命名空間和控制組集合。

命名空間提供了最基礎也是最直接的隔離,在容器中運行的進程不會被運行在主機上的進程和其它容器發 現和作用。

每個容器都有自己獨有的網絡棧,意味着它們不能訪問其他容器的sockets或接口。不過,如果主機系統上做了相應的設置,容器可以像跟主機交互一樣的和其他容器交互。當指定公共端口或使用links來連接2個容器時,容器就可以相互通信了(可以根據配置來限制通信的策略).

從網絡架構的角度來看,所有的容器通過本地主機的網橋接口相互通信,就像物理機器通過物理交換機通信一樣。

那麼,內核中實現命名空間和私有網絡的代碼是否足夠成熟?

內核名字空間從2.6.15版本(2008年7月發布)之後被引入,數年間,這些機制的可靠性在諸多大型生產系統中被實踐驗證。

實際上,名字空間的想法和設計提出的時間要更早,最初是為了在內核中引入一種機制來實現 OpenVZ 的特性。而OpenVZ項目早在2005年就發布了,其設計和實現都已經十分成熟。

控制組

控制組是Linux容器機制的另外一個關鍵組件,負責實現資源的審計和限制.

它提供了很多有用的特性;以及確保各個容器可以公平地分享主機的內存、CPU、磁盤IO等資源;當然,更重要的是,控制組確保了當容器內的資源使用產生壓力時不會連累主機系統。

儘管控制組不負責隔離容器之間相互訪問、處理數據和進程,它在防止拒絕服務(DDOS)攻擊方面是必不可少的。尤其是在多用戶的平台(比如公有或私有的PaaS)上,控制組十分重要。例如,當某些應用程序表現異常的時候,可以保證一致地正常運行和性能。

控制組始於2006年,內核從2.6.24版本開始被引入.

Docker服務端的防護

運行一個容器或應用程序的核心是通過Docker服務端。Docker服務的運行目前需要root權限,因此其安全性十分關鍵。

首先,確保只有可信的用戶才可以訪問Docker服務。Docker允許用戶在主機和容器間共享文件夾,同時不需要限制容器的訪問權限,這就容易讓容器突破資源限制。例如,惡意用戶啟動容器的時候將主機的根目錄/映射到容器的 /host目錄中,那麼容器理論上就可以對主機的文件系統進行任意修改了。這聽起來很瘋狂?但是事實上幾乎所有虛擬化系統都允許類似的資源共享,而沒法禁止用戶共享主機根文件系統到虛擬機系統

這將會造成很嚴重的安全後果。因此,當提供容器創建服務時(例如通過一個web服務器),要更加註意進行參數的安全檢查,防止惡意的用戶用特定參數來創建一些破壞性的容器

為了加強對服務端的保護,Docker的REST API(客戶端用來跟服務端通信)在0.5.2之後使用本地的Unix套接字機制替代了原先綁定在 127.0.0.1 上的TCP套接字,因為後者容易遭受跨站腳本攻擊。現在用戶使用Unix權限檢查來加強套接字的訪問安全。

用戶仍可以利用HTTP提供REST API訪問。建議使用安全機制,確保只有可信的網絡或VPN,或證書保 護機制(例如受保護的stunnel和ssl認證)下的訪問可以進行。此外,還可以使用HTTPS和證書來加強 保護。

最近改進的Linux名字空間機制將可以實現使用非root用戶來運行全功能的容器。這將從根本上解決了容器和主機之間共享文件系統而引起的安全問題。

終極目標是改進 2 個重要的安全特性:

  • 將容器的root用戶映射到本地主機上的非root用戶,減輕容器和主機之間因權限提升而引起的安全問題;
  • 允許Docker服務端在非root權限下運行,利用安全可靠的子進程來代理執行需要特權權限的操作。這些子進程將只允許在限定範圍內進行操作,例如僅僅負責虛擬網絡設定或文件系統管理、配置操作等。
  • 最後,建議採用專用的服務器來運行Docker 和相關的管理服務(例如管理服務比如ssh監控和進程監控、管理工具nrpe、collectd等)。其它的業務服務都放到容器中去運行。
內核能力機制

能力機制(Capability)是Linux內核一個強大的特性,可以提供細粒度的權限訪問控制。 Linux內核自2.2版本起就支持能力機制,它將權限劃分為更加細粒度的操作能力,既可以作用在進程上,也可以作用在文件上。

例如,一個Web服務進程只需要綁定一個低於1024的端口的權限,並不需要root權限。那麼它只需要被授權 net_bind_service能力即可。此外,還有很多其他的類似能力來避免進程獲取root權限。

默認情況下,Docker啟動的容器被嚴格限制只允許使用內核的一部分能力.

使用能力機制對加強Docker容器的安全有很多好處。通常,在服務器上會運行一堆需要特權權限的進程,包括有ssh、cron、syslogd、硬件管理工具模塊(例如負載模塊)、網絡配置工具等等。容器跟這些進程是不同的,因為幾乎所有的特權進程都由容器以外的支持系統來進行管理。

# 1. ssh訪問被主機上ssh服務來管理;
# 2. cron通常應該作為用戶進程執行執行,權限交給使用他服務的應用來處理;
# 3. 日誌系統可由Docker或第三方服務管理;
# 4. 硬件管理無關緊要,容器中也就無需執行udevd以及類似服務;
# 5. 網絡管理也都在主機上設置,除非特殊需求,容器不需要對網絡進行配置.

從上面的例子可以看出,大部分情況下,容器並不需要真正的root權限,容器只需要少數的能力即可.為了加強安全,容器可以禁用一些沒必要的權限:

# 1. 完全禁止任何mount操作.
# 2. 禁止直接訪問本地主機的套接字.
# 3. 禁止訪問一些文件系統的操作,比如創建新的設備,修改文件屬性等.
# 4. 禁止模塊加載.

這樣,就算攻擊者在容器中取得了root權限,也不能獲得本地主機的較高權限,能進行的破壞也有限.

默認認情況下,Docker採用 白名單 機制,禁用必需功能之外的其它權限。 當然,用戶也可以根據自身需求來為Docker容器啟用額外的權限。

其他安全特性.

除了能力機制之外,還可以利用一些現有的安全機制來增強docker的安全性,例如TOMOYO,AppArmor,SELinux,GRSEC等.

Docker 當前默認只開啟了能力機制,用戶可以採用多種方案來加強Docker主機的安全,例如:

  1. 在內核中啟用GRSEC和PAX,這將增加很多編譯和運行時的安全檢查,通過地址隨機化避免惡意探測等,並且,啟用該特性不需要Docker進行任何配置.

  2. 使用一些有增強安全特性的容器模板,比如帶AppArmor的模板和Redhat帶Selinux策略的模板.這些模板提供了額外的安全特性.

  3. 用戶可以自定義訪問控制機制來定製安全策略.

跟其他添加Docker容器的第三方工具一樣(比如網絡拓撲和文件系統共享),有很多類似的機制,在不改變Docker內核情況下就可以加固現有的容器.

小結

總體來說,Docker容器還是十分安全的,特別是在容器不使用root權限來運行進程的話.

另外,用戶可以使用現有工具,比如Apparmor,SELinux,GRSEC來增強安全性,甚至自己在內核中實現更複雜的安全機制.

Docker底層實現

Docker底層的核心技術包括Linux上的命名空間(Namespaces)、控制組(ControlGroups),Union文件系統(Union file systems)和容器格式(Container format).

我們知道,傳統的虛擬機通過在宿主主機中運行hypervisor來模擬一套完整的硬件環境提供給虛擬機的操作系統.虛擬機系統看到的環境是可限制的,也是彼此隔離的,這種直接的做法實現了對資源完整的封裝,但很多時候往往意味着系統資源的浪費,例如,以宿主機和虛擬機系統都為linux系統為例,虛擬機中運行的應用其實是可以利用宿主機系統中的運行環境。

我們知道,在操作系統中,包括內核、文件系統、網絡、PID、UID、IPC、內存、硬盤、CPU等等,所有的資源都是應用進程直接共享的,要想實現虛擬化,除了要實現對內存、CPU、網絡IO、硬盤IO、存儲空間等的限制外,還要實現文件系統、網絡、PID、UID、IPC等等的相互隔離,前者相對容易實現一些,後者則需要宿主機系統的深入支持.

隨着Linux系統對於命名空間功能的完善實現,程序員可以實現上面的所有需要,讓某些進程在彼此隔離的命名空間中運行,大家雖然都共用一個內核和某些運行時環境(l例如一些系統命令和系統等),但是彼此卻看不到,大家都以為系統中只有自己的存在,這種機制就是容器,利用命名空間來做權限的隔離控制,利用cgroups來做資源分配.

容器的基本架構

Dcoker採用了c/s架構,包括客戶端和服務端,Docker守護進程(Daemon)作為服務端接受來自客戶端的請求,並處理這些請求(創建、運行、分發容器).

客戶端和服務端既可以運行在一個機器上,也可以通過socket或者RESTful API來進行通信.

Docker守護進程一般在宿主主機後台運行,等待來自客戶端的消息.

Docker客戶端則為用戶提供一系列可執行的命令,用戶用這些命令實現跟Docker守護進程交互.

命名空間

命名空間是Linux內核一個強大的特性,每個容器都有自己單獨的命名空間,運行在其中的應用都像是在獨立的操作系統運行一樣,命名空間保證了容器之間彼此互不影響.

pid命名空間

不同用戶的進程就是通過pid命名空間隔離開的,且不同命名空間可以有相同的pid,所有的LXC進程在Docker中的父進程為Docker進程,每個LXC進程具有不同的命名空間,同時由於嵌套,因此可以很方便的實現嵌套的Docker容器.

net命名空間

有了pid命名空間,每個命名空間的pid能夠實現相互隔離,但是網絡端口還是共享host的端口,網絡隔離是通過net命名空間實現的,每個net命名空間有單獨的網絡設備,IP地址,路由表,/proc/net目錄,這樣每個容器的網絡就能隔離開來,Docker默認採用veth的方式,將容器中的虛擬網卡host上的一個Docker網橋docker0連接在一起.

IPC命名空間

容器中進程交互採用了Linux常見的進程交互方法,包括信號量,消息隊列和共享內存等,然而同VM不同的是,容器的進程交互實際山還是host上具有相同pid命名空間的進程交互,因此需要在IPC資源中申請加入命名空間信息,每個IPC資源有一個唯一的32位id。

mnt命名空間

類似chroot,將一個進程放到一個特定的目錄執行,mnt命名空間允許不同命名空間的進程看到的文件結構不同,這樣每個命名空間中的進程所看到的文件目錄就被隔離開了,同chroot不同,每個命名空間的容器在/proc/mounts的信息只包含所在命名空間的mount point。

uts命名空間

UTS命名空間允許每個容器擁有獨立的hostname和domain name,使其在網絡上可以被視作一個獨立的節點而非主機上的一個進程.

每個容器可以有不同的用戶和組id,也就是說可以在容器內用容器內部的用戶執行程序而非主機上的用戶.

控制組

控制組(cgroups)是一個Linux內核的一個特性,主要用來對資源進行隔離、限制、審計等,只有能控制分配到容器的資源,才能避免當多個容器同時運行時對系統資源的競爭.

控制組技術最早由Google的程序員在2006年提出,Linux內核從2.6.24開始支持.

控制組可以提供對容器的內存、CPU、磁盤IO等資源的限制和審計管理.

聯合文件系統

聯合文件系統(UnionFS)是一種分層、輕量級並且高性能的文件系統,他支持對文件系統的修改作為一次提交來一層層的疊加,同時可以將不同目錄掛載到同一個虛擬文件系統.

聯合文件系統是Docker鏡像的基礎,鏡像可以通過分層來進行繼承,基於基礎鏡像(沒有父鏡像),可以製作各種具體的應用鏡像.

另外,不同Docker容器就可以共享一些基礎的文件系統層,同時加上自己獨有的改動層,大大提高了存儲的效率.

Docker中使用的AUFS就是一種聯合文件系統,AUFS支持為每一個成員目錄(類似Git的分支)設定只讀(readonly)、讀寫(readwrite)和寫出(whiteout-able)權限,同時AUFS里有一個類似分層的概念,對只讀權限的分支可以邏輯上進行增量的修改(不影響只讀部分的).

Docker目前支持的聯合文件系統包括OverlayFS,AUFS,BtrFS,VFS,ZFS和Device Mapper。

有可能的情況下,推薦使用overlay2存儲驅動,overlay2是目前Docker默認的存儲驅動,以前是aufs,可以通過配置以上提到的其他類型存儲驅動.

容器格式

最初,Docker採用了LXC中的容器格式,從0.7版本開始以後去除LXC,轉而使用自行開發的libcontainer,從1.11開始,進一步演進為runC和containerd。

網絡實現

Docker的網絡實現其實就是利用了Linux上的網絡命名空間和虛擬網絡設備(特別是vethpair).

基本原理

首先,要實現網絡通信,機器需要至少一個網絡接口(物理接口或虛擬接口)來收發數據包,此外,如果不同子網之間要進行通信,需要路由機制.

Docker中的網絡接口默認都是虛擬的接口,虛擬接口的優勢之一就是轉發效率較高,Linux通過在內核中進行數據複製來實現虛擬接口之間的數據轉發,發送接口的發送緩存中的數據包直接複製到接收接口的接受緩存中,對於本地系統和容器內系統來看就像是一個正常的以太網卡,只是他不需要真正同外部網絡設備通信,速度要快很多.

Docker容器網絡就是利用了這項技術,他在本地主機和容器內分別創建一個虛擬接口,並讓他們彼此連通(這樣的一對接口叫做veth pair).

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

Java 異常處理的十個建議

前言

Java異常處理的十個建議,希望對大家有幫助~

本文已上傳github:

https://github.com/whx123/JavaHome

公眾號:撿田螺的小男孩

一、盡量不要使用e.printStackTrace(),而是使用log打印。

反例:

try{
  // do what you want  
}catch(Exception e){
  e.printStackTrace();
}

正例:

try{
  // do what you want  
}catch(Exception e){
  log.info("你的程序有異常啦,{}",e);
}

理由:

  • printStackTrace()打印出的堆棧日誌跟業務代碼日誌是交錯混合在一起的,通常排查異常日誌不太方便。
  • e.printStackTrace()語句產生的字符串記錄的是堆棧信息,如果信息太長太多,字符串常量池所在的內存塊沒有空間了,即內存滿了,那麼,用戶的請求就卡住啦~

二、catch了異常,但是沒有打印出具體的exception,無法更好定位問題

反例:

try{
  // do what you want  
}catch(Exception e){
  log.info("你的程序有異常啦");
}

正例:

try{
  // do what you want  
}catch(Exception e){
  log.info("你的程序有異常啦,{}",e);
}

理由:

  • 反例中,並沒有把exception出來,到時候排查問題就不好查了啦,到底是SQl寫錯的異常還是IO異常,還是其他呢?所以應該把exception打印到日誌中哦~

三、不要用一個Exception捕捉所有可能的異常

反例:

public void test(){
    try{
        //…拋出 IOException 的代碼調用
        //…拋出 SQLException 的代碼調用
    }catch(Exception e){
        //用基類 Exception 捕捉的所有可能的異常,如果多個層次都這樣捕捉,會丟失原始異常的有效信息哦
        log.info(“Exception in test,exception:{}”, e);
    }
}

正例:

public void test(){
    try{
        //…拋出 IOException 的代碼調用
        //…拋出 SQLException 的代碼調用
    }catch(IOException e){
        //僅僅捕捉 IOException
        log.info(“IOException in test,exception:{}”, e);
    }catch(SQLException e){
        //僅僅捕捉 SQLException
        log.info(“SQLException in test,exception:{}”, e);
    }
}

理由:

  • 用基類 Exception 捕捉的所有可能的異常,如果多個層次都這樣捕捉,會丟失原始異常的有效信息哦

四、記得使用finally關閉流資源或者直接使用try-with-resource

反例:

FileInputStream fdIn = null;
try {
    fdIn = new FileInputStream(new File("/jay.txt"));
    //在這裏關閉流資源?有沒有問題呢?如果發生異常了呢?
    fdIn.close();
} catch (FileNotFoundException e) {
    log.error(e);
} catch (IOException e) {
    log.error(e);
}

正例1:

需要使用finally關閉流資源,如下

FileInputStream fdIn = null;
try {
    fdIn = new FileInputStream(new File("/jay.txt"));
} catch (FileNotFoundException e) {
    log.error(e);
} catch (IOException e) {
    log.error(e);
}finally {
    try {
        if (fdIn != null) {
            fdIn.close();
        }
    } catch (IOException e) {
        log.error(e);
    }
}

正例2:

當然,也可以使用JDK7的新特性try-with-resource來處理,它是Java7提供的一個新功能,它用於自動資源管理。

  • 資源是指在程序用完了之後必須要關閉的對象。
  • try-with-resources保證了每個聲明了的資源在語句結束的時候會被關閉
  • 什麼樣的對象才能當做資源使用呢?只要實現了java.lang.AutoCloseable接口或者java.io.Closeable接口的對象,都OK。
try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) {
    // use resources   
} catch (FileNotFoundException e) {
    log.error(e);
} catch (IOException e) {
    log.error(e);
}

理由:

  • 如果不使用finally或者try-with-resource,當程序發生異常,IO資源流沒關閉,那麼這個IO資源就會被他一直佔著,這樣別人就沒有辦法用了,這就造成資源浪費。

五、捕獲異常與拋出異常必須是完全匹配,或者捕獲異常是拋異常的父類

反例:

//BizException 是 Exception 的子類
public class BizException extends Exception {}
//拋出父類Exception
public static void test() throws Exception {}

try {
    test(); //編譯錯誤
} catch (BizException e) { //捕獲異常子類是沒法匹配的哦
    log.error(e);
}

正例:

//拋齣子類Exception
public static void test() throws BizException {}

try {
    test();
} catch (Exception e) {
    log.error(e);
}

六、捕獲到的異常,不能忽略它,至少打點日誌吧

反例:

public static void testIgnoreException() throws Exception {
    try {       
        // 搞事情
    } catch (Exception e) {     //一般不會有這個異常
        
    }
}

正例:

public static void testIgnoreException() {
    try {
        // 搞事情
    } catch (Exception e) {     //一般不會有這個異常
        log.error("這個異常不應該在這裏出現的,{}",e); 
    }
}

理由:

  • 雖然一個正常情況都不會發生的異常,但是如果你捕獲到它,就不要忽略呀,至少打個日誌吧~

七、注意異常對你的代碼層次結構的侵染(早發現早處理)

反例:

public UserInfo queryUserInfoByUserId(Long userid) throw SQLException {
    //根據用戶Id查詢數據庫
}

正例:

public UserInfo queryUserInfoByUserId(Long userid) {
    try{
        //根據用戶Id查詢數據庫
    }catch(SQLException e){
        log.error("查詢數據庫異常啦,{}",e);
    }finally{
        //關閉連接,清理資源
    }
}

理由:

  • 我們的項目,一般都會把代碼分 Action、Service、Dao 等不同的層次結構,如果你是DAO層處理的異常,儘早處理吧,如果往上 throw SQLException,上層代碼就還是要try catch處理啦,這就污染了你的代碼~

八、自定義封裝異常,不要丟棄原始異常的信息Throwable cause

我們常常會想要在捕獲一個異常后拋出另一個異常,並且希望把原始異常的信息保存下來,這被稱為異常鏈。公司的框架提供統一異常處理就用到異常鏈,我們自定義封裝異常,不要丟棄原始異常的信息,否則排查問題就頭疼啦

反例:

public class TestChainException {
    public void readFile() throws MyException{
        try {
            InputStream is = new FileInputStream("jay.txt");
            Scanner in = new Scanner(is);
            while (in.hasNext()) {
                System.out.println(in.next());
            }
        } catch (FileNotFoundException e) {
            //e 保存異常信息
            throw new MyException("文件在哪裡呢");
        }
    }
    public void invokeReadFile() throws MyException{
        try {
            readFile();
        } catch (MyException e) {
            //e 保存異常信息
            throw new MyException("文件找不到");
        }
    }
    public static void main(String[] args) {
        TestChainException t = new TestChainException();
        try {
            t.invokeReadFile();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}
//MyException 構造器
public MyException(String message) {
        super(message);
    }

運行結果如下,沒有了Throwable cause,不好排查是什麼異常了啦

正例:


public class TestChainException {
    public void readFile() throws MyException{
        try {
            InputStream is = new FileInputStream("jay.txt");
            Scanner in = new Scanner(is);
            while (in.hasNext()) {
                System.out.println(in.next());
            }
        } catch (FileNotFoundException e) {
            //e 保存異常信息
            throw new MyException("文件在哪裡呢", e);
        }
    }
    public void invokeReadFile() throws MyException{
        try {
            readFile();
        } catch (MyException e) {
            //e 保存異常信息
            throw new MyException("文件找不到", e);
        }
    }
    public static void main(String[] args) {
        TestChainException t = new TestChainException();
        try {
            t.invokeReadFile();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}
//MyException 構造器
public MyException(String message, Throwable cause) {
        super(message, cause);
    }

九、運行時異常RuntimeException ,不應該通過catch 的方式來處理,而是先預檢查,比如:NullPointerException處理

反例:

try {
  obj.method() 
} catch (NullPointerException e) {
...
}

正例:

if (obj != null){
   ...
}

十、注意異常匹配的順序,優先捕獲具體的異常

注意異常的匹配順序,因為只有第一個匹配到異常的catch塊才會被執行。如果你希望看到,是NumberFormatException異常,就拋出NumberFormatException,如果是IllegalArgumentException就拋出IllegalArgumentException。

反例:

try {
    doSomething("test exception");
} catch (IllegalArgumentException e) {       
    log.error(e);
} catch (NumberFormatException e) {
    log.error(e);
}

正例:

try {
    doSomething("test exception");
} catch (NumberFormatException e) {       
    log.error(e);
} catch (IllegalArgumentException e) {
    log.error(e);
}

理由:

  • 因為NumberFormatException是IllegalArgumentException 的子類,反例中,不管是哪個異常,都會匹配到IllegalArgumentException,就不會再往下執行啦,因此不知道是否是NumberFormatException。所以需要優先捕獲具體的異常,把NumberFormatException放前面~

公眾號

  • 歡迎關注我個人公眾號,交個朋友,一起學習哈~
  • 如果答案整理有錯,歡迎指出哈,感激不盡~

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

Spring源碼系列(一)–詳細介紹bean組件

簡介

spring-bean 組件是 Spring IoC 的核心,我們可以使用它的 beanFactory 來獲取所需的對象,對象的實例化、屬性裝配和初始化等都可以交給 spring 來管理。

針對 spring-bean 組件,我計劃分成兩篇博客來講解。本文會詳細介紹這個組件,包括以下內容。下一篇再具體分析它的源碼。

  1. spring-bean 組件的相關概念:實例化、屬性裝配、初始化、bean、beanDefinition、beanFactory。
  2. bean 組件的使用:註冊bean、獲取bean、屬性裝配、處理器等。

項目環境說明

正文開始前,先介紹下示例代碼使用的環境等。

工程環境

JDK:1.8.0_231

maven:3.6.1

IDE:Spring Tool Suites4 for Eclipse 4.12

Spring:5.2.6.RELEASE

依賴引入

除了引入 spring,這裏還額外引入了日誌和單元測試。

    <properties>
        <spring.version>5.2.6.RELEASE</spring.version>
    </properties>
    
    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
		<!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- logback -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.28</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
    </dependencies>

幾個重要概念

實例化、屬性裝配和初始化

在 spring-bean 組件的設計中,這三個詞完整、有序地描述了生成一個新對象的整個流程,是非常重要的理論基礎。它們的具體含義如下:

  1. 實例化:創建出一個新對象。
  2. 屬性裝配:給對象的成員屬性賦值。
  3. 初始化:調用對象的初始化方法。

下面使用一段代碼來簡單演示下這個流程。

public class UserService implements IUserService {
    
    private UserDao userDao;
    
    
    public UserService() {
        super();
        System.err.println("UserService構造方法被調用");
        System.err.println("        ||");
        System.err.println("        \\/");
    }
    
    public void init() {
        System.err.println("UserService的init方法被調用");
        System.err.println("        ||");
        System.err.println("        \\/");
    }
    
    
    public UserDao getUserDao() {
        return userDao;
    }
    
    public void setUserDao(UserDao userDao) {
        System.err.println("UserService的屬性裝配中");
        System.err.println("        ||");
        System.err.println("        \\/");
        this.userDao = userDao;
    }
    
}

如果我們將這個 bean 交給 spring 管理,獲取 bean 時會在控制台打印以下內容:

什麼是bean

按照官方的說法, bean 是一個由 Spring IoC 容器實例化、組裝和管理的對象。我認為,這種表述是錯誤的,通過registerSingleton方式註冊的 bean,它就不是由 Spring IoC 容器實例化、組裝,所以,更準確的表述應該是這樣:

某個類的對象、FactoryBean 對象、描述對象或 FactoryBean 描述對象,被註冊到了 Spring IoC 容器,這時通過 Spring IoC 容器獲取的這個類的對象就是 bean。

舉個例子,使用了 Spring 的項目中, Controller 對象、Service 對象、DAO 對象等都屬於 bean。

至於什麼是 IoC 容器,在 spring-bean 組件中,我認為,beanFactory 就屬於 IoC 容器。

什麼是beanFactory

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

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

在 spring-bean 組件中,DefaultListableBeanFactory就是一個完整的 beanFactory 工廠,也可以說是一個 IoC 容器。接下來的例子將直接使用它來作為 beanFactory。

至於其他的接口,這裏也補充說明下。HierarchicalBeanFactory用於提供父子工廠的支持,ConfigurableBeanFactory用於提供配置 beanFactory 的支持,ListableBeanFactory用於提供批量獲取 bean 的支持(不包含父工廠的 bean),AutowireCapableBeanFactory用於提供實例化、屬性裝配、初始化等一系列管理 bean 生命周期的支持。

什麼是beanDefinition

beanDefinaition 是一個描述對象,用來描述 bean 的實例化、初始化等信息。

在 spring-bean 組件中,beanDefinaition主要包含以下四種:

  1. RootBeanDefinition:beanFactory 中最終用於 createBean 的 beanDefinaition,不允許添加 parentName。在 BeanFactory 中以下三種實現類都會被包裝成RootBeanDefinition用於 createBean。
  2. ChildBeanDefinition必須設置 parentName 的 beanDefinaition。當某個 Bean 的描述對象和另外一個的差不多時,我們可以直接定義一個ChildBeanDefinition,並設置它的 parentName 為另外一個的 beanName,這樣就不用重新設置一份。
  3. GenericBeanDefinition:通用的 beanDefinaition,可以設置 parentName,也可以不用設置
  4. AnnotatedGenericBeanDefinition:在GenericBeanDefinition基礎上增加暴露註解數據的方法。

spring-bean 組件提供了BeanDefinitionBuilder用於創建 beanDefinaition,下面的例子會頻繁使用到。

使用例子

入門–簡單地註冊和獲取bean

下面通過一個入門例子來介紹註冊和獲取 bean 的過程。

    @Test
    public void testBase() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        
        // 註冊Bean
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 獲取Bean
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
    }

兩種註冊bean的方式

beanFactory 除了支持註冊 beanDefinition,還允許直接註冊 bean 實例,如下。和前者相比,後者的實例化、屬性裝配和初始化都沒有交給 spring 管理。

    @Test
    public void testRegisterWays() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 註冊Bean-- BeanDefinition方式
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 註冊Bean-- Bean實例方式
        beanFactory.registerSingleton("userService2", new UserService());
        
        // 獲取Bean
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
        IUserService userService2 = (IUserService)beanFactory.getBean("userService2");
        System.err.println(userService2.get("userId"));
    }

當然,這種方式僅支持單例 bean 的註冊,多例的就沒辦法了。

註冊多例bean

默認情況下,我們從 beanFactory 獲取到的 bean 都是單例的,即每次 getBean 獲取到的都是同一個對象,實際項目中,有時我們需要獲取到多例的 bean,這個時候就可以通過設置 beanDefinition 的 scope 來處理。如下:

    @Test
    public void testScope() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 註冊Bean-- BeanDefinition方式
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        rootBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 獲取Bean--通過BeanType
        IUserService userService1 = beanFactory.getBean(IUserService.class);
        IUserService userService2 = beanFactory.getBean(IUserService.class);
        assertNotEquals(userService1, userService2);
    }

多種獲取bean的方式

beanFactory 提供了多種方式來獲取 bean 實例,如下。如果同時使用 beanName 和 beanType,獲取到指定 beanName 的 bean 後會進行類型檢查和類型類型,如果都不通過,將會報錯。

    @Test
    public void testGetBeanWays() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        
        // 註冊Bean
        beanFactory.registerBeanDefinition("userService", rootBeanDefinition);
        
        // 獲取Bean--通過BeanName
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
        // 獲取Bean--通過BeanType
        IUserService userService2 = beanFactory.getBean(IUserService.class);
        System.err.println(userService2.get("userId"));
        // 獲取Bean--通過BeanName+BeanType的方式
        IUserService userService3 = beanFactory.getBean("userService", IUserService.class);
        System.err.println(userService3.get("userId"));
    }

不同形式的beanName

通過 name 獲取 bean,這個 name 包含以下三種形式:

  1. beanName,即註冊 bean 時用的 beanName。這是使用最多的形式,需要注意一點,如果 beanName 對應的 bean 是FactoryBean,並不會返回FactoryBean的實例,而是會返回FactoryBean.getObject方法的返回結果。
  2. alias,即我們通過SimpleAliasRegistry.registerAlias(name, alias)方法註冊到 beanFactory 的別名。這時,需要將 name 解析為 alias 對應的 beanName 來獲取 bean。
  3. ‘&’ + factorybeanName,這時為了獲取FactoryBean的一種特殊格式。
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 註冊Bean--註冊的是一個 FactoryBean
        UserServiceFactoryBean userServiceFactoryBean = new UserServiceFactoryBean();
        beanFactory.registerSingleton("userServiceFactoryBean", userServiceFactoryBean);

        // 註冊BeanName的別名
        beanFactory.registerAlias("userServiceFactoryBean", "userServiceAlias01");

        // 通過BeanName獲取
        assertEquals(userServiceFactoryBean.getObject(), beanFactory.getBean("userServiceFactoryBean"));

        // 通過別名獲取
        assertEquals(userServiceFactoryBean.getObject(), beanFactory.getBean("userServiceAlias01"));

        // 通過&+FactoryBeanName的方式
        assertEquals(userServiceFactoryBean, beanFactory.getBean("&userServiceFactoryBean"));

bean衝突的處理

通過 beanType 的方式獲取 bean,如果存在多個同類型的 bean且無法確定最優先的那一個,就會報錯。

    @Test
    public void testPrimary() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); 
        
        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        
        // 註冊Bean
        beanFactory.registerBeanDefinition("UserRegisterBeanDefinition", rootBeanDefinition);
        beanFactory.registerSingleton("UserRegisterSingleton", new User("zzs002", 19));
        beanFactory.registerSingleton("UserRegisterSingleton2", new User("zzs002", 18));
        
        // 獲取Bean--通過BeanType
        User user = beanFactory.getBean(User.class);
        System.err.println(user);
    }

運行以上方法,將出現 NoUniqueBeanDefinitionException 的異常。

針對上面的這種問題,spring 的處理方法如下:

  1. 檢查是否存在唯一一個通過registerBeanDefinitionisPrimary = true的(存在多個會報錯),存在的話將它作為匹配到的唯一 beanName;
  2. 通過我們註冊的OrderComparator來確定優先值最小的作為唯一 beanName。注意,通過registerSingleton註冊的和通過registerBeanDefinition註冊的,比較的對象是不一樣的,前者比較的對象是 bean 實例,後者比較的對象是 bean 類型,另外,這種方法最好不要存在相同優先級的 bean。

所以,為了解決這種衝突,可以設置BeanDefinition對象的 isPrimary = true,或者為 beanFactory 設置OrderComparator,代碼如下:

    @Test
    public void testPrimary() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 為BeanFactory設置比較器
        beanFactory.setDependencyComparator(new OrderComparator() {

            @Override
            public Integer getPriority(Object obj) {
                return obj.hashCode();
            }
        });

        // 創建BeanDefinition對象
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        // rootBeanDefinition.setPrimary(true); // 設置BeanDefinition對象為isPrimary

        // 註冊Bean
        beanFactory.registerBeanDefinition("userRegisterBeanDefinition", rootBeanDefinition);
        beanFactory.registerSingleton("userRegisterSingleton", new User("zzs002", 19));
        beanFactory.registerSingleton("userRegisterSingleton2", new User("zzs003", 18));

        // 獲取Bean--通過BeanType
        User user = beanFactory.getBean(User.class);
        System.err.println(user);
    }

使用TypeConverter獲取自定義類型的對象

當我們使用 beanType 來獲取 bean 時,如果獲取到的 bean 不是指定的類型,這時,不會立即報錯,beanFactory 會嘗試使用我們註冊的TypeConverter來強制轉換。而這個類型轉換器我們可以自定義設置,如下。

    @Test
    public void testTypeConverter() {
        
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 註冊類型轉換器
        beanFactory.setTypeConverter(new TypeConverterSupport() {
            @SuppressWarnings("unchecked")
            @Override
            public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
                    @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
                // 將User轉換為UserVO
                if(UserVO.class.equals(requiredType) && User.class.isInstance(value)) {
                    User user = (User)value;
                    return (T)new UserVO(user);
                }
                return null;
            }
        });

        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("User", rootBeanDefinition);

        UserVO bean = beanFactory.getBean("User", UserVO.class);
        Assert.assertTrue(UserVO.class.isInstance(bean));
    }

屬性裝配

beanFactory 在進行屬性裝配時,會讀取 beanDefinition 對象中的PropertyValues中的propertyName=propertyValue,所以,我們想要對 bean 注入什麼參數,只要在定義 beanDefinition 時指定就行。

    @Test
    public void testPopulate() {
        // 創建BeanFactory對象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 定義userService的beanDefinition
        AbstractBeanDefinition userServiceBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        // 定義userDao的beanDefinition
        AbstractBeanDefinition userDaoBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserDao.class).getBeanDefinition();
        // 給userService設置裝配屬性
        userServiceBeanDefinition.getPropertyValues().add("userDao", userDaoBeanDefinition);
        // userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        // userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);

        // 註冊Bean
        beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
        beanFactory.registerBeanDefinition("userDao", userDaoBeanDefinition);

        // 獲取Bean
        IUserService userService = (IUserService)beanFactory.getBean("userService");
        userService.save(null);
    }

運行以上方法,發現 userDao 對象被成功注入到了 userService 對象中!

這裏補充一點,beanFactory 除了通過 beanDefinition 中的PropertyValues獲取 propertyName=propertyValue,還可以讀取 bean 中的屬性來自動定義 propertyName=propertyValue,只要設置 beanDefinition 的 autowireMode 就可以了。

bean 實例化、屬性裝配和初始化的處理器

前面講到,我們將 bean 的實例化、屬性裝配和初始化都交給了 spring 處理,然而,有時我們需要在這些節點對 bean 進行自定義的處理,這時就需要用到 beanPostProcessor。

這裏我簡單演示下如何添加處理器,以及處理器的執行時機,至於處理器的具體實現,我就不多擴展了。

    @Test
    public void testPostProcessor() {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 添加實例化處理器
        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
            // 如果這裏我們返回了對象,則beanFactory會將它作為bean直接返回,不再進行bean的實例化、屬性裝配和初始化等操作
            public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
                if(UserService.class.equals(beanClass)) {
                    System.err.println("bean實例化之前的處理。。 --> ");
                }
                return null;
            }

            // 這裏通過返回的布爾值判斷是否需要繼續對bean進行屬性裝配和初始化等操作
            public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("bean實例化之後的處理。。 --> ");
                }
                return true;
            }
        });

        // 添加裝配處理器
        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {

            // 這裏可以在屬性裝配前對參數列表進行調整
            public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("屬性裝配前對參數列表進行調整 --> ");
                }
                return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
            }

        });

        // 添加初始化處理器
        beanFactory.addBeanPostProcessor(new BeanPostProcessor() {

            // 初始化前對bean進行改造
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("初始化前,對Bean進行改造。。 --> ");
                }
                return bean;
            }

            // 初始化后對bean進行改造
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if(UserService.class.isInstance(bean)) {
                    System.err.println("初始化后,對Bean進行改造。。 --> ");
                }
                return bean;
            }
        });
        // 定義userService的beanDefinition
        AbstractBeanDefinition userServiceBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        // 定義userDao的beanDefinition
        AbstractBeanDefinition userDaoBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserDao.class).getBeanDefinition();
        // 給userService添加裝配屬性
        userServiceBeanDefinition.getPropertyValues().add("userDao", userDaoBeanDefinition);
        // 給userService設置初始化方法
        userServiceBeanDefinition.setInitMethodName("init");
        
        // 註冊bean
        beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
        beanFactory.registerBeanDefinition("userDao", userDaoBeanDefinition);

        IUserService userService = (IUserService)beanFactory.getBean("userService");
        System.err.println(userService.get("userId"));
    }

運行以上方法,控制台打印出了整個處理流程。實際開發中,我們可以通過設置處理器來改變改造生成的 bean 。

以上,基本介紹完 spring-bean 組件的使用,下篇博客再分析源碼,如果在分析過程中發現有其他特性,也會在這篇博客的基礎上擴展。

相關源碼請移步: spring-beans

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

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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