分類
發燒車訊

HUAWEI MateBook 14 開箱!輕薄筆電 性能首選

這次為大家介紹輕薄筆電性能首選的HUAWEI MateBook 14 開箱,在去年我們有介紹過華為MateBook 系列各種不同的款式,但還有獨缺一台我們一直都沒有機會開箱,這次總算是有機會跟大家介紹這款超高CP值的HUAWEI MateBook 14,採用AMD Ryzen™ 4000H系列行動處理器的MateBook 14,因此在擁有高效能的同時,價格也相對便宜很多,跟我們一起來看看這台CP值與效能兼具的HUAWEI MateBook 14。

HUAWEI MateBook 14 開箱!!高CP值兼具高效能的超輕薄筆電

外包裝採用相同設計的全白色基底,中間有著玫瑰金HUAWEI MateBook系列的燙金字樣。內容物包含了65W PD充電器與USB Type-C充電線。


65W PD快充充電器除了可為華為筆電充電以外,更可為各種平板、手機等其他使用Type-C 充電的設備充電,體積小巧通用的萬用充電器讓外出攜帶更為便利。

上蓋的金屬材質採用了磨砂處理,配色方面則是維持一貫的深空灰配色。

掀開螢幕以後就可以看到HUAWEI MateBook 14所有用的2K全面屏,高達90%的螢幕佔比搭配3:2的螢幕比例,高生產力螢幕讓整體使用工作區又大、體驗又舒服。

往下看到鍵盤區,首先是開機鍵整合了指紋辨識,非常實用。

透過Windows Hello的設定,即可使用指紋登入。

HUAWEI MateBook 14的鍵盤採用了全尺寸背光鍵盤,三段背光調整、優化的鍵程與鍵帽,使用起來非常舒服,不過值得一提的是在右邊因為整體尺寸的關係,往內縮的情況下使得右方按鍵有些變化,在使用上需要習慣一下。(市售版本為有註音符號的鍵盤)

高螢幕佔比的祕密就在於把攝像頭藏起來了,在鍵盤的F6與F7中間,有一個隱藏的攝像頭(100萬畫素、720p HD),只要按一下就可以彈起,不使用時壓下去即可收納,使用起來安全又方便。

而觸控區域的部分則是更大了,有著大面積的手勢觸控板,讓使用起來更順手,同時也是一碰傳的感應區域。

I/O配置部分,左側配置了一個USB Type-C (支援充電、數據傳輸與外接顯示)、3.5mm耳機孔以及一個HDMI孔。

右側則是配置了兩個USB 3.2 Gen 1,相容性極高。

翻到背面,首先可以看到配合著雙風扇的散熱孔,確保風流的走向。

左右開孔,則是藏著兩顆2W的揚聲器。

接著卸下背後螺絲後,即可打開背蓋,首先可以看到的就是剛剛提到的雙風扇設計,搭配翼形設計的兩根導熱管,在最短的散熱路徑下,可以有效的增加散熱效率。

在散熱模組的下方,則可以看到主硬碟採用了M.2 PCIe SSD 512GB。

WiFi則是使用了2×2 MIMO雙天線WLAN。

MateBook 14的電池規格,採用了56Wh的配置,搭配華為快充技術,充電15分鐘使用2.5hr,即使在外面突然沒電也不用擔心。

MateBook 14整機的重量為1.46公斤,攜帶上非常便利。

 

HUAWEI MateBook 14 效能測試

HUAWEI MateBook 14搭載了採用了台積電 7奈米工藝打造的 AMD Ryzen™ 5 4600H (6C/12T) 處理器。

透過裝置管理員,可以看到AMD Ryzen™ 5 4600H 六核心12執行緒配置。

記憶體部分,則是使用了雙通道設計DDR4 16GB。

首先以CPU-Z進行核心效能測試,單核的分數為479,而在全核心的分數則是可以跑到3940.6。

接著透過CineBench R15同樣進行CPU測試,在CPU單核的分數為164 cb,而全核心的表現則為1292 cb。

針對多核心,則透過CineBench R20進行CPU測試,在CPU單核的分數為444 cb,而全核心的表現則為3010 cb。

接著透過x264 FHD BENCHMARK進行轉檔測試,Ryzen 5 4600H的表現為42 fps。

接著透過x265 FHD BENCHMARK進行轉檔測試,Ryzen 5 4600H的表現為25.9 fps。

由以上的CPU效能測試可以看出,單核心的效能雖然沒有拉到很高,但Ryzen 5 4600H有著多處理緒的優勢,在整體運算效能上還是有著非常高的運算能力。

接著我們就來看看Ryzen 5 4600H自帶的內顯效能吧,首先在3DMARK的跑分中,使用DirectX 12為核心架構的Time Spy測試,MateBook 14的跑分為960。

而使用DirectX 11為核心架構的Fire Strike測試,MateBook 14的跑分為2494。

接著是跨平台的Wild Life測試,MateBook 14的分數為5096。

最後則是採用Direct X 12的Night Raid測試,MateBook 14的分數為11105。

在儲存的部分,首先使用DiskInfo查看硬碟資訊,本次測試機台預設是搭載三星的512 GB PCIe® NVMe™ 3.0 x4 M.2 SSD。

透過CrystalDiskMark進行測試,其讀寫速度為3571.5 MB/s與2988.5 MB/s。

接著使用TxBENCH同樣進行讀寫測試,最高讀寫速度則是3563.7 MB/s與2997.2 MB/s。

整機部分則使用PCMARK 10進行測試,在針對辦公室PC的基準測試當中跑分為4975。

而針對完整的辦公室PC基準測試PCMark 10 Express中,跑分為4045。

接著我們透過PCMARK 8進行續航力測試,HUAWEI MateBook 14可以連續使用400分鐘左右,而在實際使用上則是可以連續看影片超過10小時,搭配快充使用非常方便。

而使用Google 分析模擬持續聯網下的工作續航,由100%到跳出警告的10%也有約9.5小時的表現,相當的長效:

 

HUAWEI MateBook 14 軟體介紹

首先,HUAWEI MateBook 14內建了護眼模式,在桌面上點擊右鍵即可進入設定。

在開啟後,畫面會顯得較為偏黃,但也可以針對個人喜好,去調整護眼模式的強弱、護眼模式的色彩。

HUAWEI MateBook 14也內建了PC Manager,可以透過此程式一鍵更新驅動以及發現系統軟、硬件問題,直接更新處裡,非常便利。

點擊START按鈕即會開始自動為電腦做健康檢查。

如果對於系統或是軟體有個別問題的話,也可以在下面的分頁內進行檢測。

在PC Manager的最下方,則是與華為自家手機的連結:一碰傳。

按下連結以後,即會開始搜尋手機並進行配對。

連結以後即可啟動一碰傳,除了可以同步手機畫面到電腦上進行工作傳輸,而且無論傳各式檔案都非常快速,而且還擁有圖片辨識文字功能,如果有想要辨識的文字,只要用手機拍張照,透過一碰傳傳到電腦就可以快速轉換成文字了,對於文書處理來說非常方便。

透過PC Manager也可以直接從手機撈檔案。

 

總結

HUAWEI MateBook 14是一台高CP值與高效能兼具的超輕薄筆電,雖說沒有搭配獨立顯卡,但基於AMD自身APU的強大效能,無論是在基本文書、影音創作上,都可以有著非常不錯的運算能力,並且在螢幕的配置上,3:2的高生產力螢幕比例搭配90%的螢幕佔比,在使用上非常的舒服,並且搭配了超高續航力與快充技術,在外工作不擔心不用再為了快沒電這件事情所困擾,如果又是使用華為的手機,搭配一碰傳功能可以說是非常的方便。

而且除了筆電本身的強悍外,HUAWEI MateBook 14現在還有搭配了HUAWEI MateBook系列的保固啟動服務,可以享有7大筆電安心服務,只要購買HUAWEI MateBook系列並且上網登錄( https://www.huaweifans .com.tw/events/register/matebook-d/ ),再審核過後即可啟動保固服務,內容包含了無亮點保固、1+1年延長保固、60min快修、全台維修(不含離島地區)、到府收送(不含離島地區)、甲地送修乙地取件(不含離島地區)以及專人專線不中斷7大服務,相信對於筆電的後續服務上可以更放心的使用,而且上市期間,購買HUAWEI MateBook 14,加贈Microsoft 365個人版(贈品建議售價NTD 2,190)及羅技M235無線滑鼠(贈品建議售價NTD 399),2/28之前到華為品牌店與燦坤門市購買(請點我)再送市價6,988 元的ViewSonic 27 吋2K 薄型顯示器一部,強力推薦給大家。

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

小米10T Lite 5G 在台推出:高通 Snapdragon 750G 5G 處理器、120Hz 更新率螢幕、64MP 四鏡頭主相機,萬元有找輕鬆入手

目前在台灣手機市場的5G 手機選擇從入門、中階到旗艦級都有,今(26)日稍早小米台灣也舉行「小米5G手機超值選」直播,帶大家看看在不同預算、使用需求考量下,消費者該選擇哪款5G 手機。另外,小米台灣也在今日直播迎來一款全新登台的5G 手機「小米10T Lite」,搭載高通Snapdragon 750 5G 行動平台、120Hz 更新率螢幕、6400 萬像素四鏡頭主相機,價格也特別訂在萬元有找相當超值。

小米10T Lite 5G 在台推出:高通Snapdragon 750G 5G 處理器、120Hz 更新率螢幕、64MP 四鏡頭主相機,萬元有找輕鬆入手

日前我們才報導過「小米10T Lite」這款中階5G手機將在台灣推出新機的消息,今(26)日也稍早小米台灣也已直播方式揭曉這款新機的上市資訊。
小米10T Lite搭載6.67吋20:9顯示比例、 FHD+(2400×1080)解析度的居中挖孔全螢幕,機身前後採用康寧第五代大猩猩玻璃保護:

顯示方面,小米10T Lite除了支持1500:1對比度、 NTSC 84%色域、 120Hz螢幕更新率、 240Hz觸控採樣率,螢幕也通過TÜVRheinland德國萊茵低藍光認證。
另外,小米10T Lite前後配備智慧環境光感應器,能提供最舒適的視覺體驗。螢幕採用陽光螢幕3.0 ,在陽光直射下自動增強對比度以確保畫面清晰可見:

相機方面,小米10T Lite 搭載6400 萬像素四鏡頭主相機,採用6400 萬像素F/1.89 光圈主鏡頭(SONY IMX682 感光元件)、800 萬像素120° 超廣角鏡頭、200 萬像素4cm 微距鏡頭以及200 萬像素景深鏡頭的搭配。

前鏡頭則配備1600 萬像素前置自拍相機:

小米10T Lite 相機應用功能也相當豐富,像是也支持前後鏡頭同時錄影:

內建訂十連拍功能,最高可一分鐘600 張照片並轉換成影片:

另外也內建長曝光模式,讓拍攝初學者也能拍出專業感的照片:

延時攝影也支持自拍影片功能:

性能方面,小米10T Lite 搭載Qualcomm Snapdragon 750G 5G 處理器, 6GB RAM 和128GB ROM :

小米10T Lite 內建4820mAh 大電量電池,支持33W 快速充電能在59 分鐘充電至100% :

配色方面,小米10 Lite在台共推出玫瑰金、珍珠灰以及海洋藍三種配色選擇。
玫瑰金:

珍珠灰:

海洋藍:

銷售資訊

最後就是大家最關心的售價囉!小米10T Lite 在台推出6GB+128GB 單一規格,售價為NT$9,999 ,將於3 月2 日起於小米專賣店、遠傳電信、PChome24h購物小米旗艦館、法雅客網路商店、friDay購物與燦坤3C陸續開賣。

即日起至3 月7 日止,購買任一款小米5G手機並出示指定截圖,即贈Redmi行動電源

為慶祝小米10T Lite 5G 的上市,進一步完整小米在台灣市場5G手機的佈局,凡於活動期間於全台小米專賣及燦坤3C實體門市購買任一小米5G手機(包含小米10T Pro、小米10T 5G、小米10T Lite 5G及Redmi Note 9T 5G),出示小米台灣官方Facebook粉絲頁上2月26日直播截圖,即可獲贈Redmi行動電源10000 標準版白色乙個(價值NT$345)。

小米5G手機超值選

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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

分類
發燒車訊

關於 鎖的四種狀態與鎖升級過程 圖文詳解

一、前言

鎖的狀態總共有四種,級別由低到高依次為:無鎖、偏向鎖、輕量級鎖、重量級鎖,這四種鎖狀態分別代表什麼,為什麼會有鎖升級?其實在 JDK 1.6之前,synchronized 還是一個重量級鎖,是一個效率比較低下的鎖,但是在JDK 1.6后,Jvm為了提高鎖的獲取與釋放效率對(synchronized )進行了優化,引入了 偏向鎖 和 輕量級鎖 ,從此以後鎖的狀態就有了四種(無鎖、偏向鎖、輕量級鎖、重量級鎖),並且四種狀態會隨着競爭的情況逐漸升級,而且是不可逆的過程,即不可降級,也就是說只能進行鎖升級(從低級別到高級別),不能鎖降級(高級別到低級別),意味着偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

二、鎖的四種狀態

synchronized 最初的實現方式是 “阻塞或喚醒一個Java線程需要操作系統切換CPU狀態來完成,這種狀態切換需要耗費處理器時間,如果同步代碼塊中內容過於簡單,這種切換的時間可能比用戶代碼執行的時間還長”,這種方式就是 synchronized實現同步最初的方式,這也是當初開發者詬病的地方,這也是在JDK6以前 synchronized效率低下的原因,JDK6中為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”。

所以目前鎖狀態一種有四種,從級別由低到高依次是:無鎖、偏向鎖,輕量級鎖,重量級鎖,鎖狀態只能升級,不能降級

如圖所示:

三、鎖狀態的思路以及特點

鎖狀態 存儲內容 標誌位
無鎖 對象的hashCode、對象分代年齡、是否是偏向鎖(0) 01
偏向鎖 偏向線程ID、偏向時間戳、對象分代年齡、是否是偏向鎖(1) 01
輕量級鎖 指向棧中鎖記錄的指針 00
重量級鎖 指向互斥量的指針 11

四、鎖對比

優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗 適用於只有一個線程訪問同步塊場景
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度 如果始終得不到索競爭的線程,使用自旋會消耗CPU 追求響應速度,同步塊執行速度非常快
重量級鎖 線程競爭不使用自旋,不會消耗CPU 線程阻塞,響應時間緩慢 追求吞吐量,同步塊執行速度較慢

五、Synchronized鎖

synchronized 用的鎖是存在Java對象頭裡的,那麼什麼是對象頭呢?

5.1 Java 對象頭

我們以 Hotspot 虛擬機為例,Hopspot 對象頭主要包括兩部分數據:Mark Word(標記字段) 和 Klass Pointer(類型指針)

Mark Word:默認存儲對象的HashCode,分代年齡和鎖標誌位信息。這些信息都是與對象自身定義無關的數據,所以Mark Word被設計成一個非固定的數據結構以便在極小的空間內存存儲盡量多的數據。它會根據對象的狀態復用自己的存儲空間,也就是說在運行期間Mark Word里存儲的數據會隨着鎖標誌位的變化而變化。

Klass Point:對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

在上面中我們知道了,synchronized 用的鎖是存在Java對象頭裡的,那麼具體是存在對象頭哪裡呢?答案是:存在鎖對象的對象頭的Mark Word中,那麼MarkWord在對象頭中到底長什麼樣,它到底存儲了什麼呢?

在64位的虛擬機中:

在32位的虛擬機中:

下面我們以 32位虛擬機為例,來看一下其 Mark Word 的字節具體是如何分配的

無鎖:對象頭開闢 25bit 的空間用來存儲對象的 hashcode ,4bit 用於存放對象分代年齡,1bit 用來存放是否偏向鎖的標識位,2bit 用來存放鎖標識位為01

偏向鎖: 在偏向鎖中劃分更細,還是開闢 25bit 的空間,其中23bit 用來存放線程ID,2bit 用來存放 Epoch,4bit 存放對象分代年齡,1bit 存放是否偏向鎖標識, 0表示無鎖,1表示偏向鎖,鎖的標識位還是01

輕量級鎖:在輕量級鎖中直接開闢 30bit 的空間存放指向棧中鎖記錄的指針,2bit 存放鎖的標誌位,其標誌位為00

重量級鎖: 在重量級鎖中和輕量級鎖一樣,30bit 的空間用來存放指向重量級鎖的指針,2bit 存放鎖的標識位,為11

GC標記: 開闢30bit 的內存空間卻沒有佔用,2bit 空間存放鎖標誌位為11。

其中無鎖和偏向鎖的鎖標誌位都是01,只是在前面的1bit區分了這是無鎖狀態還是偏向鎖狀態

關於內存的分配,我們可以在git中openJDK中 markOop.hpp 可以看出:

public:
  // Constants
  enum { age_bits                 = 4,
         lock_bits                = 2,
         biased_lock_bits         = 1,
         max_hash_bits            = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
         hash_bits                = max_hash_bits > 31 ? 31 : max_hash_bits,
         cms_bits                 = LP64_ONLY(1) NOT_LP64(0),
         epoch_bits               = 2
  };
  • age_bits: 就是我們說的分代回收的標識,佔用4字節
  • lock_bits: 是鎖的標誌位,佔用2個字節
  • biased_lock_bits: 是是否偏向鎖的標識,佔用1個字節
  • max_hash_bits: 是針對無鎖計算的hashcode 佔用字節數量,如果是32位虛擬機,就是 32 – 4 – 2 -1 = 25 byte,如果是64 位虛擬機,64 – 4 – 2 – 1 = 57 byte,但是會有 25 字節未使用,所以64位的 hashcode 佔用 31 byte
  • hash_bits: 是針對 64 位虛擬機來說,如果最大字節數大於 31,則取31,否則取真實的字節數
  • cms_bits: 不是64位虛擬機就佔用 0 byte,是64位就佔用 1byte
  • epoch_bits: 就是 epoch 所佔用的字節大小,2字節。

5.2 Monitor

Monitor 可以理解為一個同步工具或一種同步機制,通常被描述為一個對象。每一個 Java 對象就有一把看不見的鎖,稱為內部鎖或者 Monitor 鎖。

Monitor 是線程私有的數據結構,每一個線程都有一個可用 monitor record 列表,同時還有一個全局的可用列表。每一個被鎖住的對象都會和一個 monitor 關聯,同時 monitor 中有一個 Owner 字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程佔用。

Synchronized是通過對象內部的一個叫做監視器鎖(monitor)來實現的,監視器鎖本質又是依賴於底層的操作系統的 Mutex Lock(互斥鎖)來實現的。而操作系統實現線程之間的切換需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什麼 Synchronized 效率低的原因。因此,這種依賴於操作系統 Mutex Lock 所實現的鎖我們稱之為重量級鎖。

隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。JDK 1.6中默認是開啟偏向鎖和輕量級鎖的,我們也可以通過-XX:-UseBiasedLocking=false來禁用偏向鎖。

六、鎖的分類

6.2 無鎖

無鎖是指沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功。

無鎖的特點是修改操作會在循環內進行,線程會不斷的嘗試修改共享資源。如果沒有衝突就修改成功並退出,否則就會繼續循環嘗試。如果有多個線程修改同一個值,必定會有一個線程能修改成功,而其他修改失敗的線程會不斷重試直到修改成功。

6.3 偏向鎖

初次執行到synchronized代碼塊的時候,鎖對象變成偏向鎖(通過CAS修改對象頭裡的鎖標誌位),字面意思是“偏向於第一個獲得它的線程”的鎖。執行完同步代碼塊后,線程並不會主動釋放偏向鎖。當第二次到達同步代碼塊時,線程會判斷此時持有鎖的線程是否就是自己(持有鎖的線程ID也在對象頭裡),如果是則正常往下執行。由於之前沒有釋放鎖,這裏也就不需要重新加鎖。如果自始至終使用鎖的線程只有一個,很明顯偏向鎖幾乎沒有額外開銷,性能極高。

偏向鎖是指當一段同步代碼一直被同一個線程所訪問時,即不存在多個線程的競爭時,那麼該線程在後續訪問時便會自動獲得鎖,從而降低獲取鎖帶來的消耗,即提高性能。

當一個線程訪問同步代碼塊並獲取鎖時,會在 Mark Word 里存儲鎖偏向的線程 ID。在線程進入和退出同步塊時不再通過 CAS 操作來加鎖和解鎖,而是檢測 Mark Word 里是否存儲着指向當前線程的偏向鎖。輕量級鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時候依賴一次 CAS 原子指令即可。

偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程是不會主動釋放偏向鎖的。

關於偏向鎖的撤銷,需要等待全局安全點,即在某個時間點上沒有字節碼正在執行時,它會先暫停擁有偏向鎖的線程,然後判斷鎖對象是否處於被鎖定狀態。如果線程不處於活動狀態,則將對象頭設置成無鎖狀態,並撤銷偏向鎖,恢復到無鎖(標誌位為01)或輕量級鎖(標誌位為00)的狀態。

6.4 輕量級鎖(自旋鎖)

輕量級鎖是指當鎖是偏向鎖的時候,卻被另外的線程所訪問,此時偏向鎖就會升級為輕量級鎖,其他線程會通過自旋(關於自旋的介紹見文末)的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。

輕量級鎖的獲取主要由兩種情況:
① 當關閉偏向鎖功能時;
② 由於多個線程競爭偏向鎖導致偏向鎖升級為輕量級鎖。

一旦有第二個線程加入鎖競爭,偏向鎖就升級為輕量級鎖(自旋鎖)。這裏要明確一下什麼是鎖競爭:如果多個線程輪流獲取一個鎖,但是每次獲取鎖的時候都很順利,沒有發生阻塞,那麼就不存在鎖競爭。只有當某線程嘗試獲取鎖的時候,發現該鎖已經被佔用,只能等待其釋放,這才發生了鎖競爭。

在輕量級鎖狀態下繼續鎖競爭,沒有搶到鎖的線程將自旋,即不停地循環判斷鎖是否能夠被成功獲取。獲取鎖的操作,其實就是通過CAS修改對象頭裡的鎖標誌位。先比較當前鎖標誌位是否為“釋放”,如果是則將其設置為“鎖定”,比較並設置是原子性發生的。這就算搶到鎖了,然後線程將當前鎖的持有者信息修改為自己。

長時間的自旋操作是非常消耗資源的,一個線程持有鎖,其他線程就只能在原地空耗CPU,執行不了任何有效的任務,這種現象叫做忙等(busy-waiting)。如果多個線程用一個鎖,但是沒有發生鎖競爭,或者發生了很輕微的鎖競爭,那麼synchronized就用輕量級鎖,允許短時間的忙等現象。這是一種折衷的想法,短時間的忙等,換取線程在用戶態和內核態之間切換的開銷。

6.4 重量級鎖

重量級鎖顯然,此忙等是有限度的(有個計數器記錄自旋次數,默認允許循環10次,可以通過虛擬機參數更改)。如果鎖競爭情況嚴重,某個達到最大自旋次數的線程,會將輕量級鎖升級為重量級鎖(依然是CAS修改鎖標誌位,但不修改持有鎖的線程ID)。當後續線程嘗試獲取鎖時,發現被佔用的鎖是重量級鎖,則直接將自己掛起(而不是忙等),等待將來被喚醒。

重量級鎖是指當有一個線程獲取鎖之後,其餘所有等待獲取該鎖的線程都會處於阻塞狀態。

簡言之,就是所有的控制權都交給了操作系統,由操作系統來負責線程間的調度和線程的狀態變更。而這樣會出現頻繁地對線程運行狀態的切換,線程的掛起和喚醒,從而消耗大量的系統資

五、總結

文中講述了鎖的四種狀態以及鎖是如何一步一步升級的過程,文中有理解不到位或者有問題的地方,歡迎大家在評論區中下方指出和交流,謝謝大家

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

從消息中間件看分佈式系統的多種套路

     

 

 

 

  

  消息中間件作為分佈式系統的重要成員,各大公司及開源均有許多解決方案。目前主流的開源解決方案包括RabbitMQ、RocketMQ、Kafka、ActiveMQ等。消息這個東西說簡單也簡單,說難也難。簡單之處在於好用方便,接入簡單使用簡單,異步操作能夠解耦系統間的依賴,同時失敗后也能夠追溯重試。難的地方在於,設計一套可以支撐業務的消息機制,並提供高可用架構,解決消息存儲、消息重試、消息隊列的負載均衡等一系列問題。然而難也不代表沒有方法或者“套路”,熟悉一下原理與實現,多看幾個框架的源碼后多總結勢必能找出一些共性。

  消息框架大同小異,熟練掌握其原理、工作機制是必要的。就拿用的比較多的RocketMQ為引,來說說消息引擎的設計與實現。阿里的消息引擎經過了從Notify到Napoli、再到MetaQ三代的發展,現在已經非常成熟,在不同部門的代碼中現在沒準都還可以從代碼里看到這一系列演進過程。當前的Apache RocketMQ 就是阿里將MetaQ項目捐贈給了Apache基金會,而內部還是沿用MetaQ的名稱。

      首先詮釋幾個消息相關的基本概念。

  • 每個消息隊列都必須建立一個Topic。
  • 消息可以分組,每個消息隊列都至少需要一個生產者Producer和一個消費者Consumer。生產者生產發送消息,消費者接收消費消息。
  • 每個消費者和生產者都會分批提個ID。

 

RocketMQ 系統架構

 

    

 

  接下來再來看看RocketMQ的架構,如圖所示,簡要描述一下幾種角色及作用。 

  • NameServer
    • NameServer是消息Topic的註冊中心,用於發現和管理消息生產者、消費者、及路由關係。
  • Broker
    • 消息存儲與轉發的中轉站,使用隊列機制管理數據存儲。Broker中會存儲多份消息數據進行容錯,以Master/Slave的架構保證系統的高可用,Broker中可以部署單個或多個Master。單個Master的場景,Master掛掉后,Producer新產生的消息無法被消費,但已經發送到Broker的消息,由於Slave節點的存在,還能繼續被Consumer所消費;如果部署多個Master則系統能能正常運轉。
    • 另外,Broker中的Master和Slave不是像Zookeeper集群中用選舉機制進行確定,而是固定的配置,這也是在高可用場景需要部署多個Master的原因。
    • 生產者將消息發送到Broker中后,Broker會將消息寫到本地的CommitLog文件中,保存消息。
  • Producer
    • 生產者會和NameServer集群中某一節點建立長鏈接,定時從NamerServeri獲取Topic路由信息,並且和Broker建立心跳。
  • Consumer
    • 消費者需要給生產者一個明確的消費成功的回應,MetaQ才會認為消費成功,否則失敗。失敗后,RocketMQ會將消息重新發回Broker,在指定的延遲時間內進行重試,當重試達到一定的次數后(默認16次),MetaQ則認為此消息不能被消費,消息會被投遞到死信隊列。

 

  這個架構看其實是否很熟悉?好像接觸過的一些分佈式系統的架構和這個長的都比較像是吧,甚至只要裏面框圖的角色稍微換換就能變成另外一個框架的介紹,比如Dubbo/Redis…。

並且在RocketMQ架構設計中,要解決的問題與其他分佈式框架也可以觸類旁通。Master/Slave機制,天然的讀寫分離方式都是分佈式高可用系統的典型解決方案。

負載均衡

  負載均衡是消息框架需要解決的又一個重要問題。當系統中生產者生產了大量消息,而消費者有多個或多台機器時,就需要平衡負載,讓消息均分地被消費者進行消費。目前RocketMQ中使用了多種負載均衡算法。主要有以下幾種,靜態配置由於過於簡單,直接為消費者配置需要消費的隊列,因此直接忽略。

  1. 求平均數法
  2. 環形隊列法
  3. 一致Hash算法
  4. Machine Room算法
  5. 靜態配置

  來看一下源碼,RocketMQ內部對以上負載均衡算法均有實現,並定義了一個接口 AllocateMessageQueueStrategy,採用策略模式,每種負載均衡算法都依靠實現這個接口實現,在運行中,會獲取這個接口的實例,從而動態判斷到底採用的是哪種負載均衡算法。

 1 public interface AllocateMessageQueueStrategy {
 2 
 3     /**
 4      * Allocating by consumer id
 5      *
 6      * @param consumerGroup current consumer group
 7      * @param currentCID current consumer id
 8      * @param mqAll message queue set in current topic
 9      * @param cidAll consumer set in current consumer group
10      * @return The allocate result of given strategy
11      */
12     List<MessageQueue> allocate(
13         final String consumerGroup,
14         final String currentCID,
15         final List<MessageQueue> mqAll,
16         final List<String> cidAll
17     );
18 
19     /**
20      * Algorithm name
21      *
22      * @return The strategy name
23      */
24     String getName();
25 }

 

 

1. 求平均數法

  顧名思義,就是根據消息隊列的數量和消費者的數量,求出單個消費者上應該負擔的平均消費隊列數,然後根據消費者的ID,按照取模的方式將消息隊列分配到指定的consumer上。具體代碼可以去Github上找,截取核心算法代碼如下, mqAll就是消息隊列的結構,是一個MessageQueue的List,cidAll是消費者ID的列表,也是一個List。考慮mqAll和cidAll固定時以及變化時,當前消費者節點會從隊列中獲取到哪個隊列中的消息,比如當 averageSize 大於1時,這時每個消費者上的消息隊列就不止一個,而分配在每個消費者的上的隊列的ID是連續的。

 

 1     int index = cidAll.indexOf(currentCID);
 2         int mod = mqAll.size() % cidAll.size();
 3         int averageSize =
 4             mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()
 5                 + 1 : mqAll.size() / cidAll.size());
 6         int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;
 7         int range = Math.min(averageSize, mqAll.size() - startIndex);
 8         for (int i = 0; i < range; i++) {
 9             result.add(mqAll.get((startIndex + i) % mqAll.size()));
10         }
11         return result;

 

2. 環形平均法

  這種算法更為簡單。首先獲取當前消費者在整個列表中的下標index,直接用求余方法得到當前消費者應該處理的消息隊列。注意mqAll的size和cidAll的size可以是任意的。

  • 當ciAll.size() == mqAll.size() 時,該算法就是類似hashtable的求余分桶。
  • 當ciAll.size() > mqAll.size() 時,那麼多出的消費者上並不能獲取到消費的隊列,只有部分消費者能夠獲取到消息隊列並執行,相當於在消費者資源充足的情況下,由於隊列數少,所以使用其中一部分消費者就能滿足需求,不用額外的開銷。
  • 當ciAll.size() < mqAll.size() 時,這樣每個消費者上需要負載的隊列數就超過了1個,並且區別於直接求平均的方式,分配在每個消費者上的消費隊列不是連續的,而是有一定步長的間隔。
1         int index = cidAll.indexOf(currentCID);
2         for (int i = index; i < mqAll.size(); i++) {
3             if (i % cidAll.size() == index) {
4                 result.add(mqAll.get(i));
5             }
6         }
7         return result;

 

3. 一致Hash算法

  循環所有需要消費的隊列,根據隊列toString后的hash值計算出處理當前隊列的最近節點並分配給該節點。routeNode 中方法稍微複雜一些,有時間建議細看,這裏就只說功能。

 1      Collection<ClientNode> cidNodes = new ArrayList<ClientNode>();
 2         for (String cid : cidAll) {
 3             cidNodes.add(new ClientNode(cid));
 4         }
 5 
 6         final ConsistentHashRouter<ClientNode> router; //for building hash ring
 7         if (customHashFunction != null) {
 8             router = new ConsistentHashRouter<ClientNode>(cidNodes, virtualNodeCnt, customHashFunction);
 9         } else {
10             router = new ConsistentHashRouter<ClientNode>(cidNodes, virtualNodeCnt);
11         }
12 
13         List<MessageQueue> results = new ArrayList<MessageQueue>();
14         for (MessageQueue mq : mqAll) {
15             ClientNode clientNode = router.routeNode(mq.toString());
16             if (clientNode != null && currentCID.equals(clientNode.getKey())) {
17                 results.add(mq);
18             }
19         }
20 
21         return results;

 

 

4. Machine Room算法

  基於機房的Hash算法。這個命名看起來很詐唬,其實和上面的普通求余算法是一樣的,只不過多了個配置和過濾,為了把這個說清楚就把源碼貼全一點。可以看到在這個算法的實現類中多了一個成員 consumeridcs,這個就是consumer id的一個集合,按照一定的約定,預先給broker命名,例如us@metaq4,然後給不同集群配置不同的consumeridcs,從而實現不同機房處理不同消息隊列的能力。

 1 /*
 2  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  * contributor license agreements.  See the NOTICE file distributed with
 4  * this work for additional information regarding copyright ownership.
 5  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  * (the "License"); you may not use this file except in compliance with
 7  * the License.  You may obtain a copy of the License at
 8  *
 9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package com.aliyun.openservices.shade.com.alibaba.rocketmq.client.consumer.rebalance;
18 
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Set;
22 import com.aliyun.openservices.shade.com.alibaba.rocketmq.client.consumer.AllocateMessageQueueStrategy;
23 import com.aliyun.openservices.shade.com.alibaba.rocketmq.common.message.MessageQueue;
24 
25 /**
26  * Computer room Hashing queue algorithm, such as Alipay logic room
27  */
28 public class AllocateMessageQueueByMachineRoom implements AllocateMessageQueueStrategy {
29     private Set<String> consumeridcs;
30 
31     @Override
32     public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll,
33         List<String> cidAll) {
34         List<MessageQueue> result = new ArrayList<MessageQueue>();
35         int currentIndex = cidAll.indexOf(currentCID);
36         if (currentIndex < 0) {
37             return result;
38         }
39         List<MessageQueue> premqAll = new ArrayList<MessageQueue>();
40         for (MessageQueue mq : mqAll) {
41             String[] temp = mq.getBrokerName().split("@");
42             if (temp.length == 2 && consumeridcs.contains(temp[0])) {
43                 premqAll.add(mq);
44             }
45         }
46 
47         int mod = premqAll.size() / cidAll.size();
48         int rem = premqAll.size() % cidAll.size();
49         int startIndex = mod * currentIndex;
50         int endIndex = startIndex + mod;
51         for (int i = startIndex; i < endIndex; i++) {
52             result.add(mqAll.get(i));
53         }
54         if (rem > currentIndex) {
55             result.add(premqAll.get(currentIndex + mod * cidAll.size()));
56         }
57         return result;
58     }
59 
60     @Override
61     public String getName() {
62         return "MACHINE_ROOM";
63     }
64 
65     public Set<String> getConsumeridcs() {
66         return consumeridcs;
67     }
68 
69     public void setConsumeridcs(Set<String> consumeridcs) {
70         this.consumeridcs = consumeridcs;
71     }
72 }

 

  由於近些年阿裏海外業務的擴展和投入,RocketMQ 等中間件對常見的海外業務場景的支持也更加健全。典型的場景包括跨單元消費以及消息路由。跨單元消費是比較好實現的,就是在consumer中增加一個配置,指定接收消息的來源單元,RocketMQ內部會完成客戶端從指定單元拉取消息的工作。而全球消息路由則是需要一些公共資源,消息的發送方只能將消息發送到一個指定單元/機房,然後將消息路由到另外指定的單元,consumer部署在指定單元。區別在於一個配置在客戶端,一個配置在服務端。

 

 

總結

從RocketMQ的設計、原理以及用過的個人用過的其他分佈式框架上看,典型的分佈式系統在設計中無外乎要解決的就是以下幾點,RocketMQ全都用上了。

  • 服務的註冊和發現。一般會有一個統一的註冊中心進行管理維護。
  • 服務的提供方和使用方間的通信,可以是異步也可以是同步,例如dubbo服務同步服務,而消息類型就是異步通信。
  • HA——高可用架構。八字決 ———— “主從同步,讀寫分離”。 要再加一句的話可以是“異地多活”。
  • 負載均衡。典型的負載均衡算法在文章內容裏面已經列出好幾種了,常用的基本也就這些。

當然消息框架設計中用到的套路遠不止這些,包括如何保證消息消費的順序性、消費者和服務端通信、以及消息持久化等問題也是難點和重點,同樣,分佈式緩存系統也需要解決這些問題,先寫到這裏,要完全理解並自己設計一個這樣的框架難度還是相當大的。

 

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

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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

分類
發燒車訊

既能穿西裝又能幹苦活的全能选手,教授試駕全新菱智M5

賬面數據來看,它不會讓你有很高昂的駕駛激情,但實際駕駛過程中,也是足夠使用的。菱智M5的離合器匹配很成熟,離合踏板幅度較大,需要稍微適應一下,等離合器到位后順勢給油就能完美起步,熟練的司機可以做到毫無頓挫感。

自2007年第一代菱智面世以來,這款MpV已經在市場上走了9年之久。親民的定價、務實的用途,使得菱智在三四線城市中備受歡迎,而在大城市的貨運車中,也常能看見它的身影。但說實在的,把菱智系列當商務用車、甚至家用車的車主屈指可少,很大原因是因為它的產品價值被死死定在了貨運上,“撐不住面子”的它,很難上檯面。

在新款菱智M5上,我們能看到它悄悄打上了“新商務”的標籤,不僅可以貨物運輸,還能兼顧商務接待,這不禁讓對這次試駕充滿了期待,畢竟之前試駕都以乘用車為主,MpV還真的不多。

外觀設計、空間表現如何?

新款菱智M5的外觀設計比起舊款有了不少的提升,鷹眼似的前大燈看上去銳利無比,四條格柵呈“展翼”狀,採用鍍鉻處理。外觀的升級是值得肯定的,起碼整體的氣質變化很明顯。

菱智M5車身尺寸為:4745*1720*1940,軸距2800。內部空間是十分夠用的,特別是第三排座椅,1米83的身高坐進去一點壓迫感都沒有,別說更寬敞的第二排了。不過有利有弊,如果第三排座椅立起的情況下,後備箱的空間就顯得不足了。

不過如果放下後排座椅,這輛菱智M5也會很好地發揮它的貨運本能,可輕鬆放進一台電腦桌。

開起來是什麼感覺?

試駕的這款菱智M5,搭載的是三菱4A92發動機以及5MT變速箱,最大轉速6000轉,最大馬力122,最高扭矩153牛米。賬面數據來看,它不會讓你有很高昂的駕駛激情,但實際駕駛過程中,也是足夠使用的。

菱智M5的離合器匹配很成熟,離合踏板幅度較大,需要稍微適應一下,等離合器到位后順勢給油就能完美起步,熟練的司機可以做到毫無頓挫感。

試駕的時候車上載着5個成年人,並開啟着空調。油門響應比較平緩,需要稍微深踩,降檔地板油時,轉速也不會提高的很积極,所以超車能力一般,但滿載情況下,也滿足市區需求了。不過如果做商務接待的話,平穩的駕駛才是最重要的,即便是貨運的話,這個動力也完全足夠了。

菱智M5的底盤調教功底還是有的,採用雙橫臂扭桿彈簧獨立前懸架和鋼板彈簧后懸架。這樣的懸挂設計,側重承載能力,為商用物流運輸提供了可靠的保障,而且耐用性更能從容應對山路、爛路等複雜路況。

順帶說一句,新款菱智M5的隔音做得不錯,怠速抖動在接受範圍內,基本上只有細微的風噪和胎噪,做工用料值得肯定。

最後總結:

菱智系列一直以來的銷量都很穩定,今年以來一直在1萬浮動上下,這與它空間大、外觀好看、內飾美觀、底盤耐用…等等優點是分不開的。更何況菱智M5的定價僅為7.19萬~8.49萬,所以也不必用更苛刻的眼光去看待了。務實耐用,貨運和商務都兼顧,這款車已經做得足夠好了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

這輛SUV說起操控,它敢認第二沒有人認第一!

5s的百公里加速時間。而且這隻是之前發布的Mini cooper JCW版本功率,countryman可能有着更讓人驚訝的動力表現。除了發動機不同的以外,懸架以及剎車都有着很大的改善,有着更加的性能表現。一旦駕駛起來,你就會發現它們之間有着很大的不同。

前言

對於SUV來說,天生的高重心以及高質量,使得它們在操控方面一直是比較弱的。不過有一個品牌卻是把運動做得淋漓盡致,最近它的又一諜照被外國所捕捉到,並且給我們帶來了不少的信息,相信這會是SUV界的最有駕駛樂趣的一員。

(上圖為2017款Mini Countryman S)

它就是寶馬旗下的MINI Countryman,大家可以將它理解為Mini cooper的加高五門版本。雖然加高加長了,但是它保留着Mini幾乎卡丁車的那種樂趣。

最近被曝光的是JCW版本的Mini Country Man,這是在普通版本上性能更進一部的版本。而它在最近的曝光中,最先是在紐林伯格,也就是眾多跑車測試性能的紐北。有着這般的背景,它的性能不容小覷。

單純在遮蓋的外觀來看,JCW版本Country Man在包圍上會有着較大的不同,取消了霧燈換來更大的散熱面積。

(上圖為2017款Mini Countryman S發動機)

動力方面將使用的是2.0T渦輪增壓發動機,最大功率162千瓦,最大扭矩320牛米,可以讓它有着6.5s的百公里加速時間。而且這隻是之前發布的Mini cooper JCW版本功率,countryman可能有着更讓人驚訝的動力表現。

除了發動機不同的以外,懸架以及剎車都有着很大的改善,有着更加的性能表現。一旦駕駛起來,你就會發現它們之間有着很大的不同。

內飾方面相信和新款的countryman幾乎一模一樣。僅僅在一些如方向盤、儀錶、座椅等細節處有着一點差異。

在國內,countryman JCW的最大競爭對手就是奔馳的GLA45 ,售價為57.80。但鑒於這是Mini品牌的產品,所以價格極有可能會在40萬左右。

編者總結:

這應該是市場上最具駕駛樂趣的SUV,在操控以及動力上和競爭對手相比有着較大的優勢,在外觀內飾上也有着非常出色表現,非常的個性和獨特。不過這註定是一部分人的玩物,並且是不使用的玩物,畢竟它在後排空間上始終是處於弱勢。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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

分類
發燒車訊

c#中的值類型和引用類型

值類型和引用類型,是c#比較基礎,也必須掌握的知識點,但是也不是那麼輕易就能掌握,今天跟着老胡一起來看看吧。
 

典型類型

首先我們看看這兩種不同的類型有哪些比較典型的代表。
 

典型值類型

int, long, float, double等原始類型中表示数字的類型都是值類型,表示時間的datatime也是值類型,除此之外我們還可以通過關鍵字struct自定義值類型。
 

典型引用類型

原始類型中,array, list, dictionary, queue, stack和string都是引用類型,除此之外我們通過關鍵字class自定義引用類型。
 

基類

c#中所有的類型都最終繼承自Object,這是沒有疑問的,但是這其中還有些微區別。
 

值類型基類

對於值類型來說,除了最終繼承自Object,還繼承自ValueType,繼承鏈如下

但是請不要誤解,這裏僅僅指的是值類型天然是ValueType,但是不代表值類型能夠這麼聲明

struct Struct1 : ValueType
{

}

這樣是會引起編譯錯誤的,值類型不能繼承任何其他類型,值類型只能實現接口,不能繼承自其它類型。只有引用類型既可以實現接口也能繼承自其它類型。順便說一下,還有一點比較重要的是,ValueType重寫了Object基類的Equals方法和GetHashCode方法,所以當使用Equals比較兩個值類型的時候,系統會比較兩個值類型的各個屬性是否相等,再返回結果,這就是所謂的相等性。與此相對,引用類型在使用Equals的時候,會在後台調用object.ReferenceEquals,換言之,引用類型在比較相等性的時候會考慮同一性
 

引用類型基類

對於引用類型就沒有那麼麻煩,引用類型不會繼承自ValueType。引用類型可以繼承其他類型。
 

在內存中的表現

我們都知道,C#將內存分為了兩部分,一個是Stack,另外一個是Managed Heap。一般來說,用於函數調用進棧,函數返回出棧,用的是Stack,而當創造一個新的實例時,會根據創建的實例屬於值類型還是引用類型決定使用Stack還是Managed Heap。
 

值類型在內存中

當創建一個值類型對象時,c#會在Stack上面創建一塊空間,這塊空間就存放這個值類型對象。
int是一個典型的值類型,如下語句

int age = 10;

會存在於內存中的Stack上面。

如果把值類型的實例賦值給另外一個值類型,那麼效果就是複製一個新的值類型實例。

int myAge = age;

 

引用類型在內存中

與值類型在內存中的表現不一樣,創建一個引用類型的實例,不但會在Stack上面新建一個引用,還會在Heap上面劃分出內存以容納該引用類型實例。用戶在使用的時候通過Stack上面的變量間接引用該實例。

class Author
{
	public string Name{get;set;}
	public int Age{get;set;}
}

Author author = new Author(){Name="deatharthas", Age= 32};

注意看和值類型在內存中的區別,引用類型通過Stack上的變量訪問位於Heap上面的實例。
在賦值的時候,拷貝的僅僅是Stack上面的變量,新拷貝出來的對象和舊的對象指向的是同一塊內存。

Author myAuthor = author;

這個時候,author和myAuthor指向同一塊內存,稱為同一性,通過調用

object.ReferenceEquals(myAuthor, author);

可以得到驗證。
 
但可能有細心的朋友會有疑問了,不是說int是值類型,值類型是存在於Stack上面的嗎?為什麼在author類裏面,它會在Heap裏面呢?贊一個細心!值類型一般存在於Stack上面,但如果某個值類型包含於引用類型,那麼它也會隨着那個引用類型存放在Heap上面。
 

當參數時的行為區別

c#中的參數傳遞默認都是傳值(by value),但是根據所傳遞對象是值類型還是引用類型,它們的行為還是有所區別,現在我們來看看。

值類型當參數

值類型當參數的時候,傳遞到函數內部的是一份值類型的拷貝,所以在函數內部修改這個拷貝不會影響原對象。除非我們在傳遞參數的時候使用了ref或者out。
 

引用類型當參數

如果參數是引用類型,傳遞到函數內部的依然是一份拷貝,但是這個拷貝是其在Stack上面的變量的拷貝,就像上面的賦值那個例子。所以這個時候這份拷貝其實和原對象指向同一塊內存(指向同一性),修改這個對象可以反映到原對象上面。
 

謹慎返回引用類型

編程是一項需要謹慎的工作,有時候我們經常會犯一些錯誤,而這些錯誤又是那麼的不明顯以至於不摔坑幾次,我們根本察覺不了,考慮下面一個例子。

    class People
    {
        public string Name { get; set; }
        public int Age { get; set; }
        private People _Father = null;
        public People Father { get { return _Father; } }
        public People(People father)
        {
            _Father = father;
        }
        public void ShowFather()
        {
            Console.WriteLine("father's name is " + Father.Name + " and his age is " + Father.Age);
        }
    }

    class Program
    {        
        static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            son.ShowFather();
            Console.ReadLine();
        }
    }

看起來沒什麼問題,對吧?Father沒有提供setter,似乎是安全的。但是我們試試下面的代碼。

	static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            var f = son.Father;
            f.Name="Changed";
            son.ShowFather();
            Console.ReadLine();
        }

看,發現了什麼,外部改變了本來應該被封裝所保護的Father屬性,封裝被破壞了!
稍微一想我們應該能明白這個道理,Father屬性返回的拷貝的變量和原Father變量指向同一塊實例。要想解決這個問題,我們要麼返回一個值類型,要麼返回一個全新的對象。修改Father屬性如下:

public People Father { get { return new People(_Father._Father) { Name = _Father.Name, Age = _Father.Age }; } }

再次測試,

這次封裝就沒問題了。
 

總結

我們大概知道了值類型和引用類型的區別,包括它們的行為,在內存的居住方式,以及使用引用類型時可能會遇到的暗坑,希望大家通過閱讀這篇文章,能夠加深一些對它們的了解,少走一些彎路。
今天也簡單的提到了比較時的同一性,和預防封裝被破壞所採用的返回一個新的實例拷貝的策略(這個時候適合使用DeepCopy),我們之後有機會再詳細聊。

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

WebService之Spring+CXF整合示例

一、Spring+CXF整合示例

WebService是一種跨編程語言、跨操作系統平台的遠程調用技術,它是指一個應用程序向外界暴露一個能通過Web調用的API接口,我們把調用這個WebService的應用程序稱作客戶端,把提供這個WebService的應用程序稱作服務端。

環境

win10+Spring5.1+cxf3.3.2

下載

  • 官網下載:https://archive.apache.org/dist/cxf/
  • 百度網盤:
    鏈接:https://pan.baidu.com/s/1nsUweTFG_6CcZKaVBCQ7uQ
    提取碼:4qp7

服務端

  • 新建web項目
  • 放入依賴
    apache-cxf-3.3.2\lib中的jar包全部copy至項目WEB-INF\lib目錄下(偷個懶,這些jar包中包含了Spring所需的jar包)
  • web.xml中添加webService的配置攔截
<!--webService  -->
<servlet>
    <servlet-name>CXFService</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>CXFService</servlet-name>
    <url-pattern>/webservice/*</url-pattern>
</servlet-mapping>
  • webservice服務接口
    在項目src目錄下新建pms.inface.WebServiceInterface
package pms.inface;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService(targetNamespace = "http://spring.webservice.server", name = "WebServiceInterface")
public interface WebServiceInterface {

	@WebMethod
    @WebResult(name = "result", targetNamespace = "http://spring.webservice.server")
	public String sayBye(@WebParam(name = "word", targetNamespace = "http://spring.webservice.server") String word);

}

  • 接口實現類
    在項目src目錄下新建pms.impl.WebServiceImpl
package pms.impl;

import javax.jws.WebService;

import pms.inface.WebServiceInterface;

@WebService
public class WebServiceImpl implements WebServiceInterface{

	@Override
	public String sayBye(String word) {
		return word + "當和這個真實的世界迎面撞上時,你是否找到辦法和自己身上的慾望講和,又該如何理解這個鋪面而來的人生?";
	}

}

  • webservice配置文件
    WEB-INF目錄下新建webservice配置文件cxf-webService.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xmlns:cxf="http://cxf.apache.org/core"
	xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://cxf.apache.org/jaxws
       http://cxf.apache.org/schemas/jaxws.xsd
       http://cxf.apache.org/core
	   http://cxf.apache.org/schemas/core.xsd
	   http://cxf.apache.org/transports/http/configuration
	   http://cxf.apache.org/schemas/configuration/http-conf.xsd
	   ">
	   
	<import resource="classpath:META-INF/cxf/cxf.xml" />

	<!-- 使用jaxws:server標籤發布WebService服務 ,設置address為訪問地址, 和web.xml文件中配置的CXF配合為一個完整的路徑 -->
	<!-- serviceClass為實現類的接口 serviceBean引用配置好的WebService實現類 -->
	<jaxws:server address="/webServiceInterface"
		serviceClass="pms.inface.WebServiceInterface">
		<jaxws:serviceBean>
			<ref bean="WebServiceImpl" />
		</jaxws:serviceBean>
	</jaxws:server>
	
	<!-- 為所有的WS設置超時時間 ,此時為默認值 連接時間30s,等待回復時間為60s-->	
	<http-conf:conduit name="*.http-conduit">
		<http-conf:client ConnectionTimeout="60000" ReceiveTimeout="120000"/>
	</http-conf:conduit>

</beans>
  • spring配置文件
    WEB-INF目錄下新建spring配置文件applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean id="WebServiceImpl" class="pms.impl.WebServiceImpl"></bean>
	
	<import resource="cxf-webService.xml" />

</beans>

      在web.xml中配置applicationContext.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
		    /WEB-INF/applicationContext.xml
		</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  • 將項目放至tomcat中啟動
    啟動后訪問地址:localhost:PORT/項目名/webservice/webServiceInterface?wsdl,如下圖所示,webservice接口發布成功

二、SoapUI測試

SoapUI是一個開源測試工具,通過soap/http來檢查、調用、實現Web Service的功能/負載/符合性測試。

下載

  • 百度網盤
    鏈接:https://pan.baidu.com/s/1N2RTqhvrkuzx7YJvmDeY7Q
    提取碼:e1w3

測試

  • 打開SoapUI,新建一個SOAP項目,將剛才的發布地址copyInitial WSDL欄,點擊OK按鈕
  • 發起接口請求

三、客戶端

使用wsdl2java工具生成webservice客戶端代碼

  • 該工具在剛才下載的apache-cxf-3.3.2\bin目錄下
  • 配置環境變量
    設置CXF_HOME,並添加%CXF_HOME %/binpath環境變量。
  • CMD命令行輸入wsdl2java -help,有正常提示說明環境已經正確配置
  • wsdl2java.bat用法:
wsdl2java –p 包名 –d 存放目錄 -all wsdl地址

-p 指定wsdl的命名空間,也就是要生成代碼的包名

-d 指令要生成代碼所在目錄

-client 生成客戶端測試web service的代碼

-server 生成服務器啟動web service代碼

-impl 生成web service的實現代碼,我們在方式一用的就是這個

-ant 生成build.xml文件

-all 生成所有開始端點代碼
  • 生成客戶端代碼
wsdl2java -p pms.inface -d ./ -all http://localhost:8080/spring_webservice_server/webservice/webServiceInterface?wsdl

客戶端調用

  • 新建web項目
  • 放入依賴
    apache-cxf-3.3.2\lib中的jar包全部copy至項目WEB-INF\lib目錄下
  • wsdl2java生成的代碼放至src.pms.inface目錄下
調用方法一:
  • 新建webServiceClientMain測試
package pms;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import pms.inface.WebServiceInterface;

public class webServiceClientMain {
	public static void main(String[] args) {
		JaxWsProxyFactoryBean svr = new JaxWsProxyFactoryBean();
		svr.setServiceClass(WebServiceInterface.class);
		svr.setAddress("http://localhost:8080/spring_webservice_server/webservice/webServiceInterface?wsdl");
		WebServiceInterface webServiceInterface = (WebServiceInterface) svr.create();

		System.out.println(webServiceInterface.sayBye("honey,"));
	}
}
  • 運行webServiceClientMain
調用方法二:
  • 在src目錄下新建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.0.xsd
		http://cxf.apache.org/jaxws
		http://cxf.apache.org/schemas/jaxws.xsd">

	<jaxws:client id="webServiceInterface"
		serviceClass="pms.inface.WebServiceInterface"
		address="http://localhost:8080/spring_webservice_server/webservice/webServiceInterface?wsdl" >
	</jaxws:client>	
</beans>
  • 新建webServiceClientTest測試
package pms;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pms.inface.WebServiceInterface;

public class webServiceClientTest {

	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		WebServiceInterface webServiceInterface = context.getBean(WebServiceInterface.class);
		String result = webServiceInterface.sayBye("honey,");
		System.out.println(result);
	}
	
}
  • 運行webServiceClientTest

四、服務端攔截器

  • 需求場景:服務提供方安全驗證,也就是webservice自定義請求頭的實現,服務接口在身份認證過程中的密碼字段滿足SM3(哈希函數算法標準)的加密要求
  • SM3加密所需jar包:commons-lang3-3.9.jarbcprov-jdk15on-1.60.jar,這兩個jar包在剛才下載的apache-cxf-3.3.2\lib下就有
  • 請求頭格式
<security>
	<username></username>
	<password></password>
</auth>
  • src.pms.interceptor下新建WebServiceInInterceptor攔截器攔截請求,解析頭部
package pms.interceptor;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.transport.http.AbstractHTTPDestination;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import pms.support.Sm3Utils;
import pms.support.StringUtils;

/**
 * WebService的輸入攔截器
 * @author coisini
 * @date May 2020, 13
 *
 */
public class WebServiceInInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
	
    private static final String USERNAME = "admin";
    private static final String PASSWORD = "P@ssw0rd";
    
    /**
     * 允許訪問的IP
     */
    private static final String ALLOWIP = "127.0.0.1;XXX.XXX.XXX.XXX";

	public WebServiceInInterceptor() {
		/*
		 * 攔截器鏈有多個階段,每個階段都有多個攔截器,攔截器在攔截器鏈的哪個階段起作用,可以在攔截器的構造函數中聲明
		 * RECEIVE 接收階段,傳輸層處理
		 * (PRE/USER/POST)_STREAM 流處理/轉換階段
		 * READ SOAPHeader讀取 
		 * (PRE/USER/POST)_PROTOCOL 協議處理階段,例如JAX-WS的Handler處理 
		 * UNMARSHAL SOAP請求解碼階段 
		 * (PRE/USER/POST)_LOGICAL SOAP請求解碼處理階段 
		 * PRE_INVOKE 調用業務處理之前進入該階段 
		 * INVOKE 調用業務階段 
		 * POST_INVOKE 提交業務處理結果,並觸發輸入連接器
		 */
		super(Phase.PRE_INVOKE);
	}

	/**
	  * 客戶端傳來的 soap 消息先進入攔截器這裏進行處理,客戶端的賬目與密碼消息放在 soap 的消息頭<security></security>中,
	  * 類似如下:
     * <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
     * <soap:Header><security><username>admin</username><password>P@ssw0rd</password></security></soap:Header>
     * <soap:Body></soap:Body></soap:Envelope>
     * 現在只需要解析其中的 <head></head>標籤,如果解析驗證成功,則放行,否則這裏直接拋出異常,
     * 服務端不會再往後運行,客戶端也會跟着拋出異常,得不到正確結果
     *
     * @param message
     * @throws Fault
     */
	@Override
    public void handleMessage(SoapMessage message) throws Fault {
		System.out.println("PRE_INVOKE");
		
		HttpServletRequest request = (HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST);
	    String ipAddr=request.getRemoteAddr();
	    System.out.println("客戶端訪問IP----"+ipAddr);
	    
	    if(!ALLOWIP.contains(ipAddr)) {
			throw new Fault(new IllegalArgumentException("非法IP地址"), new QName("0009"));
		}
		
		/**
		 * org.apache.cxf.headers.Header
         * QName :xml 限定名稱,客戶端設置頭信息時,必須與服務器保持一致,否則這裏返回的 header 為null,則永遠通不過的
         */
		Header authHeader = null;
		//獲取驗證頭
		List<Header> headers = message.getHeaders();
		for(Header h:headers){
			if(h.getName().toString().contains("security")){
				authHeader=h;
				break;
			}
		}
		System.out.println("authHeader");
		System.out.println(authHeader);
		
		if(authHeader !=null) {
			Element auth = (Element) authHeader.getObject();
			NodeList childNodes = auth.getChildNodes();
			String username = null,password = null;
			for(int i = 0, len = childNodes.getLength(); i < len; i++){
					Node item = childNodes.item(i);
					if(item.getNodeName().contains("username")){
						username = item.getTextContent();
						System.out.println(username);
					}
					if(item.getNodeName().contains("password")){
						password = item.getTextContent();
						System.out.println(password);
					}
			}
			
			if(StringUtils.isBlank(username) || StringUtils.isBlank(password)) { 
		    	throw new Fault(new IllegalArgumentException("用戶名或密碼不能為空"), new QName("0001")); 
		    }
			
			if(!Sm3Utils.verify(USERNAME, username) || !Sm3Utils.verify(PASSWORD,password)) { 
		    	throw new Fault(new IllegalArgumentException("用戶名或密碼錯誤"), new QName("0008")); 
		    }
		  
		    if (Sm3Utils.verify(USERNAME, username) && Sm3Utils.verify(PASSWORD,password)) { 
		    	System.out.println("webService 服務端自定義攔截器驗證通過...."); 
		    	return;//放行
		    } 
		}else {
			throw new Fault(new IllegalArgumentException("請求頭security不合法"), new QName("0010"));
		}
	}

	// 出現錯誤輸出錯誤信息和棧信息
	public void handleFault(SoapMessage message) {
		Exception exeption = message.getContent(Exception.class);
		System.out.println(exeption.getMessage());
	}
	
}
  • src.pms.support下新建Sm3Utils加密類
package pms.support;

import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.util.Arrays;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

/**
 * SM3加密
 * @author coisini
 * @date May 2020, 13
 */
public class Sm3Utils {
	 private static final String ENCODING = "UTF-8";
     static {
         Security.addProvider(new BouncyCastleProvider());
     }
	    
    /**
     * sm3算法加密
     * @explain
     * @param paramStr
     * 待加密字符串
     * @return 返回加密后,固定長度=32的16進制字符串
     */
    public static String encrypt(String paramStr){
        // 將返回的hash值轉換成16進制字符串
        String resultHexString = "";
        try {
            // 將字符串轉換成byte數組
            byte[] srcData = paramStr.getBytes(ENCODING);
            // 調用hash()
            byte[] resultHash = hash(srcData);
            // 將返回的hash值轉換成16進制字符串
            resultHexString = ByteUtils.toHexString(resultHash);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return resultHexString;
    }
    
    /**
     * 返回長度=32的byte數組
     * @explain 生成對應的hash值
     * @param srcData
     * @return
     */
    public static byte[] hash(byte[] srcData) {
        SM3Digest digest = new SM3Digest();
        digest.update(srcData, 0, srcData.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        return hash;
    }
    
    /**
     * 通過密鑰進行加密
     * @explain 指定密鑰進行加密
     * @param key
     *            密鑰
     * @param srcData
     *            被加密的byte數組
     * @return
     */
    public static byte[] hmac(byte[] key, byte[] srcData) {
        KeyParameter keyParameter = new KeyParameter(key);
        SM3Digest digest = new SM3Digest();
        HMac mac = new HMac(digest);
        mac.init(keyParameter);
        mac.update(srcData, 0, srcData.length);
        byte[] result = new byte[mac.getMacSize()];
        mac.doFinal(result, 0);
        return result;
    }
    
    /**
     * 判斷源數據與加密數據是否一致
     * @explain 通過驗證原數組和生成的hash數組是否為同一數組,驗證2者是否為同一數據
     * @param srcStr
     *            原字符串
     * @param sm3HexString
     *            16進制字符串
     * @return 校驗結果
     */
    public static boolean verify(String srcStr, String sm3HexString) {
        boolean flag = false;
        try {
            byte[] srcData = srcStr.getBytes(ENCODING);
            byte[] sm3Hash = ByteUtils.fromHexString(sm3HexString);
            byte[] newHash = hash(srcData);
            if (Arrays.equals(newHash, sm3Hash))
                flag = true;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return flag;
    }
    
    public static void main(String[] args) {
        // 測試二:account
        String account = "admin";
        String passoword = "P@ssw0rd";
        String hex = Sm3Utils.encrypt(account);
        System.out.println(hex);//dc1fd00e3eeeb940ff46f457bf97d66ba7fcc36e0b20802383de142860e76ae6
        System.out.println(Sm3Utils.encrypt(passoword));//c2de40449a2019db9936381fa9810c22c8548a8635ed2b7fb3c7ec362e37429d
        //驗證加密后的16進制字符串與加密前的字符串是否相同
        boolean flag =  Sm3Utils.verify(account, hex);
        System.out.println(flag);// true
    }
}
  • StringUtils工具類
package pms.support;

/**
 * 字符串工具類
 * @author coisini
 * @date Nov 27, 2019
 */
public class StringUtils {

	/**
	 * 判空操作
	 * @param value
	 * @return
	 */
	public static boolean isBlank(String value) {
		return value == null || "".equals(value) || "null".equals(value) || "undefined".equals(value);
	}

}
  • cxf-webService.xml添加攔截器配置
<!-- 在此處引用攔截器 -->
<bean id="InInterceptor"
	class="pms.interceptor.WebServiceInInterceptor" >
</bean>

<cxf:bus>
	<cxf:inInterceptors>
		<ref bean="InInterceptor" />
	</cxf:inInterceptors>
</cxf:bus> 
  • SoapUI調用
  • java調用

    服務端攔截器到此結束,由上圖可以看出攔截器配置生效

五、客戶端攔截器

  • src.pms.support下新建AddHeaderInterceptor攔截器攔截請求,添加自定義認證頭部
package pms.support;

import java.util.List;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage>{ 
    
    private String userName; 
    private String password; 
       
    public AddHeaderInterceptor(String userName, String password) { 
        super(Phase.PREPARE_SEND); 
        this.userName = userName; 
        this.password = password;  
    } 
   
    @Override 
    public void handleMessage(SoapMessage msg) throws Fault { 
    	   System.out.println("攔截...");
        
           /**
            * 生成的XML文檔
            * <authHeader>
            *      <userName>admin</userName>
            *      <password>P@ssw0rd</password>
            * </authHeader>
            */ 
        
        	// SoapHeader部分待添加的節點
     		QName qName = new QName("security");
     		Document doc = DOMUtils.createDocument();

     		Element pwdEl = doc.createElement("password");
     		pwdEl.setTextContent(password);
     		Element userEl = doc.createElement("username");
     		userEl.setTextContent(userName);
     		Element root = doc.createElement("security");
     		root.appendChild(userEl);
     		root.appendChild(pwdEl);
     		// 創建SoapHeader內容
     		SoapHeader header = new SoapHeader(qName, root);
     		// 添加SoapHeader內容
     		List<Header> headers = msg.getHeaders();
     		headers.add(header); 
    } 
}
  • java調用,修改webServiceClientMain調用代碼如下
public class webServiceClientMain {
	public static void main(String[] args) {
		JaxWsProxyFactoryBean svr = new JaxWsProxyFactoryBean();
		svr.setServiceClass(WebServiceInterface.class);
		svr.setAddress("http://localhost:8081/spring_webservice_server/webservice/webServiceInterface?wsdl");
		WebServiceInterface webServiceInterface = (WebServiceInterface) svr.create();
		
		// jaxws API 轉到 cxf API 添加日誌攔截器
		org.apache.cxf.endpoint.Client client = org.apache.cxf.frontend.ClientProxy
				.getClient(webServiceInterface);
		org.apache.cxf.endpoint.Endpoint cxfEndpoint = client.getEndpoint();
		//添加自定義的攔截器
		cxfEndpoint.getOutInterceptors().add(new AddHeaderInterceptor("dc1fd00e3eeeb940ff46f457bf97d66ba7fcc36e0b20802383de142860e76ae6", "c2de40449a2019db9936381fa9810c22c8548a8635ed2b7fb3c7ec362e37429d"));
		
		System.out.println(webServiceInterface.sayBye("honey,"));
	}
}

  • SoapUI調用

六、代碼示例

服務端:https://github.com/Maggieq8324/spring_webservice_server.git
客戶端:https://github.com/Maggieq8324/spring_webservice_client.git

.end

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

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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

分類
發燒車訊

真不是隨便選的,原來車漆顏色的選擇有那麼多門道

而且合適的車漆能讓我們的愛車有着更好的外觀效果。

筆者總結:

所以說車漆的選擇是有一定門道,這是我們在購車前就應該了解的,畢竟這關乎到我們用車養車的各個方面。而且合適的車漆能讓我們的愛車有着更好的外觀效果。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

分類
發燒車訊

論冠道具備何種洪荒之力,從SUV戰場突圍

在通常情況下,車輛處於前輪驅動行駛狀態,這套系統會實時監控發動機扭矩以及各車輪轉速等信息參數。當需要後輪的驅動力時,电子系統就會將動力分配給後輪,得到一定的脫困能力和爬坡能力,比我們平常熟悉的適時四驅系統智能上不少。

熟悉數碼產品的你們,應該都會知道有句話叫做“索尼大法好”,這讓索尼上升到了宗師級的地位,這滿滿的都是情懷。而在汽車界,“本田大法好”也是我們經常聊到的話題,本田的“黑科技”使不少朋友們都成為了本田粉,這也是滿滿的情懷之說。

繼小型化10AT變速箱之後,前不久,本田又要在變速箱領域中想要大顯身手,據海外媒體報道,本田已向日本專利局為其全新變速箱提出申請,這台變速箱有11個擋位並且包括了3個離合器。

這再次證明了本田在研製和調校變速箱有着絕對的實力,而全新上市的冠道則採用了來自德國ZF的9AT變速箱,相信大家都不陌生,它之前在路虎攬勝極光、Jeep自由光上都有搭載。

按照本田的設計和理論來說,更多的擋位和離合器會讓換擋響應效率更高,跳擋更加平順,而且還能有效減少扭矩損失,意味着能夠達到更好的燃油經濟性。

毫無疑問,搭載着9AT變速箱的冠道駕駛起來平順之餘,燃油經濟性同樣突出。根據官方的說法,這台9速自動變速箱中從6擋開始即為超速擋,也就是輸入轉速低於輸出轉速,更多的超速擋意味着在寬泛的車速區間能以更經濟的轉速行駛,這也是省油的原因之一。

告訴大家一個小秘密,冠道作為大塊頭喝93號(京92號)汽油,油箱容積為57L,加滿一箱油才不到四百塊,能夠省不少用車成本。

思域TYpE-R在本田粉心目中的地位是非常高的,紅頭髮動機對我們這一代人來說意味着本田的“最強動力”,這台發動機征戰了無數次紐博格林北環賽道,7分50秒這個数字在本田粉心中一直揮之不去。

採用2.0T發動機的冠道,其發動機就是源自思域TYpE-R的紅頭髮動機而打造的,作為一台中型SUV,採用了性能車的發動機也是實屬罕見。

最大功率 200 kW(272ps)/6500rpm,最大扭矩 370N m/2250-4500rpm,單純從數據上看,或許你以為這就是一台小鋼炮。

冠道在同級別車型中擁有着最強動力,比起2.0T漢蘭達最大功率162kW(220ps)強上不是一星半點,8秒內能時速破百,看到這裏你服氣嗎?你要想想這大塊頭擁有着1.8噸左右的車重…

●VTEC渦輪增壓技術,有着更高更徹底的燃燒效率;

●帶電動廢氣門的高功率渦輪增壓以及雙進排氣VTC,告別渦輪遲滯;

●全系標配發動機節能自動啟停系統,進一步實現燃油經濟性.

對於一台中型SUV來說,車輛的脫困能力自然要求不低,所以作為一台中型SUV的冠道,在四驅系統方面一點也不馬虎,採用的四驅系統為全路況的Real-Time AWD智能四驅,那麼該如何理解呢?

在通常情況下,車輛處於前輪驅動行駛狀態,這套系統會實時監控發動機扭矩以及各車輪轉速等信息參數。當需要後輪的驅動力時,电子系統就會將動力分配給後輪,得到一定的脫困能力和爬坡能力,比我們平常熟悉的適時四驅系統智能上不少。

▲IDM多路況駕駛適應系統

擁有一套完善優秀的四驅系統還不夠,講求越野或是舒適還是得靠底盤,冠道的IDM多路況駕駛適應系統既能滿足城市駕駛也能應對越野路況,總能給人一種最合適的駕駛感受,SpORT OR COMFORT?這是你的選擇。

或許很多人都質疑冠道為什麼沒有7座版本,但從另外一個角度來想,其實這才是明智的選擇。

相比雞肋的第三排,還不如更加寬敞的第二排來得實在,老實說,七座SUV的第三排座椅的利用率是真的低,換作是誰都不願意去第三排座椅坐,不是頂頭就是雙腳放着難受,總之第三排座椅的乘坐體驗是不太好。

就特別心疼老人家坐第三排座椅,為了讓年輕人或小孩子坐前排,通常都是強顏歡笑說不難受,可是作為兒子來說,心裏真的不好受,家裡人多還是選擇7座的MpV更好。

如果你是一名公務人員,經常接待客戶的話,冠道的超寬敞空間能給你的客戶帶來輕鬆的乘坐體驗,加上雙層靜音玻璃、12個音響環繞、後排獨立空調出風口、電動遮陽簾以及超大的全景天窗,這些處處都能給你重要的客戶或長輩帶來與眾不同的體驗。

當然,冠道有着大容量滑道式對開手扶箱,想一想从里面拿出一份資料給客戶看的情景,會心一笑。

冠道採用了本田CONCEpT D概念車的設計理念,什麼?你不知道CONCEpT D長啥樣?下面放圖希望大家都能HOLD住…

從CONCEpT D的設計理念中看,近來本田上市的車型都有着其中一些設計元素,從外貌上更新換代,也能猜到了本田往後推出車型的外觀設計方向。

近兩年,在前臉的設計上大家都喜歡將大燈總成和中網格柵連接在一起,這樣更顯得前衛一些。冠道基於CONCEpT-D的原型打造,我們不難看出有不少共同之處,最令人喜歡的莫過於就是全系LED的燈光,處處彰顯着高端大氣的形象。

足以可以用“迷人”兩個字來形容冠道的鷹翼式全LED前大燈,另外還搭配了ACL主動轉向照明系統,根據車輛的轉向,調節燈光動態,減少了行駛中的“盲區”,增加轉彎時的安全。

這樣的外觀設計,你覺得能支撐起冠道之名嗎?

冠道帶着洪荒之力,

從SUV戰場中突圍,

你期待嗎?

總結:

本田一向以緊湊型轎車和SUV打天下,如今冠道的推出,不斷完善本田SUV家族的矩陣,雖說冠道的起步定價高,但未來將會推出1.5T發動機版本的冠道,價格會更加親民一些,而一款優質的SUV車型擺在你眼前,不好好珍惜,又更待何時呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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