虎眼看中國中國SUV新標杆瑞虎7華南試駕會精彩大趴

0DVVT+CVT、1。5T+DCT三種動力組合。1。5T高性能渦輪增壓發動機搭配愛信6速手動變速箱,油耗低至百公里6。3L。瑞虎7后懸採用多連桿獨立懸架+副車架結構,令整車的操控樂趣與駕乘舒適性完美統一。科技配置及安全性方面,瑞虎7搭載了Cloudrive2。

2016年12月18日,奇瑞瑞虎7千人交車儀式暨試駕會在花園城市東莞隆重舉行。近三百輛嶄新的瑞虎7正式交付東莞車主。

12月18日對於奇瑞汽車來說,一個非常特別的日子,1999年12月18日奇瑞汽車第一台整車下線,時至今天—2016年12月18日,奇瑞公司在大家的陪伴下走過了整整19個年頭,以榮獲日內瓦國際車展設計大獎TX概念車為原型的瑞虎7,瑞虎7自9月20日上市以來,上市首月客戶訂單突破2萬台,受到廣大車友的一致追捧,其前瞻性動感造型,出色動力及精準操控,滿足多樣化駕控需求、科技智慧帶來便捷行車生活。瑞虎7的上市熱銷,標志著奇瑞汽車創下中國車企造車水平的又一新高度。

伴隨着動感的音樂,瑞虎7千人交車儀式正式開始,華南大區總經理翟小兵先生為今天活動致辭,一起見證瑞虎7千人交車活動的珍貴瞬間。

瑞虎7不僅是奇瑞歷史上最好的產品,更是中國品牌標杆產品之一。作為奇瑞進入戰略2.0后的首款SUV車型,瑞虎7是在“大奇瑞”國際化團隊協作下,融合捷豹路虎、觀致技術流程開發的專業SUV平台——奇瑞T1X平台開發的首款產品。以未來派前瞻美學的設計風格、自主領先,超越合資的整體性能,實現了品質與品位的完美統一。

在全廣東媒體和現場上百人的見證下,奇瑞汽車廠家領導將瑞虎7車鑰匙交給首批東莞瑞虎7車主,恭喜進300名瑞虎7車主“金”日提車。

瑞虎7上市熱銷,兼具顏值和實力 榮耀之作樹立中國SUV新標杆。

外形方面,轎跑風的“水流”車身設計、側身極速破浪式躍動腰線、超凡奪目的三叉戟式氙氣大燈配虎爪矩陣式LED尾燈,盡顯霸氣與精緻。

動力操控方面,瑞虎7搭載了1.5T+6MT、2.0DVVT+CVT、1.5T+DCT三種動力組合。1.5T高性能渦輪增壓發動機搭配愛信6速手動變速箱,油耗低至百公里6.3L。瑞虎7后懸採用多連桿獨立懸架+副車架結構,令整車的操控樂趣與駕乘舒適性完美統一。

科技配置及安全性方面,瑞虎7搭載了Cloudrive2.0智雲互聯行車系統,採用9英寸超大電容觸摸真彩高清屏,內置4G無線網卡,為全車用戶提供高速流暢的互聯體驗,打造全時在線生活。

瑞虎7採用6D縱梁框架和3R-Body的一體化籠式高強度車身結構,有效提高正側面撞擊的抗擊能力。全系標配ESp电子車身穩定系統、HHC上坡起步輔助系統、TpMS智能胎壓監測系統、前後8探頭雷達,高配車型還裝備了360°全景式影像系統、實時監測車身周邊障礙物,由內而外保護車內乘員的人身安全。

瑞虎7堪稱瑞虎家族“十年磨一劍”的蛻變之作,是一款具有划時代意義的精品SUV。市場對瑞虎7充滿了期待,瑞虎7上市熱銷也給出了完美的交代。瑞虎7在高速增長又競爭激烈的SUV市場中扮演了一名狠角色。已成為中國SUV市場新標杆,征服華南區SUV市場。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

自動網絡搜索(NAS)在語義分割上的應用(一)

【摘要】本文簡單介紹了NAS的發展現況和在語義分割中的應用,並且詳細解讀了兩篇流行的work:DARTS和Auto-DeepLab。

自動網絡搜索

多數神經網絡結構都是基於一些成熟的backbone,如ResNet, MobileNet,稍作改進構建而成來完成不同任務。正因如此,深度神經網絡總被詬病為black-box,因為hyparameter是基於實驗求得而並非通過嚴謹的數學推導。所以,很多DNN研究人員將大量時間花在修改模型和實驗“調參”上面,而忽略novelty本身。許多教授戲稱這種現象為“graduate student descent”。

近兩年,學術界興起了“自動網絡搜索”取代人工設計網絡結構。2016年,Google Brain公開了他們的研究成果NASNet【1】,這是第一個用自動網絡搜索Neural Architecture Seach (NAS)完成的神經網絡,為深度學習打開了新局面。NASNet是由一系列operation(如depth separable conv, max pooling等)疊加而成。至於怎樣選擇operation,作者用強化學習(RL)的方法,用一個controller網絡隨機組合operations生成模塊,通過評估選擇最優模塊組成網絡結構。Google提供的雲上服務Cloud AutoML正是基於NAS方法,根據用戶上傳的數據自動搜索神經網絡再將結果輸出。迄今為止,國內外很多工業界和學術界的AI Lab都有NAS相關工作:Google Brain提出了NasNet, MNasNet和Nas-FPN,Google提出MobileNetv3,Auto-DeepLab和Dense Prediction Cell(DPC);MIT Han Song團隊提出ProxylessNet;Facebook提出FbNet;Baidu提出SETN;騰訊提出FPNAS;小米提出FairNAS;京東AI提出CAS;華為諾亞有提出P-DARTS等。通過各大實驗室對NAS的研究成果,似乎可以看出自動網絡搜索將會成為趨勢。

接下來我們簡單介紹一下NAS的流程,詳細內容可參考【2】。NAS的架構主要包含三部分:搜索空間Search Space,搜索策略Search Strategy和評估機制Performance Estimation(如下圖)。

  • 首先搜索空間會定義一些模塊(cell),operations(例如dilated conv 3×3),和宏觀網絡結構。cell是由若干個operation組合而成。通常來說,NAS會自動搜索出一些可以重複的cell,將cell按照設定的宏觀網絡結構堆疊起來形成network。Zoph et al.提出了兩種cell,normal cell和reduction cell【3】。Reduction cell有改變spatial resolution的功能。這兩種cell在現有的算法中最為常見。此外還有來自CAS網絡【4】的multi-scale cell,作用和ASPP類似可用作decoder。至於對宏觀網絡結構的設定,早期的工作會採用chained-structured,或者帶分支結構multi-branch例如ResNet和DenseNet帶有skip和shortcut。
  • 搜索策略在NAS中至關重要,因為它決定了下一步要選擇哪一個cell或者operation組成網絡結構。搜索的目的就是讓現有的網絡結構在unseen data上得到最好的效果。如前文提到,第一篇工作NASNet中用到了強化學習作為搜索策略。雖然實驗結果尚可,但強化學習耗時過長並且需要大量GPU資源。NASNet就是用幾百個GPU搜索了幾天才完成,這對於資源少的研究人員來說不可能完成。繼NASNet之後早期的工作多是基於RL完成搜索。隨着其他方法的研究,如random search, Bayesian optimization, Gradient-based, evolution algorithms,搜索時間在下降並且GPU使用量也在減少,使得NAS得以普及。感謝https://github.com/D-X-Y/Awesome-NAS 提供了搜索策略的調研。
  • 由於一個神經網絡通常有幾十甚至上百層,結構也很複雜,一個及時並有效的反饋對於減少搜索時間很有幫助。最簡單的方法是把訓練數據分成兩部分,train data用來訓練網絡結構,validation data用來評估當前搜索到的網絡結構。一般來說,一個好的評估機制要兼具速度和準確度,舉個例子,train data少則網絡結構訓練不到位,train data多則搜索時間過長。來自Freiburg大學的Elsken團隊發表了關於NAS的調研,裏面對於評估方法講的很詳細,請挪步【2】。

自動網絡搜索在語義分割上的應用

相比於目標檢測,語義分割可以更好的解析圖像,因為圖像上的每一個pixel都會被分類。所以語義分割可以完成目標檢測無法完成的任務,如自動駕駛的全景分割,醫療圖像診斷,衛星圖分割,背景摳圖和AR換裝等。過去的五年繼FCN之後,很多神經網絡在公開數據集上都有不錯的成績。隨着AI產品落地,對神經網絡性能的要求更為嚴格:在保證accuracy的前提下,在有限制的硬件資源運行並且提升inference速度。這為設計神經網絡增加了很大難度。所以NAS是一個很好的選擇,它可以避免通過大量調參實驗來決定最優網絡結構。在語義分割之前,NAS在圖像分類和目標檢測上均有成功的應用。我們總結了近兩年比較流行的NAS在語義分割上的工作。多數的語義分割的網絡都是encoder-decoder結構,在這裏我們對比了實際搜索的部分,搜索性能耗時和方法。由於每一個work都在不同實驗環境下進行,所以實驗結果沒有直接進行對比。

(1)第一篇將NAS應用於語義分割的工作來自Google DeepLab團隊,作者是Liang-Chieh Chen等一眾大佬。DeepLabv3+的準確度已經達到一定高度,繼而DeepLab團隊將研究方向轉移到了NAS上面。基於和DeepLab同樣的encoder-decoder結構, 在【5】工作中,作者將encoder結構固定,試圖搜索出一個更小的 ASPP,用到的搜索策略是random search。在Cityscapes達到82.7% mIoU,在PASCAL VOC 12上達到87.9%,結果尚可但搜索時間過慢,用370個GPU搜索一周才得到網絡,這也限制了該方法的普及。

(2)同樣來自DeepLab團隊,Auto-DeepLab【6】相比之前的工作在搜索效率上有顯著的提升。這都歸功於它gradient-based的搜索策略DARTS【9】。與上一篇相反,Auto-DeepLab的目標是搜索encoder,而decoder則採用ASPP。從實驗結果來看,Auto-DeepLab的準確度和DeepLabv3+相差不多,但是FLOPs和參數數量卻是DeepLabv3+的一半。後文我們會具體介紹DARTS和Auto-DeepLab。

(3)Customizable Architecture Search (CAS)【4】是來自京東AI Lab的工作。在搜索空間定義了三種cell,分別是normal cell,reduction cell和新提出的multi-scale cell。其中multi-scale cell是ASPP 的功能類似,所以CAS的搜索區間是針對全部網絡。在搜索策略上依然採用DARTS。值得一提的是,在搜索網絡的時候,CAS不僅考慮accuracy,還考慮了每個模塊的GPU time,CPU time,FLOPs和參數數量。這些屬性對於實時任務至關重要。所以CAS在Cityscapes上的準確度是72.3%,雖然沒有很高,但是在TitanXP GPU的速度fps達到108。

(4)來自Adeleide大學的Vladimir Nekrasov團隊在light weight模型上面做了大量工作,而最近也將研究重心轉到了NAS上面。在【7】中,作者採用RL的搜索策略搜索decoder。眾所周知RL非常耗時,所以作者採用知識蒸餾策略和Polyak Averaging方法結合提升搜索速度,而這正是本文的major contribution。

(5)最後一篇來自商湯發布在Artix的文章,採用圖神經網絡GCN作為搜索策略,試圖尋找cell之間最優的連接方式【8】。和CAS類似,在本文中每個模塊的性能如latency也在搜索評估時考慮進去。在Cityscape上GAS達到73.3%mIoU,在TitanXP GPU的速度是102fps。

簡單總結一下上述工作,我們可以發現NAS在語義分割上的應用還算成功,並且很多團隊已經在NAS上進行研究探索。尤其在DARTS類似的高效搜索策略提出后,個人研究者和小團隊也可以構建自己的NAS網絡,而不受制於GPU資源。

DARTS: Differentiable Architecture Search

通常來說搜索空間是一個離散的空間,在DARTS中【9】,作者將搜索空間定義成一個連續空間,這樣一來搜索到的每一個cell都是可導的,可以用stochastic gradient descent來優化。所以DARTS相比RL並不需要大量GPU資源和搜索時間。在實驗中,DARTS成功用在了CNN模型的圖像分類和RNN模型的language modelling任務上。感恩作者提供了源代碼 https://github.com/quark0/darts ,它可以為我們搭建自己NAS模型提供很好的基礎。

正如前文中提到,多數的NAS網絡會搜索不同類型的可重複的cell,然後將cell連接起來構成神經網絡。這個cell通常用directed acyclic graph(DAG)表示。一個DAG cell包含N個有順序的node,每一個node可以看成在它前面所有node的結合(就像feature map一樣是一個latent representation)。從node i 到node j的連接是某一種operation記作o(i,j). 每一個cell都有兩個input node和一個output node。下圖中是一個DARTS展示的reduction cell。我們可以看到cell k 有兩個input nodes 分別是c_{k-1}和c_{k-2} (來自cell k-1和cell k-2的output node),cell k包含了4個immediate nodes和一個output node c_{k} 。o(i,j)是邊緣上的max_pool_3x3,max_pool等。理論上從一個node到相鄰的node可以有很多種operation,所以搜索最優網絡結構也可以看成是選擇每一個邊上最佳的operation。

DARTS將每對node之間的每一個operation都賦予一個weight,最優解可以用softmax求得,也就是說有最大probability的path代表最優operation,這也是DARTS的核心部分。DARTS在搜索空間中定義了兩種cell,reduction cell和normal cell。宏觀網絡結構是固定的,作者採用了簡單的chained-structured,將reduction cell放在了網絡結構的1/3和2/3處。所以說在搜索的過程中,cell內部不斷更新而宏觀結構沒有變化。我們定義operation的參數為W,將cell中operation的weight記為Alpha。根據論文和source code,我們總結了DARTS的搜索流程如下圖。

網絡搜索的第一步是對模型結構,optimizer,loss進行初始化。文中定義了幾種operation,代碼中的定義在operation.OPS, 兩種cell在代碼中的定義是genotypes.PROMITIVES. 參數Alpha在代碼中定義為arch_parameters()={alphas.normal, alphas.reduce}. 在搜索過程中,train data被分成兩部分,train patch用來訓練網絡參數W而validation patch用來評估搜索到的網絡結構。在代碼中,搜索過程的核心部分在architect.step()。網絡搜索的目標函數就是讓validation在現有網絡的loss最小,文章中公式(3)給出了objective:

為了減少搜索時間,每一輪只用一個training patch去更新參數W計算train loss。在計算Alpha的時候涉及到二階求導,稍微複雜一點,但是論文和代碼都給了詳細解釋,這裏不贅述,代碼中architect._hession_vector_product是求二階導的實現。在更新W和Alpha之後,最優operation通過softmax來計算。文中保留了top-k probability的operation。W和Alpha不斷計算更新直到搜索過程結束。

文中進行了大量實驗,我們這裏只介紹一下在CIFAR-10數據上面進行的圖像分類任務。作者將DARTS與傳統人工設計的網絡DenseNet,和幾個其他常見的NAS網絡進行對比,如AmoeNet和ENet都是常被提及的。DARTS在準確度上優於其他所有算法,並且在搜索速度上明顯比RL快很多。由於結構簡單效果好,而且不需要大量GPU和搜索時間,DARTS已經被大量引用。

Auto-DeepLab: Hierarchical Neural Architecture Search for Semantic Image Segmentation

基於DARTS的結構,Google DeepLab團隊提出了Auto-DeepLab並發表在2019年CVPR上。在tensorflow deeplab官網上公布了nas backbone並且給出了可以訓練的模型結構,但是搜索過程並沒有公開。於是我們訓練了給出的nas網絡結構,在沒有任何pre-training的情況下與deeplab v3+進行對比。代碼參考 https://github.com/tensorflow/models/tree/master/research/deeplab 。

在DARTS中,宏觀網絡結構是提前定義的,而在Auto-DeepLab中宏觀網絡結構也是搜索的一部分。繼承自DeepLab v3+的encoder-decoder結構,Auto-DeepLab的目的是搜索Encoder代替現有的xception65,MobileNet等backbone,decoder採用ASPP。在搜索空間中定義了reduction cell,normal cell和一些operation。Reduction cell用來改變spatial resolution,使其變大兩倍,或不變,或變小兩倍。為了保證feature map的精度,Auto-DeepLab規定最多downsampling 32倍 (s=32)。下圖定義了宏觀網絡結構(左)和cell內部的結構(右)。

Auto-DeepLab定義了12個cell,而上圖(左)中前面兩個白色的node是固定的兩層為了縮小spatial resolution。如圖左灰色箭頭所示,正式搜索之後,每一個cell的位置都有多種cell類型可以選擇:可以來自於當前cell相同的spatial resolution的cell,也可以是比當前cell的spatial resolution大一倍或小一倍的cell。作者將這些空間路徑(灰色箭頭表示的路徑)也賦予一個weight,記作Beta。如圖右,每一個cell的輸出都是由相鄰spatial resolution的cell結合而成,而Beta的值可以理解成不同路徑的probability。為了更直觀,我們把圖右的三個cell分別用藍色,黃色和綠色標註,對應圖左的三個cell。與DARTS類似,我們將operation的parameters記作W,將cell內部operation的權重記作Alpha。所以搜索最優網絡結構,即迭代計算並更新W,Alpha和Beta。文中給出每一個cell的實際輸出為:

從上面公式可以看出,W和{Alpha,Beta}要分別計算和更新。所有的weight都是非負數。Alpha的計算方式依然是ArgMax,而計算Beta用了經典的貪心算法Viterbi算法。下圖給出的宏觀網絡結構是基於Cityscapes搜索到的結果,對應代碼中的backbone是[0,0,0,1,2,1,2,2,3,3,2,1], 数字代表downsample倍數。在模型中,每一個cell中的node由兩個路徑組成,如圖右。

文中用了三組開源數據PASCAL VOC 12, Cityscapes和ADE20k做了對比實驗。具體實驗參數設置和對比算法在論文中有詳細說明,這裏只對比和Deeplab v3+。Cityscapes訓練數據尺寸是[769×769],而PASCAL VOC 12和ADE20k訓練數據尺寸是[513×513]。一般來說,Auto-DeepLab和DeepLabv3+準確度相差無幾,但是速度上要快2.33倍,並且Auto-DeepLab可以從零開始訓練。

除了文中給出的實驗結果以外,我們在PASCAL VOC 12數據上從零開始訓練了Auto-DeepLab,用代碼中給出的模型結構,並且與DeepLabv3+(xception65)進行結果對比。但是並不是所有結果都能復現,分析原因大概是這樣:首先,上文中給出的模型結構是用Cityscapes數據集搜索得到,也許在PASCAL VOC 12上並不是最優解;其次沒有用ImageNet做pre-training,訓練環境也不同。我們在下面表格中對比了FLOPs, 參數數量, 在K80 GPU上面的fps和mIoU。

下圖中直觀對比了ground truth(第二列),deeplabv3+(第三列)和Auto-DeepLab-S(第四列)的分割結果。與上面的mIoU一致,DeepLabv3+的分割結果要比Auto-DeepLab更精準一些,尤其是在邊緣。對於簡單的圖像案例,兩者分割結果相差無幾,但是在較難的情況下,Auto-DeepLab會有很大誤差(在第三個案例中,Auto-DeepLab將女孩識別成狗)。

總結

本文簡單介紹了NAS的發展現況和在語義分割中的應用,並且詳細解讀了兩篇流行的work:DARTS和Auto-DeepLab。從整體實驗結果來看,還不能看出NAS的方法比傳統的模型有壓倒性優勢,尤其在準確度上。但是NAS給深度學習注入了新鮮的血液,為研究者提供了一種新的思路,並且還有很大的提升空間和待開發領域。也許人工設計網絡結構將會被自動網絡搜索取代。

翻譯或有誤差,請參考原文https://medium.com/@majingting2014/neural-architecture-search-on-semantic-segmentation-1801ee48d6c4

 

對這塊比較關注的同學可以移步繼續閱讀《自動網絡搜索(NAS)在語義分割上的應用(二)》

References

[1] Zoph, Barret, and Quoc V. Le. “Neural architecture search with reinforcement learning.” The International Conference on Learning Representations (ICLR) (2017)

[2] Elsken, Thomas, Jan Hendrik Metzen, and Frank Hutter. “Neural Architecture Search: A Survey.” Journal of Machine Learning Research 20.55 (2019): 1-21.

[3] Zoph, Barret, et al. “Learning transferable architectures for scalable image recognition.” Proceedings of the IEEE conference on computer vision and pattern recognition (CVPR). 2018.

[4] Zhang, Yiheng, et al. “Customizable Architecture Search for Semantic Segmentation.” Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR). 2019.

[5] Chen, Liang-Chieh, et al. “Searching for efficient multi-scale architectures for dense image prediction.” Advances in Neural Information Processing Systems (NIPS). 2018.

[6] Liu, Chenxi, et al. “Auto-deeplab: Hierarchical neural architecture search for semantic image segmentation.” Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR). 2019.

[7] Nekrasov, Vladimir, et al. “Fast neural architecture search of compact semantic segmentation models via auxiliary cells.” Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR). 2019.

[8] Lin, Peiwen, et al. “Graph-guided Architecture Search for Real-time Semantic Segmentation.” arXiv preprint arXiv:1909.06793 (2019).

[9] Liu, Hanxiao, Karen Simonyan, and Yiming Yang. “Darts: Differentiable architecture search.” The International Conference on Learning Representations (ICLR) (2019).

點擊關注,第一時間了解華為雲新鮮技術~

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

【其他文章推薦】

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

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

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

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

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

一文說通Dotnet Core的後台任務

這是一文說通系列的第二篇,裏面有些內容會用到第一篇中間件的部分概念。如果需要,可以參看第一篇:一文說通Dotnet Core的中間件

一、前言

後台任務在一些特殊的應用場合,有相當的需求。

比方,我們需要實現一個定時任務、或周期性的任務、或非API輸出的業務響應、或不允許併發的業務處理,像提現、支付回調等,都需要用到後台任務。

通常,我們在實現後台任務時,有兩種選擇:WebAPI和Console。

下面,我們會用實際的代碼,來理清這兩種工程模式下,後台任務的開發方式。

    為了防止不提供原網址的轉載,特在這裏加上原文鏈接:https://www.cnblogs.com/tiger-wang/p/13081020.html

二、開發環境&基礎工程

這個Demo的開發環境是:Mac + VS Code + Dotnet Core 3.1.2。

$ dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.201
 Commit:    b1768b4ae7

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.15
 OS Platform: Darwin
 RID:         osx.10.15-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.1.201/

Host (useful for support):
  Version: 3.1.3
  Commit:  4a9f85e9f8

.NET Core SDKs installed:
  3.1.201 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

首先,在這個環境下建立工程:

  1. 創建Solution
% dotnet new sln -o demo
The template "Solution File" was created successfully.
  1. 這次,我們用Webapi創建工程
cd demo
% dotnet new webapi -o webapidemo
The template "ASP.NET Core Web API" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on webapidemo/webapidemo.csproj...
  Restore completed in 179.13 ms for demo/demo.csproj.

Restore succeeded.
% dotnet new console -o consoledemo
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on consoledemo/consoledemo.csproj...
  Determining projects to restore...
  Restored consoledemo/consoledemo.csproj (in 143 ms).

Restore succeeded.
  1. 把工程加到Solution中
% dotnet sln add webapidemo/webapidemo.csproj
% dotnet sln add consoledemo/consoledemo.csproj

基礎工程搭建完成。

三、在WebAPI下實現一個後台任務

WebAPI下後台任務需要作為託管服務來實現,而託管服務,需要實現IHostedService接口。

首先,我們需要引入一個庫:

cd webapidemo
% dotnet add package Microsoft.Extensions.Hosting

引入后,我們就有了IHostedService

下面,我們來做一個IHostedService的派生託管類:

namespace webapidemo
{
    public class DemoService : IHostedService
    {
        public DemoService()
        
{
        }

        public Task StartAsync(CancellationToken cancellationToken)
        
{
            throw new NotImplementedException();
        }

        public Task StopAsync(CancellationToken cancellationToken)
        
{
            throw new NotImplementedException();
        }
    }
}

IHostedService需要實現兩個方法:StartAsyncStopAsync。其中:

StartAsync: 用於啟動後台任務;

StopAsync:主機Host正常關閉時觸發。

如果派生類中有任何非託管資源,那還可以引入IDisposable,並通過實現Dispose來清理非託管資源。

這個類生成后,我們將這個類注入到ConfigureServices中,以使這個類在Startup.Configure調用之前被調用:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddHostedService<DemoService>();
}

下面,我們用一個定時器的後台任務,來加深理解:

namespace webapidemo
{
    public class TimerService : IHostedService, IDisposable
    {
          /* 下面這兩個參數是演示需要,非必須 */
        private readonly ILogger _logger;
        private int executionCount = 0;

          /* 這個是定時器 */
        private Timer _timer;

        public TimerService(ILogger<TimerService> logger)
        
{
            _logger = logger;
        }

        public void Dispose()
        
{
            _timer?.Dispose();

        }

        private void DoWork(object state)
        
{
            var count = Interlocked.Increment(ref executionCount);

            _logger.LogInformation($"Service proccessing {count}");
        }

        public Task StartAsync(CancellationToken cancellationToken)
        
{
            _logger.LogInformation("Service starting");

            _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        
{
            _logger.LogInformation("Service stopping");

            _timer?.Change(Timeout.Infinite, 0);
            return Task.CompletedTask;
        }
    }
}

注入到ConfigureServices中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddHostedService<TimerService>();
}

就OK了。代碼比較簡單,就不解釋了。

四、WebAPI後台任務的依賴注入變形

上一節的示例,是一個簡單的形態。

下面,我們按照標準的依賴注入,實現一下這個定時器。

依賴注入的簡單樣式,請參見一文說通Dotnet Core的中間件。

首先,我們創建一個接口IWorkService

namespace webapidemo
{
    public interface IWorkService
    {
        Task DoWork();
    }
}

再根據IWorkService,建立一個實體類:

namespace webapidemo
{
    public class WorkService : IWorkService
    {
        private readonly ILogger _logger;
        private Timer _timer;
        private int executionCount = 0;

        public WorkService(ILogger<WorkService> logger)
        
{
            _logger = logger;
        }

        public async Task DoWork()
        
{
            var count = Interlocked.Increment(ref executionCount);

            _logger.LogInformation($"Service proccessing {count}");
        }
    }
}

這樣就建好了依賴的全部內容。

下面,創建託管類:

namespace webapidemo
{
    public class HostedService : IHostedService, IDisposable
    {
        private readonly ILogger<HostedService> _logger;
        public IServiceProvider Services { get; }
        private Timer _timer;

        public HostedService(IServiceProvider services, ILogger<HostedService> logger)
        
{
            Services = services;
            _logger = logger;
        }

          public void Dispose()
        
{
            _timer?.Dispose();
        }

        private void DoWork(object state)
        
{
            _logger.LogInformation("Service working");

            using (var scope = Services.CreateScope())
            {
                var scopedProcessingService =
                    scope.ServiceProvider
                        .GetRequiredService<IWorkService>();

                scopedProcessingService.DoWork().GetAwaiter().GetResult();
            }
        }

        public Task StartAsync(CancellationToken cancellationToken)
        
{
            _logger.LogInformation("Service starting");

            _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
            return Task.CompletedTask;
        }


        public Task StopAsync(CancellationToken cancellationToken)
        
{
            _logger.LogInformation("Service stopping");

            _timer?.Change(Timeout.Infinite, 0);
            return Task.CompletedTask;
        }
    }
}

把託管類注入到ConfigureServices中:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddHostedService<HostedService>();
    services.AddSingleton<IWorkService, WorkService>();
}

這樣就完成了。

這種模式下,可以根據注入的內容切換應用的執行內容。不過,這種模式需要注意services.AddSingletonservices.AddScopedservices.AddTransient的區別。

五、Console下的後台任務

Console應用本身就是後台運行,所以區別於WebAPI,它不需要託管運行,也不需要Microsoft.Extensions.Hosting庫。

我們要做的,就是讓程序運行,就OK。

下面是一個簡單的Console模板:

namespace consoledemo
{
    class Program
    {

        private static AutoResetEvent _exitEvent;

        static async Task Main(string[] args)
        
{
                /* 確保程序只有一個實例在運行 */
            bool isRuned;
            Mutex mutex = new Mutex(true"OnlyRunOneInstance", out isRuned);
            if (!isRuned)
                return;

            await DoWork();

                        /* 後台等待 */
            _exitEvent = new AutoResetEvent(false);
            _exitEvent.WaitOne();
        }

        private static async Task DoWork()
        
{
            throw new NotImplementedException();
        }
    }
}

這個模板有兩個關鍵的內容:

  1. 單實例運行:通常後台任務,只需要有一個實例運行。所以,第一個小段,是解決單實例運行的。多次啟動時,除了第一個實例外,其它的實例會自動退出;
  2. 後台等待:看過很多人寫的,在這兒做後台等待時,用了一個無限的循環。類似於下面的:
while(true)
{
    Thread.Sleep(1000);
}

這種方式也沒什麼太大的問題。不過,這段代碼總是要消耗CPU的計算量,雖然很少,但做為後台任務,或者說Service,畢竟是一種消耗,而且看着不夠高大上。

當然如果我們需要中斷,我們也可以把這個模板改成這樣:

namespace consoledemo
{
    class Program
    {

        private static AutoResetEvent _exitEvent;

        static async Task Main(string[] args)
        
{
            bool isRuned;
            Mutex mutex = new Mutex(true"OnlyRunOneInstance", out isRuned);
            if (!isRuned)
                return;

            _exitEvent = new AutoResetEvent(false);
            await DoWork(_exitEvent);
            _exitEvent.WaitOne();
        }

        private static async Task DoWork(AutoResetEvent _exitEvent)
        
{
            /* Your Code Here */

            _exitEvent.Set();
        }
    }
}

這樣就可以根據需要,來實現中斷程序並退出。

六、Console應用的其它運行方式

上一節介紹的Console,其實是一個應用程序。

在實際應用中,Console程序跑在Linux服務器上,我們可能會有一些其它的要求:

  1. 定時運行

Linux上有一個Service,叫cron,是一個用來定時執行程序的服務。

這個服務的設定,需要另一個命令:crontab,位置在/usr/bin下。

具體命令格式這兒不做解釋,網上隨便查。

  1. 運行到後台

命令後邊加個&字符即可:

$ ./command &
  1. 運行為Service

需要持續運行的應用,如果以Console的形態存在,則設置為Service是最好的方式。

Linux下,設置一個應用為Service很簡單,就這麼簡單三步:

第一步:在/etc/systemd/system下面,創建一個service文件,例如command.service

[Unit]
# Service的描述,隨便寫
Description=Command

[Service]
RestartSec=2s
Type=simple
# 執行應用的默認用戶。應用如果沒有特殊要求,最好別用root運行
User=your_user_name
Group=your_group_name
# 應用的目錄,絕對路徑
WorkingDirectory=your_app_folder
# 應用的啟動路徑
ExecStart=your_app_folder/your_app
Restart=always

[Install]
WantedBy=multi-user.target

差不多就這麼個格式。參數的詳細說明可以去網上查,實際除了設置,就是運行了一個腳本。

第二步:把這個command.service加上運行權限:

# chmod +x ./command.service

第三步:註冊為Service:

# systemctl enable command.service

完成。

為了配合應用,還需要記住兩個命令:啟動和關閉Service

# #啟動Service
# systemctl start command.service
# #關閉Service
# systemctl stop command.service

七、寫在後邊的話

今天這個文章,是因為前两天,一個兄弟跑過來問我關於數據總線的實現方式,而想到的一個點。

很多時候,大家在寫代碼的時候,會有一種固有的思想:寫WebAPI,就想在這個框架中把所有的內容都實現了。這其實不算是一個很好的想法。WebAPI,在業務層面,就應該只是實現簡單的處理請求,返回結果的工作,而後台任務跟這個內容截然不同,通常它只做處理,不做返回 — 事實上也不太好返回,要麼客戶端等待時間太長,要麼客戶端已經斷掉了。換句話說,用WebAPI實現總線,絕不是一個好的方式。

不過,Console運行為Service,倒是一個總線應用的絕好方式。如果需要按序執行,可以配合MQ服務器,例如RabbitMQ,來實現消息的按序處理。

再說代碼。很多需求,本來可以用很簡單的方式實現。模式這個東西,用來面試,用來講課,都是很好的內容,但實際開發中,如果有更簡單更有效的方式,用起來!Coding的工作是實現,而不是秀技術。當然,能否找到簡單有效的方式,這個可能跟實際的技術面有關係。但這並不是一個不能跨越的坎。

多看,多想,每天成長一點點!

今天的代碼,在:https://github.com/humornif/Demo-Code/tree/master/0012/demo

(全文完)

 

 

微信公眾號:老王Plus

掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送

本文版權歸作者所有,轉載請保留此聲明和原文鏈接

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

帶你輕鬆了解C# Lock 關鍵字

  相信絕大多數.NET玩家和我一樣,常常使用Timer這個對象,而在WPF中使用DispatcherTimer的人也是很多,DispatcherTimer是在UI線程跑的。我們的程序中大多數都會充斥很多Timer,可以理解它是一個線程,它繼承自 System.Windows.Threading 。

  程序中也許會有一些靜態變量或是單例模式的對象來讓不同的頁面進行交互,但也就是這樣讓每個線程之間打架提供了基礎。因為資源是單獨的,就像是腳踩兩隻船的人,必定會翻車。例如一個List集合,你在一個線程中對它進行了操作,在同步瞬間的另線程中,如果不對它謹慎處理,就會造成 “集合已修改;可能無法執行枚舉操作”。當然我們說的不是關於集合的相關問題,而是關於資源分配的,當然在資源搶奪上,是在耗時的線程中才會出現的,例如下面的這張圖。

 

  這種耗時的操作,並且在同步線程中,沒有對線程進行封裝,很容易造成資源搶奪問題,假如Object是個集合,我在中間把它改了,下一秒的其它線程對它進行臟讀了,就會產生錯誤,我們可以通過Lock關鍵字。

  首先在Microsoft文檔中對Lock的說明是,lock 關鍵字可以用來確保代碼塊完成運行,而不會被其他線程中斷。這是通過在代碼塊運行期間為給定對象獲取互斥鎖來實現的。

  不過我們需要注意的是Lock本質上Monitor.Enter,Monitor.Enter會使值類型裝箱,每次Lock的是裝箱后的對象。Lock其實是類似編譯器的語法糖,因此編譯器直接限制住不能lock值類型,為啥呢,你仔細想想,每次裝箱后都是不同的對象,我怎麼判斷? object.ReferenceEquals 每次都是false…還有就是千萬不要Lock 字符串,簡單來說Lock字符串之後,只要是你以後有字符串匹配和你Lock里的內容有一樣的,那個該字符串也會被鎖定,相當於死鎖了。

  Lock和Monitor的區別不是很大,具體看以下代碼。

private static object obj = new object();
        public void LockSomething()
        {
            lock (obj)
            {
                dosomething();
            }
        }
        public void MonitorSomeThing()
        {
                Monitor.Enter(obj);
                dosomething();
                Monitor.Exit(obj);
        }
        public void dosomething()
        { 
            //做具體的事情
        }

   lock和Monitor是.NET用一個特殊結構實現的,Monitor對象是完全託管的、完全可移植的,並且在操作系統資源要求方面可能更為有效,同步速度較快,但不能跨進程同步。主要作用是鎖定臨界區,使臨界區代碼只能被獲得鎖的線程執行。Monitor.Wait和Monitor.Pulse用於線程同步,類似信號操作,個人感覺使用比較複雜,容易造成死鎖。

  lock就是封裝了Monitor.Enter和Monitor.Exit方法其實非常不難理解,只要確定Lock在啥時候用,該怎麼用就可以了,總結一句話。經常會應用於防止多線程操作導致公用變量值出現不確定的異常,用於確保操作的安全性。

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

【其他文章推薦】

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

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

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

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

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

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

工作5年,一位來自廣西測試妹子學習《自動化測試》經驗分享

本文來自一位妹子的投稿

大家好,我是黎婷,一位來自廣西南寧的測試妹子,工作5年,藉著原作者平台,今天給大家分享一下近期在學習《自動化測試實戰寶典》一書過程中的一些感想、學習心得,希望對大家有所幫助。

1. 來自大多數測試人員的困惑

在當前整個軟件行業的高速發展中,作為測試人員如何在這個急速浪潮中站穩腳跟,想必大多數人都知道需要學習更多的知識與技能,鞏固並加強自身能力,才能保證自己不被淘汰。

然而目前相當多行業內的測試人員基本都會面臨這種困惑:大體的方向是確定了,但是具體學什麼,怎麼學,落實到地的方案卻比較少有人能想明白。

2. 自己的經歷

想到自己當初在第一家公司離職選擇跳槽的時候,也是帶着這樣的迷茫進入了一家境外電商公司,初入這家公司時,正值質量管理部門大力推廣要求各種自動化測試工具使用的風潮中,基於對KPI的追求,於是開始着手查找當下各種流行的框架工具,為了KPI為了所謂的數據好看使用工具而學習測試工具,前期也是投入較多的時間,成果也是有的,但是很快也就放棄了使用:時間投入高,產出低,還不如手工測試

然而隨着公司業務的快速發展,產品迭代頻繁,重複性的回歸工作慢慢的佔據很大一部分工作時間,加班處理工作已經成了常態,鑒於這種處境,又開始重拾了測試框架與工具,這回的目的很明確:在能保障產品質量的同時也要提高自己的工作效率,這些觀點在最近學習的書上提到關於自動化測試的意義點也是不謀而合的。

因此在這種狀況下開始主動學習工具和框架時,也多了份思考:如何利用這個框架的特點和優勢契合到實際工作的使用,能夠剛好解決工作中的痛點。帶着這個目的去學習工具使用工具,搞起的自動化,成效也是有很明顯的提升,從測試環境的回歸測試效率提升,以及後續線上核心功能的持續監控落地,都給自己實際工作帶來很大的推動促進作用,自己也有更多時間投入到更有價值的專項測試中,而不是疲於日常的重複性工作不得發展。

繼續帶着這份思考,本人最近花了一兩個星期大體研讀了《自動化測試實戰寶典 Robot Framework+Python》一書,基本成功快速地掌握了用Robot Framework工具進行接口自動化測試。

在這次學習的過程中,不僅僅能掌握到技術方面的知識,同時發覺它不只是一本工具書,更是一本有着完整且與時俱進的測試理論體系知識的書籍,書中成熟的思想理論加循序漸進的實戰講解,讓我從一個之前只聞RF名不識其真實面目的RF小白,到現在只通過一兩周時間就能快速掌握RF進行自動化測試。當然,在這麼短的時間內,很難一下子消化完這麼豐富的知識點,在接下來的測試工作學習中,希望能在本書的指引下,更深入學習RF框架來推動自己的工作更高效、高質量的交付。

3. 學習后的幾點心得

接下來想分享自己在學習該書籍過程中的幾點心得:

心得之一:筆者在接口測試項目實戰章節中講到的一個點:

做好接口測試除了在‘術’上融合工程化的最佳實踐,武裝方式方法,在‘道’上更需要有一個正確的認知和決心。正確的認知和決心更加強調的是團隊成員之間的目標是否一致,大家是不是真心認同這件事,而不是被迫地或者礙於領導下達的任務指標才去做。所有流於形式或者並非發自內心而被迫做的事情,最後收效往往都是甚微的,即便給你的是一把‘屠龍刀’,也發揮不出應有的作用。只有大家有意識地從心出發,主動尋求改變、追求質量和高效率、精益求精,再結合合適的工程實踐,才能做到‘道’和‘術’的雙重兼顧。”

對於筆者提到的這個點,跟讀者我的實際工作遇到的情況也是非常契合的

在自己之前的公司中,也多多少少接觸到各種測試工具和框架,在工作之初去學習這類工具框架的時候,與其說初始的目的是為以後長遠的職業發展學習一項或多項技能鞏固能力,更不如說是基於領導的各種任務要求而“心不甘情不願”的去學習,在抱着這種想法去學習的技能,不僅不深入而且成果甚微,更是浪費了很多時間,最後甚至磨滅了繼續學習下去的念頭。

直到工作生涯中後期,才開始有意識自發的通過學習一些工具和技術,來真正地解決實際工作中的問題,在這個過程中領悟到學習的技術知識確切能給自己的工作帶來“收大於支”的成果時,不僅工作能力得到提升,內心也認可了技術帶來的改變,讓“屠龍寶刀”發揮其最大化作用,也真正契合了筆者提到的“道”和術的雙重兼顧

當內心開始認可了這項東西時,會發現學習技術的路上原來如此有趣而引人入勝。利用自己所學,發揮其最大用處,會將自己的學習帶入一個良性的循環中,讓自己不只是在日常工作中得到效益,長遠來講,何嘗不是對將來的職業生涯鋪墊穩固的基石呢。

心得之二:談談學習過程中解決問題發思路。

在學習接口測試實戰項目時,在一些基礎環境配置、導入三方庫、接口認證鑒權等方面都遇到了阻礙。在解決這些問題的過程中,分享下我對於學習過程中遇到問題解決問題的一個思路。

  1. 首先自己在學習一些新技術時,心態都會放得比較平,因為面對新東西新技術,會遇到問題是在所難免的,一帆風順的狀況基本不存在,所以一定要擁有一份积極的心態,而不是遇到問題就開始心煩氣躁,不僅不利於解決問題,還很容易喪失繼續學習下去的信心。
  2. 其次就是解決問題的方法了,這次學習的RF框架,提供了詳細且比較全面的日誌信息,要學會通過具體報錯信息定位問題產生的原因。通過確定問題關鍵字在網絡上各大技術論壇社區平台進行搜索查找,對搜索到的結果也需要學會進行篩選總結,總結出搜索出來的解決方法的規律去解決問題,多次嘗試,不怕試錯,問題在這個階段80%都能得到解決。而不太建議一遇到問題就去找他人協助解決,首先溝通是有成本的,其次也會佔用他人寶貴的時間。
  3. 如果嘗試多次自己解決還是無果,接下來才是找能幫助自己解決問題可能性比較大的小夥伴,因為漫無目的的請教也是浪費雙方時間的行為。有其他小夥伴的協助,也很難有不能解決的問題了。最後解決問題之後的記錄總結也是很重要,作為學習的沉澱,可以在下次遇到同樣問題能快速解決,也可以給後續遇到同樣問題的小夥伴提供幫助。

總的來說,在這次學習RF接口測試中,自己所在部門的小夥伴都很熱心給予了很大的支持幫助,自己也才能在這麼短的時間掌握該項技能。大家能在百忙之中抽出時間給予協助是非常可貴的,作為請教者,在今後的學習中,也要提供給他人力所能及的協助。畢竟,學習是相互的,大家彙集起來的知識泉源才能壯大,也能更好推動大家一起發展。

4. 談一談RF框架可以優化的幾個點

從RF框架本身來談,它的優勢是絕對佔主導的,接下來談一談在使用RF框架中個人覺得還可以優化的幾點:

  1. RIDE編輯器性能有待提高,在加載現有的測試部的接口腳本,左側目錄樹經常出現卡頓,會一定程度影響腳本編寫效率。解決辦法之一可以嘗試用其他編輯器如pycharm等替代。

  2. 希望可以支持通過抓包工具抓取到接口數據進行轉化,自動生成符合腳本編寫格式的關鍵字,可以進一步提高編寫腳本的效率。如httprunner的har2case命令可以很方便的將har文件轉換成對應框架支持的腳本書寫規範。當然對於這塊優化點,也是有解決辦法的:可以通過自己寫工具去轉換抓到的har文件,而目前自己所在團隊也已經有大佬實現拓展了該工具

  3. robot framework 的編輯器RIDE目前對Python3 的兼容性還不是特別好,主要是擴展庫的語法有比較多不兼容。當然RF框架本身一直是支持Python3的,若習慣使用如pycharm這些編輯器來編寫腳本,這塊不足也不存在了。但如果是習慣使用RIDE編輯器的話,對於當前主流使用的Python3維護兼容性成本還是比較高。

總得來說,本次的學習實戰時間還是比較短暫,而本書提供的技術及知識理論是非常豐富的,在接下來的學習中,還需要繼續精讀細讀,慢慢消化,利用好這把“屠龍寶刀”加強自己的“戰鬥值”。

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

【其他文章推薦】

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

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

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

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

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

面試官:說說Redis的Hash底層 我:……(來自閱文的面試題)

redis源碼分析系列文章

[Redis源碼系列]在Liunx安裝和常見API 

為什麼要從Redis源碼分析 

String底層實現——動態字符串SDS 

Redis的雙向鏈表一文全知道

前言

hello,各位小可愛們,又見面了。今天這篇文章來自去年面試閱文的面試題,結果被虐了。這一part不說了,下次專門開一篇,寫下我面試被虐的名場面,尷尬的不行,全程尬聊。哈哈哈哈,話不多說,開始把。

 

今天要寫Redis的Hash類型,如果有對Redis不熟悉,或者對其他數據類型感興趣的,可以移步上面的系列文章。(最上面的最上面最上面,重要的事情說三遍)

在Redis中Hash類型的應用非常廣泛,其中key到value的映射就通過字典結構來維護的。記筆記,此處要考。

API使用

API的使用比較簡單,所以以下就粗略的寫了。

插入數據hset

使用hset命令往myhash中插入兩個key,value的鍵值對,分別是(name,zhangsan)和(age,20),返回值當前的myhash的長度。

獲取數據hget

使用hget命令獲取myhash中key為name的value值。

獲取所有數據hgetall

使用hgetall命令獲取myhash中所有的key和value值。

獲取所有key

使用hkeys命令獲取myhash中所有的key值。

獲取長度

使用hlen命令獲取myhash的長度。

獲取所有value

使用hvals命令獲取myhash中所有的value值。

具體邏輯圖

hash的底層主要是採用字典dict的結構,整體呈現層層封裝。

首先dict有四個部分組成,分別是dictType(類型,不咋重要),dictht(核心),rehashidx(漸進式hash的標誌),iterators(迭代器),這裏面最重要的就是dictht和rehashidx。

接下來是dictht,其有兩個數組構成,一個是真正的數據存儲位置,還有一個用於hash過程,包括的變量分別是真正的數據table和一些常見變量。

最後數據節點,和上篇說的雙向鏈表一樣,每個節點都有next指針,方便指向下一個節點,這樣目的是為了解決hash碰撞。具體的可以看下圖。

這邊看不懂沒關係,後面會針對每個模塊詳細說明。(千萬不要看到這裏就跳過啦)

雙向鏈表的定義

字典結構體dict

我們先看字典結構體dict,其包括四個部分,重點是dictht[2](真正的數據)和rehashidx(漸進式hash的標誌)。具體圖如下。

具體代碼如下:

//字典結構體 
 typedef struct dict {
    dictType *type;//類型,包括一些自定義函數,這些函數使得key和value能夠存儲 
    void *privdata;//私有數據 
    dictht ht[2];//兩張hash表 
    long rehashidx; //漸進式hash標記,如果為-1,說明沒在進行hash
    unsigned long iterators; //正在迭代的迭代器數量
} dict;

 

數組結構體dictht

dictht主要包括四個部分,1是真正的數據dictEntry類型的數組,裏面存放的是數據節點;2是數組長度size;3是進行hash運算的參數sizemask,這個不咋重要,只要記住等於size-1;4是數據節點數量used,當前有多少個數據節點。

具體代碼如下:

//hash結構體 
typedef struct dictht {
    dictEntry **table;//真正數據的數組 
    unsigned long size;//數組的大小 
    unsigned long sizemask;//用戶將hash映射到table的位置索引,他的值總是等於size-1 
    unsigned long used;//已用節點數量 
} dictht;

 

數據節點dictEntry

dictEntry為真正的數據節點,包括key,value和next節點。

//每個節點的結構體  
typedef struct dictEntry {
    void *key; //key
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;//value
    struct dictEntry *next; //下一個數據節點的地址
} dictEntry;

 

 

 

擴容過程和漸進式Hash圖解

我們先來第一個部分,dictht[2]為什麼會要2個數組存放,真正的數據只要一個數組就夠了?

這其實和Java的HashMap相似,都是數據加鏈表的結構,隨着數據量的增加,hash碰撞發生的就越頻繁,每個數組後面的鏈表就越長,整個鏈表顯得非常累贅。如果業務需要大量查詢操作,因為是鏈表,只能從頭部開始查詢,等一個數組的鏈表全部查詢完才能開始下一個數組,這樣查詢時間將無線拉長。

這無疑是要進行擴容,所以第一個數組存放真正的數據,第二個數組用於擴容用。第一個數組中的節點經過hash運算映射到第二個數組上,然後依次進行。那麼過程中還能對外提供服務嗎?答案是可以的,因為他可以隨時停止,這就到了下一個變量rehashidx。(一點都不生硬的轉場,哈哈哈)

rehashidx其實是一個標誌量,如果為-1說明當前沒有擴容,如果不為-1則表示當前擴容到哪個下標位置,方便下次進行從該下標位置繼續擴容。

這樣說是不是太抽象了,還是一臉懵逼,貼心的送上擴容過程全解,一定要點贊評論多誇誇我哦。(越來越不要臉了。。。)

步驟1

首先是未擴容前,rehashidx為-1,表示未擴容,第一個數組的dictEntry長度為4,一共有5個節點,所以used為5。

步驟2

當發生擴容了,rahashidx為第一個數組的第一個下標位置,即0。擴容之後的大小為大於used*2的2的n次方的最小值,即能包含這些節點*2的2的倍數的最小值。因為當前為5個數據節點,所以used*2=10,擴容后的數組大小為大於10的2的次方的最小值,為16。從第一個數組0下標位置開始,查找第一個元素,找到key為name,value為張三的節點,將其hash過,找到在第二個數組的下標為1的位置,將節點移過去,其實是指針的移動。這邊就簡單說了。

 

步驟3

key為name,value為張三的節點移動結束后,繼續移動第一個數組dictht[0]的下標為0的後續節點,移動步驟和上面相同。

步驟4

繼續移動第一個數組dictht[0]的下標為0的後續節點都移動完了,開始移動下標為1的節點,發現其沒有數據,所以移動下標為2的節點,同時修改rehashidx為2,移動步驟和上面相同。

整個過程的重點在於rehashidx,其為第一個數組正在移動的下標位置,如果當前內存不夠,或者操作系統繁忙,擴容的過程可以隨時停止。

停止之後如果對該對象進行操作,那是什麼樣子的呢?

  • 如果是新增,則直接新增后第二個數組,因為如果新增到第一個數組,以後還是要移過來,沒必要浪費時間
  • 如果是刪除,更新,查詢,則先查找第一個數組,如果沒找到,則再查詢第二個數組。

字典的實現(源碼分析)

創建並初始化字典

首先分配內存,接着調用初始化方法_dictInit,主要是賦值操作,重點看下rehashidx賦值為-1(這驗證了剛才的圖解,-1表示未進行hash擴容),最後返回是否創建成功。

/* 創建並初始化字典 */
dict *dictCreate(dictType *type,
        void *privDataPtr)
{
    dict *d = zmalloc(sizeof(*d));
    _dictInit(d,type,privDataPtr);
    return d;
}

/* Initialize the hash table */
int _dictInit(dict *d, dictType *type,
        void *privDataPtr)
{
    _dictReset(&d->ht[0]);
    _dictReset(&d->ht[1]);
    d->type = type;
    d->privdata = privDataPtr;
    d->rehashidx = -1;//賦值為-1,表示未進行hash
    d->iterators = 0;
    return DICT_OK;
}

 

擴容

dict裏面有一個靜態方法_dictExpandIfNeed,判斷是否需要擴容。

首先判斷通過dictIsRehashing方法,判斷是否處於hash狀態,其調用的是宏常量#define dictIsRehashing(d) ((d)->rehashidx != -1),即判斷rehashidx是否為-1,如果為-1,即不處於hash狀態,if條件為false,可以進行擴容,如果不為-1,即處於hash狀態,if條件為true,不可以進行擴容,直接返回常量DICT_OK。

接着判斷第一個數組的size是否為0,如果為0,則擴容為默認大小4,如果不為0,則執行下面的代碼。

再接着判斷是否需要擴容,if中有三個條件,具體的分析如下。

最後就是調用dictExpand擴容方法了,參數為數據節點的雙倍大小ht[0].used*2。此處驗證了上面擴容過程的數組大小16。

擴容方法比較簡單點,獲取擴容后的大小,將第二個設置新的大小。

這樣講感覺有點空,看下流程圖。

擴容流程圖

具體代碼:

static int _dictExpandIfNeeded(dict *d)
{
    //判斷是否處於擴容狀態中,通過調用宏常量#define dictIsRehashing(d) ((d)->rehashidx != -1)
    //來判斷是否可以擴容
    if (dictIsRehashing(d)) return DICT_OK;

    //判斷第一個數組size是否為0,如果為0,則調用擴容方法,大小為宏常量
    //#define DICT_HT_INITIAL_SIZE     4
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    //下面先列出if條件中所使用到的參數 
    // static int dict_can_resize = 1;數值為1表示可以擴容
    //static unsigned int dict_force_resize_ratio = 5;
    //我們來分析if條件,如果第一個數組的所有節點數量大於等於第一個數組的大小(表示節點數據已經有些多)
    //並且可用擴容(數值為1)或者所有節點數量除以數組大小大於5
    //這個條件表示擴容那個的條件,第一個就是節點必要大於等於數組長度,
    //第二點就再可以擴容和數據太多,超過5兩个中選其一
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        //調用擴容方法
        return dictExpand(d, d->ht[0].used*2);
    }
    return DICT_OK;
}

int dictExpand(dict *d, unsigned long size)
{
    dictht n;
    //獲取擴容后真正的大小,找到比size大的最小值,且是2的倍數
    unsigned long realsize = _dictNextPower(size);

    //一些判斷條件
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    if (realsize == d->ht[0].size) return DICT_ERR;

    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0;

    //第一個hash為null,說明在初始化 
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }
    //正在hash,給第二個hash的長度設置新的, 
    d->ht[1] = n;
    d->rehashidx = 0;//設置當前正在hash 
    return DICT_OK;
}

/* 找到比size大的最小值,且是2的倍數 */
static unsigned long _dictNextPower(unsigned long size)
{
    unsigned long i = DICT_HT_INITIAL_SIZE;

    if (size >= LONG_MAX) return LONG_MAX;
    while(1) {
        if (i >= size)
            return i;
        i *= 2;
    }
}

 

 

漸進式hash

漸進式hash過程已經通過上面圖解說明,以下主要看下代碼是如何實現的,以及過程是不是對的。

擴容之後就是執行dictRehash方法,參數包括待移動的哈希表d和步驟数字n。

首先判斷標誌量rehashidx是否等於-1,如果等於-1,則表示hash完成,如果不等於-1,則執行下面的代碼。

接着進行循環,遍歷第一個數組上的每個下標,每次移動下標位置,都需要更新rehashidx值,每次加1。

再接着進行第二個循環,遍歷下標的鏈表每個節點,完成數據的遷移,主要是指針的移動和一些參數的修改。

最後,返回int數值,如果為0表示整個數據全部hash完成,如果返回1則表示部分hash結束,並沒有全部完成,下次可以通過rehashidx值繼續hash。

具體代碼如下:

//重新hash這個哈希表
 // Redis的哈希表結構公有兩個table數組,t0和t1,平常只使用一個t0,當需要重hash時則重hash到另一個table數組中
 //參數列表
 // 1. d: 待移動的哈希表,結構中存有目前已經重hash到哪個桶了
  //  2. n: N步進行rehash 
// 返回值 返回0說明整個表都重hash完成了,返回1代表未完成
int dictRehash(dict *d, int n) {
    int empty_visits = n*10; 
    //如果當前rehashidx=-1,則返回0,表示hash完成 
    if (!dictIsRehashing(d)) return 0;
    //分n步,而且ht[0]還有沒有移動的節點 
    while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        //第一個循環用來更新 rehashidx 的值,因為有些桶為空,所以 rehashidx並非每次都比原來前進一個位置,而是有可能前進幾個位置,但最多不超過 10。
        //將rehashidx移動到ht[0]有節點的下標,也就是table[d->rehashidx]非空
        while(d->ht[0].table[d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
        de = d->ht[0].table[d->rehashidx];  //第二個循環用來將ht[0]表中每次找到的非空桶中的鏈表(或者就是單個節點)拷貝到ht[1]中

        /* 利用循環講數據節點移過去 */
        while(de) {
            unsigned int h;

            nextde = de->next;
            /* Get the index in the new hash table */
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de;
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }

    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1];
        _dictReset(&d->ht[1]);
        d->rehashidx = -1;
        return 0;
    }

    return 1;
}

總結

該篇主要講了Redis的Hash數據類型的底層實現字典結構Dict,先從Hash的一些API使用,引出字典結構Dict,剖析了其三個主要組成部分,字典結構體Dict,數組結構體Dictht,數據節點結構體DictEntry,進而通過多幅過程圖解釋了擴容過程和rehash過程,最後結合源碼對字典進行描述,如創建過程,擴容過程,漸進式hash過程,中間穿插流程圖講解。

如果覺得寫得還行,麻煩給個贊,您的認可才是我寫作的動力!

如果覺得有說的不對的地方,歡迎評論指出。

好了,拜拜咯。

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

【其他文章推薦】

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

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

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

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

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

【String註解驅動開發】如何按照條件向Spring容器中註冊bean?這次我懂了!!

寫在前面

當bean是單實例,並且沒有設置懶加載時,Spring容器啟動時,就會實例化bean,並將bean註冊到IOC容器中,以後每次從IOC容器中獲取bean時,直接返回IOC容器中的bean,不再創建新的bean。

如果bean是單實例,並且使用@Lazy註解設置了懶加載,則Spring容器啟動時,不會實例化bean,也不會將bean註冊到IOC容器中,只有第一次獲取bean的時候,才會實例化bean,並且將bean註冊到IOC容器中。

如果bean是多實例,則Spring容器啟動時,不會實例化bean,也不會將bean註冊到IOC容器中,以後每次從IOC容器中獲取bean時,都會創建一個新的bean返回。

Spring支持按照條件向IOC容器中註冊bean,滿足條件的bean就會被註冊到IOC容器中,不滿足條件的bean就不會被註冊到IOC容器中。接下來,我們就一起來探討Spring中如何實現按照條件向IOC容器中註冊bean。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

@Conditional註解概述

@Conditional註解可以按照一定的條件進行判斷,滿足條件向容器中註冊bean,不滿足條件就不向容器中註冊bean。

@Conditional註解是由 SpringFramework 提供的一個註解,位於 org.springframework.context.annotation 包內,定義如下。

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	Class<? extends Condition>[] value();
}

從@Conditional註解的源碼來看,@Conditional註解可以添加到類上,也可以添加到方法上。在@Conditional註解中,存在一個Condition類型或者其子類型的Class對象數組,Condition是個啥?我們點進去看一下。

package org.springframework.context.annotation;

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.type.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

可以看到,Condition是一個函數式接口,對於函數式接口不了解的同學可以參見【Java8新特性】中的《【Java8新特性】還沒搞懂函數式接口?趕快過來看看吧!》一文。也可以直接查看《Java8新特性專欄》來系統學習Java8的新特性。

所以,我們使用@Conditional註解時,需要一個類實現Spring提供的Condition接口,它會匹配@Conditional所符合的方法,然後我們可以使用我們在@Conditional註解中定義的類來檢查。

@Conditional註解的使用場景如下所示。

  • 可以作為類級別的註解直接或者間接的與@Component相關聯,包括@Configuration類;
  • 可以作為元註解,用於自動編寫構造性註解;
  • 作為方法級別的註解,作用在任何@Bean方法上。

向Spring容器註冊bean

不帶條件註冊bean

我們在PersonConfig2類中新增person01()方法和person02()方法,併為兩個方法添加@Bean註解,如下所示。

@Bean("binghe001")
public Person person01(){
    return new Person("binghe001", 18);
}

@Bean("binghe002")
public Person person02(){
    return new Person("binghe002", 20);
}

那麼,這兩個bean默認是否會被註冊到Spring容器中呢,我們新建一個測試用例來測試一下。在SpringBeanTest類中新建testAnnotationConfig6()方法,如下所示。

@Test
public void testAnnotationConfig6(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
    String[] names = context.getBeanNamesForType(Person.class);
    Arrays.stream(names).forEach(System.out::println);
}

我們運行testAnnotationConfig6()方法,輸出的結果信息如下所示。

person
binghe001
binghe002

從輸出結果可以看出,同時輸出了binghe001和binghe002。說明默認情況下,Spring容器會將單實例並且非懶加載的bean註冊到IOC容器中。

接下來,我們再輸出bean的名稱和bean實例對象信息,此時我們在testAnnotationConfig6()方法中添加相應的代碼片段,如下所示。

@Test
public void testAnnotationConfig6(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
    String[] names = context.getBeanNamesForType(Person.class);
    Arrays.stream(names).forEach(System.out::println);

    Map<String, Person> beans = context.getBeansOfType(Person.class);
    System.out.println(beans);
}

再次運行SpringBeanTest類中的testAnnotationConfig6()方法,輸出結果如下所示。

person
binghe001
binghe002
給容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}

可以看到,輸出了註冊到容器的bean。

帶條件註冊bean

現在,我們就要提出新的需求了,比如,如果當前操作系統是Windows操作系統,則向Spring容器中註冊binghe001;如果當前操作系統是Linux操作系統,則向Spring容器中註冊binghe002。此時,我們就需要使用@Conditional註解了。

這裏,有小夥伴可能會問:如何獲取操作系統的類型呢,別急,這個問題很簡單,我們繼續向下看。

使用Spring的ApplicationContext接口就能夠獲取到當前操作系統的類型,如下所示。

ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
System.out.println(osName);

我們將上述代碼整合到SpringBeanTest類中的testAnnotationConfig6()方法中,如下所示。

@Test
public void testAnnotationConfig6(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
    Environment environment = context.getEnvironment();
    String osName = environment.getProperty("os.name");
    System.out.println(osName);

    String[] names = context.getBeanNamesForType(Person.class);
    Arrays.stream(names).forEach(System.out::println);

    Map<String, Person> beans = context.getBeansOfType(Person.class);
    System.out.println(beans);
}

接下來,我們運行SpringBeanTest類中的testAnnotationConfig6()方法,輸出的結果信息如下所示。

Windows 10
person
binghe001
binghe002
給容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18), binghe002=Person(name=binghe002, age=20)}

由於我使用的操作系統是Windows 10操作系統,所以在結果信息中輸出了Windows 10。

到這裏,我們成功獲取到了操作系統的類型,接下來,就可以實現:如果當前操作系統是Windows操作系統,則向Spring容器中註冊binghe001;如果當前操作系統是Linux操作系統,則向Spring容器中註冊binghe002的需求了。此時,我們就需要藉助Spring的@Conditional註解來實現了。

要想使用@Conditional註解,我們需要實現Condition接口來為@Conditional註解設置條件,所以,這裏,我們創建了兩個實現Condition接口的類,分別為WindowsCondition和LinuxCondition,如下所示。

  • WindowsCondition
package io.mykit.spring.plugins.register.condition;


import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * @author binghe
 * @version 1.0.0
 * @description Windows條件,判斷操作系統是否是Windows
 */
public class WindowsCondition implements Condition {
    /**
     * ConditionContext:判斷條件使用的上下文環境
     * AnnotatedTypeMetadata:註釋信息
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //判斷是否是Linux系統
        //1.獲取到IOC容器使用的BeanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2.獲取類加載器
        ClassLoader classLoader = context.getClassLoader();
        //3.獲取當前的環境信息
        Environment environment = context.getEnvironment();
        //4.獲取bean定義的註冊類,我們可以通過BeanDefinitionRegistry對象查看
        //Spring容器中註冊了哪些bean,也可以通過BeanDefinitionRegistry對象向
        //Spring容器中註冊bean,移除bean,查看bean的定義,查看是否包含某個bean的定義
        BeanDefinitionRegistry registry = context.getRegistry();
        String property = environment.getProperty("os.name");
        return property.contains("Windows");
    }
}
  • LinuxCondition
package io.mykit.spring.plugins.register.condition;

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * @author binghe
 * @version 1.0.0
 * @description Linux條件,判斷操作系統是否是Linux
 */
public class LinuxCondition implements Condition {
    /**
     * ConditionContext:判斷條件使用的上下文環境
     * AnnotatedTypeMetadata:註釋信息
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //判斷是否是Linux系統
        //1.獲取到IOC容器使用的BeanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //2.獲取類加載器
        ClassLoader classLoader = context.getClassLoader();
        //3.獲取當前的環境信息
        Environment environment = context.getEnvironment();
        //4.獲取bean定義的註冊類,我們可以通過BeanDefinitionRegistry對象查看
        //Spring容器中註冊了哪些bean,也可以通過BeanDefinitionRegistry對象向
        //Spring容器中註冊bean,移除bean,查看bean的定義,查看是否包含某個bean的定義
        BeanDefinitionRegistry registry = context.getRegistry();
        String property = environment.getProperty("os.name");
        return property.contains("linux");
    }
}

接下來,我們就需要在PersonConfig2類中使用@Conditional註解添加條件了。添加註解后的方法如下所示。

@Conditional({WindowsCondition.class})
@Bean("binghe001")
public Person person01(){
    return new Person("binghe001", 18);
}

@Conditional({LinuxCondition.class})
@Bean("binghe002")
public Person person02(){
    return new Person("binghe002", 20);
}

此時,我們再次運行SpringBeanTest類中的testAnnotationConfig6()方法,輸出的結果信息如下所示。

Windows 10
person
binghe001
給容器中添加Person....
{person=Person(name=binghe002, age=18), binghe001=Person(name=binghe001, age=18)}

可以看到,輸出結果中不再含有名稱為binghe002的bean了,說明程序中檢測到當前操作系統為Windows10,沒有向Spring容器中註冊名稱為binghe002的bean。

@Conditional註解也可以標註在類上,標註在類上含義為:滿足當前條件,這個類中配置的所有bean註冊才能生效,大家可以自行驗證@Conditional註解標註在類上的情況

@Conditional的擴展註解

@ConditionalOnBean:僅僅在當前上下文中存在某個對象時,才會實例化一個Bean。
@ConditionalOnClass:某個class位於類路徑上,才會實例化一個Bean。
@ConditionalOnExpression:當表達式為true的時候,才會實例化一個Bean。
@ConditionalOnMissingBean:僅僅在當前上下文中不存在某個對象時,才會實例化一個Bean。
@ConditionalOnMissingClass:某個class類路徑上不存在的時候,才會實例化一個Bean。
@ConditionalOnNotWebApplication:不是web應用,才會實例化一個Bean。
@ConditionalOnBean:當容器中有指定Bean的條件下進行實例化。
@ConditionalOnMissingBean:當容器里沒有指定Bean的條件下進行實例化。
@ConditionalOnClass:當classpath類路徑下有指定類的條件下進行實例化。
@ConditionalOnMissingClass:當類路徑下沒有指定類的條件下進行實例化。
@ConditionalOnWebApplication:當項目是一個Web項目時進行實例化。
@ConditionalOnNotWebApplication:當項目不是一個Web項目時進行實例化。
@ConditionalOnProperty:當指定的屬性有指定的值時進行實例化。
@ConditionalOnExpression:基於SpEL表達式的條件判斷。
@ConditionalOnJava:當JVM版本為指定的版本範圍時觸發實例化。
@ConditionalOnResource:當類路徑下有指定的資源時觸發實例化。
@ConditionalOnJndi:在JNDI存在的條件下觸發實例化。
@ConditionalOnSingleCandidate:當指定的Bean在容器中只有一個,或者有多個但是指定了首選的Bean時觸發實例化。

@Conditional 與@Profile 的對比

Spring3.0 也有一些和@Conditional 相似的註解,它們是Spring SPEL 表達式和Spring Profiles 註解 Spring4.0的@Conditional 註解要比@Profile 註解更加高級。@Profile 註解用來加載應用程序的環境。@Profile註解僅限於根據預定義屬性編寫條件檢查。 @Conditional註釋則沒有此限制。

Spring中的@Profile 和 @Conditional 註解用來檢查”If…then…else”的語義。然而,Spring4 @Conditional是@Profile 註解的更通用法。

  • Spring 3中的 @Profile僅用於編寫基於Environment變量的條件檢查。 配置文件可用於基於環境加載應用程序配置。
  • Spring 4 @Conditional註解允許開發人員為條件檢查定義用戶定義的策略。 @Conditional可用於條件bean註冊。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

參考:
https://www.cnblogs.com/cxuanBlog/p/10960575.html

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

離夢想又近了一丟丟 保時泰新款SUV也許下探10萬以內

由於SR9的自重過大,所以它並沒有使用T600的1。5T發動機,這款1。8T發動機預計將會和Z700的那款1。8T發動機型號一致。Z700的1。8T發動機最大馬力為177馬力,最大扭矩為245牛·米,變速箱為5擋手動和6擋雙離合。SR9很可能也會搭載此套動力系統。

保時捷卡宴一直是小編的夢想之車,但是百萬的售價讓太多人望而卻步。不過保時捷嘗到了SUV的甜頭,為了繼續擴大市場,推出了售價更低的Macan,Macan一推出就獲得了巨大的成功,訂單也是絡繹不絕。

雖然Macan更便宜了,但是,作為一個普通的搬磚工(不是香港的搬磚工),Macan還是距離我太遙遠,也許我這類搬磚工一輩子也開不上Macan。

但是只要有需求,必定會有企業來開拓市場,眾泰不負眾望研發出SR9,直接將“Macan”的價格做到了10.88萬。一時間坊間的小夥伴們奔走相告,喜極而泣,終於可以買到心愛的“Macan”了。不過即使是這個價格也有不少消費者覺得貴了。

作為豪車“價格殺手”的眾泰豈能善罷甘休?目前眾泰又打算推出了SR9的1.8T車型,也就是說如果你覺得10.88萬元的價格有點貴,那麼接下來這款車就可以滿足你的心愿了。

現款的SR9全係為2.0T 190馬力發動機,匹配5擋手動和6擋雙離合變速箱,售價區間為10.88-16.18萬元。眾泰為了豐富SR9的動力系統,提供更多樣化的選擇,同時也為了更大程度的降低售價,增強SR9的競爭力,果斷推出1.8T車型。使得SR9的價格進一步下探。

雖然眾泰官方沒有明確的公布1.8T發動機的具體參數,但是可以肯定的是價格更便宜的1.8T的SR9將會在2017年第四季度上市。由於SR9的自重過大,所以它並沒有使用T600的1.5T發動機,這款1.8T發動機預計將會和Z700的那款1.8T發動機型號一致。

Z700的1.8T發動機最大馬力為177馬力,最大扭矩為245牛·米,變速箱為5擋手動和6擋雙離合。SR9很可能也會搭載此套動力系統。

如果真的是這套動力系統的話那也就不用擔心動力輸出不夠了,這要動力系統還是可以可以滿足SR9的日常行車了。

SR9自從誕生之後就備受爭議,但是SR9憑藉著炫酷的外觀,極其豐富的配置和非常實惠的終端售價,吸引了一大批粉絲。況且,1.8T車型問世之後,SR9的起售價很有可能做到9萬左右,這樣會使得SR9的競爭力更上一層樓,銷量肯定會進一步增長。但是SR9也不是沒有對手,比如它們,熱度也很大。

陸風X7

指導價:12.98-14.78萬

X7的車身尺寸為4420*1910*1630mm,軸距為2670mm,定位緊湊型SUV。雖然SR9有“某保”護體,但是X7也有“某虎”助陣,同時X7全係為2.0T發動機,變速箱為8AT,這點也是比較厲害的。而且X7在坊間的名氣也是比較大的。

眾泰T600

指導價:7.98-14.98萬

誰說親兄弟就不能“互相傷害”,大家都是在激烈的市場裏面競爭,誰能取得熱銷就看誰的“活更好了”。T600在10月份交出了11508輛的銷量,表現非常好。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

不忽悠直接試!這輛車為什麼讓很多人慾罷不能?

對於在城市裡比較緊張的停車場停車,這就是避免剮蹭的一大保障。不過通過後視攝像頭集成倒車影像在這個價位的車裡不算特別稀奇,新凌派的安全配置才是最大的亮點。全系標配VSA車身穩定控制系統、HSA斜坡起步輔助系統、ESS緊急剎車警示系統及領先搭載LWC盲點显示系統四大安全科技。

又到年底,一年中大家的荷包最豐厚的時節,於是買車成為了不少人的心頭大事。後台關於車型的詢問量也與日俱增,搞得最近黃金右手有點撐不住。正好某天看到有個粉絲問能不能說說凌派,更妙的是恰好咱們手上有輛凌派試駕車,與其用黃金右手碼字說,不如直接上黃金右腳來得直接。於是一不做二不休乾脆勾搭上這位粉絲,帶着他共同試駕新凌派,讓他親自感受一番這車究竟如何!

這一次我們試駕的車型是次頂配,香檳雅金的凌派,這是新凌派的主打車色,路上見到的也多,可見大家對“土豪金”的喜愛。

接着另一位主角登場,正是邀請來試駕的粉絲——阿聰。

外觀——“時尚中不失個性”

阿聰首先感受了一番新凌派的外觀,他直言自己算是個“顏控”。而且相比中規中矩的卡羅拉、雷凌,他更喜歡凌派“時尚中不失個性”的外觀。

覺得,阿聰這個描述很到位。如果把同級車型比成一個班級,凌派就是班級里打扮最前衛,每次出現在教室門口全班同學都會抬頭看的那位。前臉十分特別,“大嘴”前格柵與“大眼睛”一般的大燈組成了獨特的“笑模樣”。側面刀鋒式的雙腰線和切削式的鋁合金輪轂造型充分展現個性,尾部採用運動型保險杠體現動感。可以說新凌派絕對是一輛辨識度非常高的車,恰恰滿足了像阿聰這樣的年輕人不想“泯然眾人已”的心理。

內飾——“簡約而不簡單”

進入車內,空調出風口、中控大屏、各個功能按鍵有序分佈,整體設計是一貫的本田風格,簡潔、實用、時尚。內飾用料也比較精細,手觸摸上去軟硬適中,完全沒有塑料感,相比很多同級車型,新凌派在這個價位做出了質感,這是非常讓人驚喜的地方。

在給阿聰對內飾做了一番講解后,阿聰瞭然的點頭,一句話總結了要表達的意思:“簡約而不簡單。”他想要的也正是這樣內飾布局簡單明了,一坐進去就非常容易上手操作的車。

空間——“夠大夠舒服”

體驗了內飾后,問阿聰對車的空間有些什麼要求。阿聰說主要是平時上下班代步,去運動,周末和朋友短途自駕,所以乘坐空間要夠大夠舒服,後備箱最好能多裝點。聽完就覺得難怪他會偏愛新凌派了。

擁有4664mm的超長車身和2650mm的軸距優勢,加上本田大師水準的空間利用技術,及本田獨有的MM理念的運用,新凌派無論前後排空間都非常寬敞。為了讓阿聰更加深刻的體驗到新凌派的空間,上陣教了他一套“媒體老濕”用的空間測試法。阿聰身高175,坐進新凌派後頭部空間一拳半,腿部空間遠遠超過兩拳,完全能夠舒服的乘坐。

後備箱空間就更能滿足阿聰的需求了。無論深度還是寬度都很夠,放了一摞又一摞物料后,阿聰整個人還能愜意的躺進後備箱,這個展現後備箱寬敞程度的招式,也是服氣。

配置——“亮點滿滿”

當然阿聰更關心的還有凌派的配置。上車之前他就親自體驗了新凌派的無鑰匙進入系統,只要攜帶鑰匙靠近,車門就會自動解鎖。阿聰覺得這個功能非常酷。

而從車內分佈的各個按鍵也能知道配置的豐富程度。尤其一鍵啟停、小綠恭弘=叶 恭弘也深得阿聰這個90后的歡心,既體現高科技,又保證經濟性。

細心的阿聰還發現,新凌派配備的倒車影像具有三模式視角,分別是廣角、常規和高角度。對於在城市裡比較緊張的停車場停車,這就是避免剮蹭的一大保障。

不過通過後視攝像頭集成倒車影像在這個價位的車裡不算特別稀奇,新凌派的安全配置才是最大的亮點。全系標配VSA車身穩定控制系統、HSA斜坡起步輔助系統、ESS緊急剎車警示系統及領先搭載LWC盲點显示系統四大安全科技。而LWC盲點显示系統更是本田的“黑科技”代表之一,在凌派的同級車型中更是絕無僅有。

LWC盲點显示系統使用非常簡單,只要按一下方向盤後方擋把上的啟動按鈕,就能啟動位於右後視鏡上的攝像頭,右後方的影像立即显示在中控大屏上。啟動這個功能后,右後方的視野能從20%擴大到80%,消除肉眼觀察時的盲區,並且帶有輔助線以供駕駛者判斷安全距離。

對這個逆天的功能阿聰表示,開車時右後方經常會有視線盲區,尤其在複雜路況行車時,切線或者轉彎都不免讓很多人的心都提一提,現在只需輕輕看一眼就能掌握右後方路況,開起車來會從容許多。甚至平時還可以來個創意自拍,新凌派的配置不僅豐富,還亮點滿滿。

只想說:果然是年輕人,會玩!

動力——“舒適之餘也談激情”

動力上凌派搭載的是1.8L自然吸氣發動機,擁有本田看家的i-VTEC系統,匹配CVT無級變速器,最大功率100kW,最大扭矩169N·m,參數表現是非常不錯的。而阿聰體驗過後實際是什麼感受呢?

剛把車發動起來,阿聰就感受到凌派強勁的動力了。不同於很多車經常會有起步比較“肉”的感受,本田的可變氣門正時系統讓發動機即使在低扭矩時也能保證足夠的動力輸出。

一天的相處下來,發現阿聰是一個個性又不失內斂的男生,開車風格是“暖男派”,不爭不搶。在市區駕駛時開啟ECON模式是非常適合他的,兼顧舒適性和經濟性。而當他偶爾想要來點激情時,讓他切到S擋,深踩油門感受了一番新凌派的加速,油門與剎車響應都比較靈敏,阿聰開起來完全游刃有餘。

最後還帶着阿聰來了一次劈彎。在比較高速的情況下轉彎,凌派的側傾只會讓你恰到好處的感覺到車身在過彎,並不會有傾斜幅度過大的感覺,車尾的循跡性也不錯。可見新凌派底盤是主打舒適的調校。用阿聰的話來說,新凌派給他的感覺非常“穩”。

最終一天試駕下來的油耗也讓阿聰更“穩”了,他開出了百公里6.8L的超低油耗,看來黃金右腳的稱號要拱手讓人了。

分別前問了阿聰最後一個,也是試駕前阿聰本人帶來的問題:

“今天能不能讓你決定射出這臨門一腳買下凌派呢?”

阿聰回答說:

“本來就覺得新凌派很對胃口,在和一起試駕后,還發現了它獨特的LWC、強勁的動力、超大的空間等超越同級的亮點,可以說由內而外都非常適合我,一天下來我感覺和新凌派幾乎不用磨合,不禁產生一種“這就是我的車”的感覺。”

還是那句話,最適合你的車就是最好的車。像阿聰這樣的年輕人,一輛外觀時尚,配置豐富,大空間,動力足的十萬級別轎車就是最大的助力。而擁有1.5油耗,1.6價格,1.8動力,2.0空間的新凌派,阿聰試完之後已經找不出不買它的理由。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

用路虎給公司女同事一個聖誕驚喜

主角到場,似乎激動得說不出話了,這是要求婚的節奏。顏值擔當:小喬Jacky深情演唱。攝影師:費玉清-如果我是DJ。後期剪輯:子詠運營:苦瓜。帥X主持人:張濤靈魂字幕君:KAM知道你們嫉妒我的顏。我就靜靜的看你們裝逼。祝大家聖誕快樂。

眼看幾年即將結束,為了給公司勞苦功高的女同事一個浪漫的聖誕之夜,TV的小夥伴們精心策劃了一個聖誕party。

有夢想的男人:大宇宙哥

7200干他:Jacky

滿滿一車東西,滿滿的都是驚喜,出發!

我曾經跨過高山大海,現在就經常買買菜。

包裝師:雞爺

悉心裝飾現場。

恩,小喬會不會感動就靠你了。

就等夜晚降臨。

主角到場,似乎激動得說不出話了,這是要求婚的節奏?

顏值擔當:小喬

Jacky深情演唱。

攝影師:費玉清-如果我是DJ。

後期剪輯:子詠

運營:苦瓜。

帥X主持人:張濤

靈魂字幕君:KAM

知道你們嫉妒我的顏。

我就靜靜的看你們裝逼。

祝大家聖誕快樂!

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

【其他文章推薦】

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

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

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

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

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

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