這台800萬的自主車 馬化騰 劉強東 雷軍人手一台

全球最快電動車發布 蔚來 Ep9動力方面搭載了4台高性能電機以及4個獨立變速箱,最終能夠輸出1,360匹馬力,0到200公里加速7。1秒,極速313公里。Ep9採用彈匣式可換電池系統,快充模式下充滿電僅需45分鐘,續航里程可達427公里。搭載的DRS可調擾流控制系統,包括三種可調模式的動態尾翼系統,和全尺寸底盤擴散器等空氣動力裝置,使得Ep9在每小時240公里的速度下能夠獲得高達24,000牛的下壓力。

思域1.0T正式發布

本次只有手動/自動各一個車型,價格分別是11.59萬和12.79萬元。相比1.5T的豪華型,前排側氣囊、前後排頭部氣簾、無鑰匙進入/啟動、電動天窗、后駐車雷達、全液晶儀錶盤、後排杯架、後排中央扶手、中控屏、前霧燈、后視鏡加熱、自動空調統統都沒有了,3缸發動機還簡配成這樣,大致意思就是說這已經是最低價了,所以 1.5T的車型該加價的加價,該等車的等車繼續等吧。

樂視 lucid motors

官方表明,從造電池到造車,Atieva算是進行了一個大的跨越,而其核心競爭力則是其獨特的電池冷卻和能量管理技術,電池的能量密度比其他競爭對手普遍高出20%,續航里程可以輕鬆超過480km,它的最大馬力可以達到1200匹,最後為了安全起見被設置在900匹。儘管如此,它的百公里加速時間也可以達到2.69秒,還沒看到實車在路上跑之前,我們就看看咯。

全球最快電動車發布 蔚來 Ep9

動力方面搭載了4台高性能電機以及4個獨立變速箱,最終能夠輸出1,360匹馬力,0到200公里加速7.1秒,極速313公里。Ep9採用彈匣式可換電池系統,快充模式下充滿電僅需45分鐘,續航里程可達427公里。

搭載的DRS可調擾流控制系統,包括三種可調模式的動態尾翼系統,和全尺寸底盤擴散器等空氣動力裝置,使得Ep9在每小時240公里的速度下能夠獲得高達24,000牛的下壓力。

紀錄片显示Ep9在10月12日德國紐博格林北環賽道進行的測試中,創造了7分05秒的最快電動汽車圈速,公司創始投資人包括雷軍,馬化騰,奶茶妹夫劉強東,整個發布會過程中透露Ep9的造價達到了120萬美元,據之前消息稱未來量產的首款車型將會是一款20萬左右的SUV車型,大家覺得怎麼樣?

新能源汽車騙補貼披露 金龍汽車罰款近8億

本月財政部向工信部抄送了《財政部行政處罰事項告知書》,確認蘇州金龍公司申報2015年度中央財政補貼資金的新能源汽車中,有1683輛車截至2015年底仍未完工,但在2015年卻提前辦理了機動車行駛證。涉及補助資金5.19億元。根據相關法規,工信部將責令蘇州金龍公司停止生產和銷售問題車型,暫停蘇州金龍公司申報新能源汽車推廣應用推薦車型資質,並將問題車型從《新能源汽車推廣應用推薦車型目錄》予以剔除,進行為期6個月整改,整改完成后,工信部將對整改情況進行驗收。

最終追回罰款達7.78億,其實不止金龍汽車,現在國內93家新能源汽車企業中有72家存在有騙補貼的行為,這一次的強力打壓會不會對以後的新能源電動車發展有所改進呢?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

軸距2.7米,這款東風旗下新轎車有叫板朗逸的實力!

先賣個關子~。

廣州車展是最近汽車圈最大的事了,作為一個時刻緊跟時事的美編,我也去看了廣州車展,除了起亞站台的LOL人物cos我比較喜歡之外,還有一款車型也吸引了我,是什麼車型呢?它就是。。。先賣個關子~

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

【其他文章推薦】

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

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

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

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

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

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

Redmi AirDots 3 真無線耳機全新繽紛配色驚喜登場:30小時超長續航、藍牙 5.2 、升級圈鐵雙單元提升音質表現

近年真無線藍牙耳機已經逐漸成為民眾聆聽音樂的選擇,在 Redmi K40 系列旗艦新機發表會除了 RedmiBook Pro 筆電和 Redmi MAX 86″ 超大螢幕電視,也推出全新一代的 Redmi AirDots 3 真無線藍牙耳機。外觀方面, Redmi AirDots 3 配色更加繽紛,擁有 30 小時最長續航、系列首次採用圈鐵雙單元大幅提升音質表現,藍牙 5.2 改善畫面不同步、卡頓等干擾問題讓連線更穩定。

Redmi AirDots 3 真無線耳機全新繽紛配色驚喜登場:30小時超長續航、藍牙 5.2 、升級圈鐵雙單元提升音質表現

過去幾代 Redmi AirDots 系列真無線藍牙耳機(台灣命名為小米 EarBuds 超值版),以平易近人的價格、超高的 CP 值,成為許多預算考量消費者在真無線耳機的首選之一。在 Redmi K40 系列新機發表會的最後,也悄悄推出了全新一代的新品 Redmi AirDots 3  。

外觀方面, Redmi AirDots 3 推出藍色、粉色和白色三款全新繽紛配色:

Redmi AirDots 3 也是 AirDots 系列首次搭載圈鐵雙單元(高頻動鐵+低拼動圈)的耳機,兼具低音聲場及低音細節,高中低頻都能還原均衡且飽滿的音色:

搭載全新 Qualcomm 3040 晶片,同時升級為藍牙 5.2 技術,有效改善過去影音不同步、卡頓干擾等問題,讓聲音傳輸更快、更穩。另外, Redmi AirDots 3 也支持 aptX Adaptive 音頻解碼技術,兼具高解析度音質與低延遲的優勢,無論追劇聽歌、娛樂遊戲都帶來更流暢的體驗:

續航方面, Redmi AirDots 3 單只耳機充滿電可使用約 7 小時,搭配內建 600mAh 充電盒最長續航可達 30 小時:

Redmi AirDots 3 支持智慧感應檢測,採用內建紅外線光學感應器可即時幾冊耳機佩戴狀態,入耳瞬間即可感應,摘下耳機自動暫停播放。

Redmi AirDots 3 在體驗上也有明顯升級,此次將過去實體按鍵操控改為觸控操作,解決耳機外觀按鈕設計的突兀、減緩案件壓迫感。使用者可透過輕觸右側耳機兩下暫停/播放音樂、輕觸左耳兩下喚醒語音助理,輕觸多功能鍵接聽/掛斷電話:

Redmi AirDots 3 支持語音助理,可透過輕觸耳機喚醒:

搭配支持 MIUI 螢幕彈窗連接功能的智慧型手機,只要打開耳機盒蓋在手機端螢幕即可彈出連接畫面進行連接,同時也會在手機畫面顯示耳機、充電盒的電量資訊:

其他機身細節部分, Redmi AirDots 3 採用 USB Type-C 充電接口,充電盒具備一鍵連接按鈕:

在 Redmi AirDots 3 充電盒外配備電量指示燈,用戶能根據亮燈情況瞭解充電盒剩餘電量,耳機本體也支持 IPX4 等級生活防潑水:

售價方面, Redmi AirDots 3 真無線藍牙耳機建議售價為人民幣 199 元(約合新台幣 860 元)。

圖片/消息來源:小米官網(中國)

延伸閱讀:
Redmi K40 系列正式發表: K40/K40 Pro/K40 Pro+ 三旗艦全系列搭載高通 8 系列旗艦處理器,售價約 8,645 元起

Redmi MAX 86 超大螢幕電視發表:86 吋超大螢幕 4KHDR ,售價只要約 34,596 元

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

微軟除了讓 Remix 3D 網站被消失,還準備在 21H2 版系統中移除「3D 物件」資料夾

2017 年 4 月,微軟推出了 Windows 10 的新版本創作者更新。在這一版更新當中,微軟為這個新世代系統帶來了一些 3D 創作相關應用,像是 Remix 3D 網站與 Paint 3D (小畫家 3D)軟體等。為了幫助用戶管理這些 3D 創作作品,還特地在檔案總管的側欄加入「3D 物件」資料夾。一年後,微軟發現這些 3D 創作軟體或服務似乎相當不受歡迎,直接關了 Remix 3D 網站。最近更是直接對 Windows 10 上的 3D 物件資料夾動刀。讓這個不太受歡迎的快捷資料夾隱藏起來:

▲(圖片來源)

小畫家 3D 雖然還是跟傳統小畫家軟體那樣好用,甚至坊間有不少關於小畫家 3D 的教學功能介紹,不過其他的 3D 軟體或服務可沒那麼幸運。很多不怎麼接觸 3D 創作功能的人對於檔案管理介面中的 3D 物件資料夾相當感冒。筆者在網路上也看到國內外有許多人提供了修改機碼來隱藏這個資料夾的教學。但修改機碼終究還是有些風險,為了一個資料夾,未必要這麼大動干戈。

功能資料夾既要刪,又不能真刪

微軟在測試新系統時顯然也知道網路上的這股風氣。畢竟玩 3D 設計、3D 列印之類的創作者雖然不少,但不是每個 Windows 10 用戶都需要這些功能。於是微軟開始把「3D 物件」這個資料夾的移除,或者說隱藏,做為下一版系統的一項變更項目。不過「小畫家 3D」這個軟體仍然會預裝在下一版系統中。如果玩家連小畫家 3D 都覺得不需要,可以透過 WIndows Store 來移除:

▲關於移除 3D 物件資料夾的部分,國外就有高手製作好移除或還原用的機碼註冊檔(圖片來源)

目前的 Windows 10 Insider Preview Build 21322 就已經將 3D 物件資料夾從檔案總管的側欄移除,這意味著打開檔案總管時,不會再看到這個資料夾卡著一個位子在介面上。不過,這不代表 3D 物件資料夾徹底人間蒸發,你還是可以透過 C:\Users\用戶名稱\3D Object 這個路徑找到這個資料夾。讓有需要的人自己釘選在明顯的地方,例如工作列、桌面或檔案總管的側欄:

▲微軟要讓它消失,卻又不能真正消失,只好用這種變戲法的形式讓它退居幕後(圖片來源)

換句話說,微軟的解決方案有些消極,變個戲法讓資料夾不在表面出現,卻還是能在深層的地方發現他的存在。相信微軟還是多少要顧及仍有需要利用 3D 物件資料夾的用戶意見。這點算是折衷的保留了雙方的需求,所能呈現的最好成果。如果微軟 Windows 10 21H2 版本沒有延遲太久的話,預計年底的時候就會正式亮相了。

消息來源

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

國外實測新款 Surface Pro X 的 SQ2 處理器效能幾乎跟 SQ1 一樣

微軟在去年十月時正式更新旗下 Surface Pro X 平版電腦,處理器升級至 SQ2,一樣是高通 ARM 架構,不過當時微軟並沒有特別說明這顆處理器的效能表現,而最近終於有外媒實測到實機,但讓人意外的是,跑分結果竟然跟 SQ1 差不多,也就是新款 Surface Pro X 效能幾乎沒有什麼提升。

新款 Surface Pro X 的 SQ2 處理器效能幾乎跟 SQ1 一樣

稍早一間德國媒體網站 Dr. Windows 分享 Surface Pro X 2020 年版的實測報告,共使用三款跑分軟體進行測試,並與舊款 Surface Pro X 比較,結果如下。

首先是 Jetstream 2 瀏覽器測試工具:

  • 搭載 SQ1 處理器的 Surface Pro X 2019 獲得 89,614
  • 搭載 SQ2 處理器的 Surface Pro X 2020 獲得 90,664

安兔兔測試工具:

  • 搭載 SQ1 處理器的 Surface Pro X 2019 獲得 286,740
  • 搭載 SQ2 處理器的 Surface Pro X 2020 獲得 286,271

Geekbench 4 測試工具:

  • 搭載 SQ1 處理器的 Surface Pro X 2019 單核心獲得 3,530、多核心獲得 11,927
  • 搭載 SQ2 處理器的 Surface Pro X 2020 單核心獲得 3,627、多核心獲得 12,042

三個測試結果中,Jetstream 2 與 Geekbench 4 雖然新款確實比較高,但也只有多一點點,安兔兔反而變低。整體來看,幾乎可以說搭載 SQ2 處理器的 Surface Pro X 2020 效能並沒有提升。

另外最近微軟官方的韌體更新中,也都合併支援 SQ1 與 SQ2 處理器:

不確定為何這兩顆效能會這麼接近,但也顯示著,對於注重效能的朋友來說,舊款 Surface Pro X 或許是 CP 值更高的選擇。

當然,除了效能,Surface Pro X 2020 還是有地方提升,像續航力官方就承諾可達到 15 小時,比舊款多 2 小時。

無論如何,有在關注新款 Surface Pro X 2020 的人,建議等更多實測報告現身再做決定。

資料來源:Dr. Windows

微軟最新的廣告,直白地跟你說 Surface Pro 7 就是比 MacBook Pro 好

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

這幾款10萬級車型投訴率最低,最可靠!竟然沒有日系

吉利遠景指導價:5。39-6。79萬推薦車型:2017款1。5L自動幸福版10月份銷量:15721輛10月份投訴量:4單吉利汽車無論是轎車還是SUV,都對汽車銷量市場貢獻了不少的成績,SUV有博越,遠景SUV和帝豪GS,轎車則有博瑞,帝豪GL和今天介紹的這款遠景,在上個月賣出了1。

根據中國汽車工業協會提供的數據表示,在10月份全國轎車的銷售量達到了117.05萬輛,車主朋友們對轎車的有效投訴達到2538宗,在投訴案例中,大多數朋友投訴的內容為發動機功率不足,車內異味以及變速箱異響的問題。而今天想為大家介紹的這幾款,確實是經過了後市場的考驗,投訴量非常少的質量可靠性轎車,不妨參考參考。

雪佛蘭科沃茲

指導價:7.99-10.99萬

推薦車型:2016款 1.5L自動欣悅版

10月份銷量:13836輛

10月份投訴量:0

在科沃茲還沒正式亮相之前,就有不少的粉絲在後台問這款車如何,而該車推出至今已經有幾個月,論質量論實力,在10萬元代步家轎中,一直保持着不錯的成績,這次通過數據來看,在9月份10月份後市場依然還沒收到科沃茲車主的投訴,這其實就是一個很好的證明了。

科沃茲外觀採用雪佛蘭家族式的雙層前格柵,燈組內部有點綴鍍鉻飾條和全新的雙L形LED光帶式日間行車燈,全系標配,內飾採用傳統飛翼式座艙設計,同時座椅提供更加出色的側向支撐和包裹性。動力上全系採用1.5L自然吸氣發動機,搭配5速手動和6速手自一體變速器,推薦的這個自動欣悅版,標配胎壓監測,ESp車身穩定,上坡輔助,天窗以及發動機啟停裝置,應該有的配置沒有差。

全力加速時轉速爬升得比較慢,大約在2500轉左右,爆發力只能說一般般,不過在加速的時候降擋相當果斷,方向盤力度沉穩,平時在道路上做併線動作也蠻幹脆利索,但是整體的駕駛感受上還是比本田鋒范差點,百公里平均油耗7L。

東風悅達起亞K2

指導價: 7.29-10.39萬

推薦車型: 2017款 三廂 1.4L AT GLS

10月份銷量:16882

10月份投訴量:3

在網絡上曾傳聞着這樣一篇文章“十大最不安全車輛”,在這個由美國評定出來的車型當中,就屬起亞K2首當其沖,雖然在這個等級上舊款起亞K2由於自身問題慘入榜單,但是相信最近剛上市的全新2017款K2能夠帶給大家不一樣的感覺。

全新的起亞K2前臉變化很大,整個車頭設計都讓人煥然一新,虎嘯式格柵面積更小,側面也比舊款顯得更加修長,沒有那麼笨重,車尾則加入了貫穿式尾燈,識別程度非常之高。內飾風格也是全新設計,中控彩色屏幕支持蘋果Carplay和百度Carlife功能,還有倒車雷達和影像,這可能是最實用的功能了,其他像ESp車身穩定和胎壓監測上坡輔助等,都變成後期加裝了,選起亞K2,確實會面臨這個尷尬的問題。

動力上提供1.4L和1.6L兩種排量的自然吸氣發動機,傳統的4AT變速器遭捨棄,升級為6速手自一體和6速手動擋變速器,試駕感受上油門踏板初段有些遲緩,需要捨得給油地深踩才會感覺到動力的提升,懸架偏向於舒適,但過減速帶感覺還是比較明顯。

吉利遠景

指導價:5.39-6.79萬

推薦車型:2017款1.5L自動幸福版

10月份銷量:15721輛

10月份投訴量:4單

吉利汽車無論是轎車還是SUV,都對汽車銷量市場貢獻了不少的成績,SUV有博越,遠景SUV和帝豪GS,轎車則有博瑞,帝豪GL和今天介紹的這款遠景,在上個月賣出了1.5多萬輛的成績,只接到4單車主用戶的投訴,這個成績還是蠻可以的。

在外觀上,2017款對前臉進氣格柵造型做了小調整,統一了家族式風格的“回”字形造型,更加時尚,內飾設計變化不大,這次該款主要是配置升級了一點,配置雖然沒有ESp車身穩定(本田飛度大部分車型也沒有),但其他配置如后倒車雷達及影像,前後攝像頭,定速巡航等功能,在這款6.69萬的1.5L自動擋版本上看到,就已經非常奇迹了,感覺賺到了。

該車不僅配備1.5L自然吸氣,如果對動力需求更大可以入手1.3T渦輪增壓版本,不過該版本沒有匹配自動變速器,比較可惜,整體車內儲物空間還是蠻多的,這輛車,價格便宜是最大的賣點,且賣出去后,用戶投訴量也非常少,可以考慮。

大眾捷達

指導價:7.99-12.08萬

推薦車型:2015款 質惠版1.6L自動舒適型

10月份銷量:29637輛

10月份投訴量:6單

捷達車型相信大家都相當熟悉了,它在我國存在的年份已經相當之久,雖然很少在網上看到它的宣傳廣告,但它一直在我國的銷量都非常好,10月份總共賣出的車輛就直逼3萬大軍,收到的投訴量也只有6單,如果真要將這4台車型來列個投訴銷量比的話,無疑大眾捷達得到的分數是最高的。

2015款的外觀還是比較圓潤中庸,內飾風格還是那麼簡樸老成,大眾標準內飾設計風格,沒有多大可以描述的地方,針對捷達來說,有着多種發動機排量可以選擇,有着1.4L和1.6L的自然吸氣版本,同時也有1.4T渦輪增壓的大動力版本,根據不同排量的發動機,匹配5速手動,6速自動和7速雙離合變速器,配置上基本不標配ESp,大部分為選裝情況。

可能大家買捷達,就是衝著德系技術,且捷達車耐用好開的特性吧,配置其實並不是絕大多數人考慮的地方,品牌效應早已超過了其他,在試駕過程中深加速時發動機噪音大,底盤比較穩,質感不錯,如今2017款新捷達已經亮相,期待上市后的市場反應。

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

【其他文章推薦】

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

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

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

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

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

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

從“中英文思維回譯法”看中英思維差異

1、中英文在表達意思的方式上的不同 

中英文表達的差異很重要一部分體現在思維上。例如中文說:

“不知道怎麼回事,我筆記本連不上網了”

地道的英文不是:

My laptop can’t connect to the internet for no reason.

而是:

I couldn’t access the internet from my laptop for some reason.

這個句子至少體現了以下幾處中英文思維的不同:

  • 中文用“我的電腦”做主語,英語用“我”做主語。
  • “上網”是 access the internet 而不是 connect to the internet.
  • 不知道怎麼回事是 for some reason 而不是 for no reason.
  • 中文先說“不知道怎麼回事”,英文後說 for some reason.

 

2、中英文思維的差異

作為跨文化交流的翻譯不僅只是兩種語言的簡單轉換,也是兩種思維方式之間的轉換。 拿英漢兩種語言來說.由於文化背景的差異等諸多因素,這兩種語言的思維方式也不盡相同,概括來說主要就是:

  • 句子重心的差異
  • 語態的差異
  • 否定視角的差異

0x1:句子重心的差異

句子重心包括兩個層面:

  • 一個是語義重心
  • 另一個是結構重心

英語和漢語的語義重點基本沒有大的區別,這兩種語言句子重心的差異主要體現在結構重心的差異上。

西方人說話喜歡直截了當、開門見山,他們在表達思想時喜歡先安排主要內容,然後再藉助各種連接手段補充其他信息,因而英語句子的結構重心通常在前,句子常常頭短尾長。

而中國人則喜歡水到渠成、漸入佳境。所以他們在表達思想時喜歡先對所述的事物進行鋪墊和渲染,然後再點出中心意思,因而漢語句子的結構重心往往靠後,句子頭長尾短。

我們來看下面的例句:

我們再來看一個例句:

這句話的結構重心為後半句“許多小船載着各種雜貨快速向客輪駛來”。通過上面的分析,這句話在翻譯時應首先將結構重心置前,先說主要內容:

0x2:語態的差異

雖然英漢兩種語言在句式上都有主動和被動之分,但表現形式和使用場合卻不盡相同。

  • 英語中被動語態的使用範圍非常廣,除了大量的及物動詞使用被動語態外,不少相當於及物動詞的短語也可以使用被動語態。
  • 相比之下,漢語中被動語態的使用就低得多了,大多數情況下漢語都使用主動形式來表達被動意義。

以“He is loved by all of us.”這句話為例,這句話在英語中司空見慣,而如果要譯成“他被我們大家喜歡着”就很彆扭,必須要使用主動語態,譯成“我們大家都喜歡他”才通順。

我們來看下面的例句:

 

被動語態能使句子的核心詞“an interesting discovery”更為突出。

我們再來看一個例句:

中文原句是主動語態,但由於“開挖隧道”和“派狗到雪地里”這兩個動作的發出者在中文里並沒有出現,所以在譯成英語時就只能使用被動語態了。

中文中有大量的無主句,在將這些句子譯成英文時都可以使用被動語態。

0x3:否定視角的差異

英語的否定方式非常豐富,大體可分為:

  • 顯形否定:通常是藉助否定詞或,含否定意義的句型來表達(如never、unless、hardly、rather than、in the absence of、too …. to …)
  • 隱形否定:沒有明顯的否定詞,其形式上是否定的,它的否定含義要通過上下文或語境來推定

與英語比起來,漢語的否定結構就單一多了,一般是在相應的詞語前面加上“不”、“沒有”等進行“詞的否定”。

所以,在漢譯英的過程中,對於漢語中相對單一的否定結構,英文中可以有多種不同的表達方式。

我們來看下面的例句:

可以看到,譯文採用了隱形否定的形式來表達。

我們再來看一個例句:

 

中文原文是肯定句,而譯文中使用了否定結構“never fail to”不僅表達了肯定的意思,還顯得更為精彩,更有力量。

3、中英文回譯法

在翻譯練習中,有一種非常有效翻譯方法叫“回譯法”,也就是對譯文進行再次翻譯后與原文進行對照,在對比中找到差異所在。 概括來說就是:

  • 從地道英語直譯成中文
  • 由直譯的中文再譯成地道的中文
  • 仔細分析記錄地道英語和地道中文的區別
  • 由地道中文再回譯成地道英文

0x1:英譯中 – 從 A 到 B

我們先徹底理解英文原文的意思,然後嘗試着翻譯成中文。如果覺得直譯很彆扭的話,那就說明這裡有值得一學的英語思維! 在這一步,我們要做到讓中文譯文符合英文原文的表述,盡量直譯,基本通順即可。 舉個例子: 一位商務人士因為電腦有問題連不上網,沒能及時回復重要郵件,等修好了,他趕緊給別人發郵件解釋,郵件開頭是這麼說的:  

這句話直譯過來就是:

我很抱歉沒能更早回復你,出於某些原因,我用筆記本電腦無法連上網。

0x2:重建中文 – 從 B 到 C

這一步,我們要引入中文思維,展開想象:

在真實的中文語境中,相同的場景中,人們會怎麼說這句話?

這一步需要你完全拋棄英文原文的影響,去想中國人真正會使用出來的中文,不能有任何的翻譯痕迹。你可以假象在同樣的場景中,你自己會如何表達。例如:

不好意思啊,這麼晚回復你,我的本子一直連不上網,不知道怎麼回事。

到這一步我們發現,經過重建的中文,已經和之前的譯文在表達順序和結構上有明顯區別了。

0x3:連接通過 – 找到 A 和 C 之間的關聯

這一步也叫“埋鈎子”,就是說,埋下一些幫助記憶的“小鈎子”,把兩種思維掛上,讓自己以後能回想起來。

我們要觀察的內容主要是三方面:

  • 多了什麼?
  • 少了什麼?
  • 改了什麼?

比如:

  • 句子結構是怎麼變的?
  • 哪些詞被捨棄了?
  • 哪些表達式被徹底替換了?
  • 為什麼有些信息乾脆就不說了?
  • 有些信息反而要被加上去?

以這句話為例,我會注意到,

  • 我們中國人通常說的“這麼晚回復你”,英文的表達思路不一定用so late,還可以用not sooner,即否定句式。
  • 連上網,這句話里用的是access這個詞,而不是我們更熟悉的connect to。
  • 句子的主語也變了,一般中文會說“我的筆記本連不上網”,主語是筆記本,但是英文原文的說法是“i coudn’t access the internet”,主要是 I,也就是我。是我沒法通過本子來連網,而不是說本子連不上網。

0x4:思維逆轉 – 把 C 譯回 A

最後一步,我們要把整個過程逆推回去,在只看到 C 的情況下,回想起 A 是怎麼說的。如果你能做到準確完成回譯,說明你已經成功地建立了通道,從中文思維跳到了英文思維。

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

Spring 源碼學習 – 單例bean的實例化過程

本文作者:geek,一個聰明好學的同事

1. 簡介

開發中我們常用@Commpont,@Service,@Resource等註解或者配置xml去聲明一個類,使其成為spring容器中的bean,以下我將用從源碼角度看以AnnotationConfigApplicationContext為例看spring如何把帶有註解的類生成spring中bean。

2. 示例代碼

public class TestContext {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		SingleBean singleBean = context.getBean(SingleBean.class);
		System.out.println("<=====>"+singleBean.getTestStr());
	}
}
@ComponentScan("com.geek")
public class AppConfig {
}
@Component
public class SingleBean {
	private String testStr = "testStr";

	public String getTestStr() {
		return testStr;
	}
}

注意:以上代碼僅需要引入spring-context依賴即可。

3. 源碼分析

​ 上面的demo在調用AnnotationConfigApplicationContext構造函數的時候,AppConfig類會被註冊到AnnotatedBeanDefinitionReader,由這個reader把AppConfig解釋為beanDefination,從而被spring獲取到要實例化的類信息,以下為bean生產的源碼及其註釋。(源碼基於springFramework 5.1.X)

3.1 創建入口

​ 單例bean的創建的入口為DefaultListableBeanFactory.java#preInstantiateSingletons,下面源碼可見創建的bean條件為非抽象,非@LazyInit註解,Scope為singleTon(默認為singleTon)。

@Override
	public void preInstantiateSingletons() throws BeansException {
		if (logger.isTraceEnabled()) {
			logger.trace("Pre-instantiating singletons in " + this);
		}
		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		//所有可能需要去實例化的class(lazy,scope)
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			/**
			 * 非抽象,非懶初始化,單例bean
			 */
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				//非工廠bean實例化
				else {
					getBean(beanName);
				}
			}
		}
		/**
		 * 實例化完成后觸發實現了SmartInitializingSingleton方法的bean
		 * 的afterSingletonsInstantiated方法
 		 */
		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

3.2 創建前doGetBean代碼邏輯

getBean方法進來后便是直接調用doGetBean,doGetBean執行的源碼解釋如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		/**
		 * 獲取beanName
		 * 1,去掉factortoryBean前綴&
		 * 2,帶有別名的bean轉換為原來名字
		 */
		final String beanName = transformedBeanName(name);
		Object bean;
		// Eagerly check singleton cache for manually registered singletons.
		/**
		 * 從DefaultSingletonBeanRegistry的singletonObjects
		 * (spring內部用來緩存單例bean的currentHashMap)檢查是否存在該bean,不存在則創建
		 * 涉及單例模式下的循環依賴解決
		 */
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			/**
			 *獲取給定bean實例的對象,如果是Factory Bean,
			 * 則可以是bean實例本身或其創建的對象。
			 */
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
			// Fail if we're already creating this bean instance:
			// We're assembly within a circular reference.
			/**
			 * 原型模式下的bean存在循環依賴則會拋異常
			 */
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}
			// Check if bean definition exists in this factory.
			/**
			 * 找不到則從父容器中查找
			 */
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}
			try {
				/**
				 * 父容器中也找不到該bean,則需要重新實例化
				 * 1,獲取要實例化bean的beanDefinition,
				 * 2,檢查bean的實例化需要依賴的其他bean
				 */
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						/**
						 * 若給定的依賴 bean 已經註冊為依賴給定的bean
						 */
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						registerDependentBean(dep, beanName);
						try {
							/**
							 * 遞歸調用getBean,優先創建依賴的bean
							 */
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}
				// Create bean instance.
				if (mbd.isSingleton()) {
					/**
					 * 通過調用DefaultSingletonBeanRegistry的getSingleton,從而調用核心方法createBean創建
					 */
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							// Explicitly remove instance from singleton cache: It might have been put there
							// eagerly by the creation process, to allow for circular reference resolution.
							// Also remove any beans that received a temporary reference to the bean.
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					String scopeName = mbd.getScope();
					final Scope scope = this.scopes.get(scopeName);
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, () -> {
							beforePrototypeCreation(beanName);
							try {
								return createBean(beanName, mbd, args);
							}
							finally {
								afterPrototypeCreation(beanName);
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' is not active for the current thread; consider " +
								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
								ex);
					}
				}
			}
			catch (BeansException ex) {
				cleanupAfterBeanCreationFailure(beanName);
				throw ex;
			}
		}
		/**
		 * 檢查需要的bean類型是否符合
		 */
		// Check if required type matches the type of the actual bean instance.
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				if (logger.isTraceEnabled()) {
					logger.trace("Failed to convert bean '" + name + "' to required type '" +
							ClassUtils.getQualifiedName(requiredType) + "'", ex);
				}
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

doGetBean的操作流程如下:

1,執行transformedBeanName方法轉換beanName

​ 傳遞的參數可能是bean的alias或者為FactoryBean,transformedBeanName執行的操作:(1)若傳進來的FactoryBean(FactoryBean以&作為前綴標記),去掉&修飾符。(2)經過(1)的處理后,有alias的bean則從aliasMap中獲取bean的原始beanName。

2,從容器的緩存中獲取bean

​ getSingleton先從spring三級緩存中的第一級singletonObjects(Map結構)中獲取,若不存在,則檢查該bean是否正在創建isSingletonCurrentlyInCreation(beanName)) ,正在創建的bean會從二級緩存earlySingletonObjects(Map結構)獲取。獲取到緩存的bean後會調用AbstractBeanFactory#getObjectForBeanInstance轉換bean的實例本身返回。因為從緩存中拿到的可能是factoryBean,所以getObjectForBeanInstance需要把是通過從緩存factoryBeanObjectCache獲取或通過factory.getObject()獲得相應的bean返回。

3,bean實例化前檢查

​ (1)先檢查是否原型模式下的bean是否存在循環依賴,是則會拋異常。

​ (2)檢查父類工廠(parentBeanFactory)是否存在,存在則從parentBeanFactory中遞歸調用doGetBean。

​ (3)獲取改bean的beanDefinition,檢查該bean實例化過程中是否涉及依賴了其他的bean,若是則遞歸調用getBean,優先創建依賴的bean。(涉及單例下的循環以來解決,下篇文章詳細介紹)。

​ (4)對創建bean代碼加synchronized和執行beforeSingletonCreation(beanName)前置處理。

3.3 創建前createBean邏輯

​ 經過前面的doGetBean的一輪檢查與準備后,便在AbstractAutowireCapableBeanFactory#createBean中開始bean的創建。

/**
	 * Central method of this class: creates a bean instance,
	 * populates the bean instance, applies post-processors, etc.
	 * 解析指定 BeanDefinition 的 class
	 * 處理 override 屬性
	 * 實例化的前置處理
	 * 創建 bean
	 * @see #doCreateBean
	 */
	@Override
	protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		if (logger.isTraceEnabled()) {
			logger.trace("Creating instance of bean '" + beanName + "'");
		}
		RootBeanDefinition mbdToUse = mbd;

		// Make sure bean class is actually resolved at this point, and
		// clone the bean definition in case of a dynamically resolved Class
		// which cannot be stored in the shared merged bean definition.
		/**
		 * 解釋bean的class,看beanDefinition是否有class,否則load class
		 */
		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}
		/**
		 * 對bean不存在lookup-method 和 replace-method
		 * 標記其方法的overloaded為false
		 */
		// Prepare method overrides.
		try {
			mbdToUse.prepareMethodOverrides();
		}
		catch (BeanDefinitionValidationException ex) {
			throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
					beanName, "Validation of method overrides failed", ex);
		}

		try {
			/**
			 * 調用實現實現BeanPostProcessor的bean後置處理生成代理對象,
			 * 有代理對象則直接返回代理對象
			 */
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		catch (Throwable ex) {
			throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
					"BeanPostProcessor before instantiation of bean failed", ex);
		}

		try {
			/**
			 * 無需代理的bean實例化
			 */
			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (logger.isTraceEnabled()) {
				logger.trace("Finished creating instance of bean '" + beanName + "'");
			}
			return beanInstance;
		}
		catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
			// A previously detected exception with proper bean creation context already,
			// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
		}
	}

createBean的操作流程如下:

1,resolveBeanClass

​ 解釋beanDefinition的class,並且保存在beanDefinition中。

2,prepareMethodOverrides

​ 處理bean中的lookup-method (在單例bean用 @Lookup註解標記的方法,註解的方法返回的對象是原型)和 replace-method( 標記的方法,標記bean中A的方法被實現被另外一個實現MethodReplacer接口的B方法替代)。

3,resolveBeforeInstantiation

​ 調用實現實現BeanPostProcessor的bean後置處理生成代理對象,有代理對象則直接返回代理對象。(spring AOP則是基於此處實現)

3.4 bean的真正實例化createBeanInstance

​ 在AbstractAutowireCapableBeanFactory#createBeanInstance中,真正創建bean,源碼及註釋如下:

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}
		/**
		 * 通過提供supplier回調方法創建
		 */
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		/**
		 * 通過工廠方法創建 bean 實例,可以是靜態工廠方法或者實例工廠
		 */
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}
		// Shortcut when re-creating the same bean...
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				/**
				 * 查找已經bean已經緩存解析的構造函數或者工廠方法
				 */
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		/**
		 * 已經有緩存的構造函數或者工廠方法,直接實例化
		 */
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}
		/**
		 * 通過實現BeanPostProcessor的代理類autowired的構造函數實例化
		 */
		// Candidate constructors for autowiring?
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}
		/**
		 * 通過本身帶有autowired的構造函數實例化,通過調用反射newInstance實現
		 */
		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}
		/**
		 * 無參構造函數實例化,通過調用反射newInstance實現
		 */
		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}

由上述源碼可以看出,實例化bean操作流程如下:

1,如果存在 Supplier 回調,則通過提供supplier回調方法創建,如以下方式定義的bean:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Spring5Application.class)
public class BeanRegistrationTest {
    @Autowired
    private GenericWebApplicationContext context;
    
    context.registerBean(A.class, () -> new A());
}

2,如果存在工廠方法,則通過工廠方法創建 bean 實例,可以是靜態工廠方法或者實例工廠,如以下方式定義的bean:

public class AFactory implements FactoryBean<A> {
	@Override
	public A getObject() throws Exception {
		return new A();
	}
	@Override
	public Class<?> getObjectType() {
		return A.class;
	}
}
//或:
@Configuration
public class BeanConfigration {
	@Bean
	public A a(){
		return new A();
	}

}

3,已經有緩存的構造函數或者工廠方法,直接實例化。

4,以上三點都不存在,則使用帶參構造函數與無參構造函數實例化。如以下方式定義的bean:

@Commponet
public class A{}

4.總結

​ spring單例bean的實例化流程大概就是這樣,很多細節地方,包括循環依賴處理,bean屬性填充等細節點下一章介紹。

參考

  • 《Spring 源碼深度解析》- 郝佳
  • 《死磕spring源碼》-chenssy

查看更多文章關注公眾號:好奇心森林

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

akka-typed(4) – EventSourcedBehavior in action

  前面提到過,akka-typed中較重要的改變是加入了EventSourcedBehavior。也就是說增加了一種專門負責EventSource模式的actor, 最終和其它種類的actor一道可以完美實現CQRS。新的actor,我還是把它稱為persistentActor,還是一種能維護和維持運行狀態的actor。即,actor內部狀態可以存放在數據庫里,然後通過一組功能函數來提供對狀態的處理轉變,即持續化處理persistence。當然作為一種具備EventSourcedBehavior的actor, 普遍應有的actor屬性、方法、消息處理協議、監管什麼的都還必須存在。在這篇討論里我們就通過案例和源碼來說明一下EventSourcedBehavior是如何維護內部狀態及作為一種actor又應該怎麼去使用它。

我們把上一篇討論里購物車的例子拿來用,再增加一些消息回復response機制,主要是彙報購物車狀態:

object ItemInfo { case class Item(name: String, price: Double) } object MyCart { import ItemInfo._ sealed trait Command sealed trait Event extends CborSerializable sealed trait Response //commands
  case class AddItem(item: Item) extends Command case object PayCart extends Command case class CountItems(replyTo: ActorRef[Response]) extends Command //event
  case class ItemAdded(item: Item) extends Event case object CartPaid extends Event //state
  case class CartLoad(load: List[Item] = Nil) //response
  case class PickedItems(items: List[Item]) extends Response case object CartEmpty extends Response val commandHandler: (CartLoad, Command) => Effect[Event,CartLoad] = { (state, cmd) => cmd match { case AddItem(item) => Effect.persist(ItemAdded(item)) case PayCart => Effect.persist(CartPaid) case CountItems(replyTo) => Effect.none.thenRun { cart => cart.load match { case Nil => replyTo ! CartEmpty case listOfItems => replyTo ! PickedItems(listOfItems) } } } } val eventHandler: (CartLoad,Event) => CartLoad = { (state,evt) => evt match { case ItemAdded(item) => state.copy(load = item :: state.load) case CartPaid => state.copy(load = Nil) } } def apply(): Behavior[Command] = EventSourcedBehavior[Command,Event,CartLoad]( persistenceId = PersistenceId("10","1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ) } object Shopper { import ItemInfo._ sealed trait Command extends CborSerializable case class GetItem(item: Item) extends Command case object Settle extends Command case object GetCount extends Command case class WrappedResponse(res: MyCart.Response) extends Command def apply(): Behavior[Command] = Behaviors.setup[Command] { ctx => val shoppingCart = ctx.spawn(MyCart(), "shopping-cart") val cartRef: ActorRef[MyCart.Response] = ctx.messageAdapter(WrappedResponse) Behaviors.receiveMessage { msg => msg match { case GetItem(item) => shoppingCart ! MyCart.AddItem(item) case Settle => shoppingCart ! MyCart.PayCart case GetCount => shoppingCart ! MyCart.CountItems(cartRef) case WrappedResponse(res) => res match { case MyCart.PickedItems(items) => ctx.log.info("**************Current Items in Cart: {}*************", items) case MyCart.CartEmpty => ctx.log.info("**************shopping cart is empty!***************") } } Behaviors.same } } } object ShoppingCart extends App { import ItemInfo._ val shopper = ActorSystem(Shopper(),"shopper") shopper ! Shopper.GetItem(Item("banana",11.20)) shopper ! Shopper.GetItem(Item("watermelon",4.70)) shopper ! Shopper.GetCount shopper ! Shopper.Settle shopper ! Shopper.GetCount scala.io.StdIn.readLine() shopper.terminate() }

實際上EventSourcedBehavior里還嵌入了回復機制,完成一項Command處理后必須回復指令方,否則程序無法通過編譯。如下:

private def withdraw(acc: OpenedAccount, cmd: Withdraw): ReplyEffect[Event, Account] = { if (acc.canWithdraw(cmd.amount)) Effect.persist(Withdrawn(cmd.amount)).thenReply(cmd.replyTo)(_ => Confirmed) else Effect.reply(cmd.replyTo)(Rejected(s"Insufficient balance ${acc.balance} to be able to withdraw ${cmd.amount}")) }

不過這個回復機制是一種副作用。即,串連在Effect產生之後立即實施。這個動作是在eventHandler之前。在這個時段無法回復最新的狀態。

說到side-effect, 如Effect.persist().thenRun(produceSideEffect): 當成功持續化event后可以安心進行一些其它的操作。例如,當影響庫存數的event被persist后可以馬上從賬上扣減庫存。

在上面這個ShoppingCart例子里我們沒有發現狀態轉換代碼如Behaviors.same。這隻能是EventSourcedBehavior屬於更高層次的Behavior,狀態轉換已經嵌入在eventHandler里了,還記着這個函數的款式吧  (State,Event) => State, 這個State就是狀態了。

Events persist在journal里,如果persist操作中journal出現異常,EventSourcedBehavior自備了安全監管策略,如下:

  def apply(): Behavior[Command] = EventSourcedBehavior[Command,Event,CartLoad]( persistenceId = PersistenceId("10","1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds))

值得注意的是:這個策略只適用於onPersistFailure(),從外部用Behaviors.supervisor()包嵌是無法實現處理PersistFailure效果的。但整個actor還是需要一種Backoff策略,因為在EventSourcedBehavior內部commandHandler,eventHandler里可能也會涉及一些數據庫操作。在操作失敗后需要某種Backoff重啟策略。那麼我們可以為actor增加監控策略如下:

  def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds)) } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

現在這個MyCart可以說已經是個安全、強韌性的actor了。

既然是一種persistentActor,那麼持久化的管理應該也算是核心功能了。EventSourcedBehavior通過接收信號提供了對持久化過程監控功能,如:

 def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup[Command] { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) ).receiveSignal { case (state, RecoveryCompleted) => ctx.log.info("**************Recovery Completed with state: {}***************",state) case (state, SnapshotCompleted(meta))  => ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr) case (state,RecoveryFailed(err)) => ctx.log.error("recovery failed with: {}",err.getMessage) case (state,SnapshotFailed(meta,err)) => ctx.log.error("snapshoting failed with: {}",err.getMessage) } } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

EventSourcedBehavior.receiveSignal是個偏函數:

  def receiveSignal(signalHandler: PartialFunction[(State, Signal), Unit]): EventSourcedBehavior[Command, Event, State]

下面是一個EventSourcedBehavior Signal 清單:

sealed trait EventSourcedSignal extends Signal @DoNotInherit sealed abstract class RecoveryCompleted extends EventSourcedSignal case object RecoveryCompleted extends RecoveryCompleted { def instance: RecoveryCompleted = this } final case class RecoveryFailed(failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure } final case class SnapshotCompleted(metadata: SnapshotMetadata) extends EventSourcedSignal { def getSnapshotMetadata(): SnapshotMetadata = metadata } final case class SnapshotFailed(metadata: SnapshotMetadata, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getSnapshotMetadata(): SnapshotMetadata = metadata } object SnapshotMetadata { /** * @param persistenceId id of persistent actor from which the snapshot was taken. * @param sequenceNr sequence number at which the snapshot was taken. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown. * in milliseconds from the epoch of 1970-01-01T00:00:00Z. */ def apply(persistenceId: String, sequenceNr: Long, timestamp: Long): SnapshotMetadata =
    new SnapshotMetadata(persistenceId, sequenceNr, timestamp) } /** * Snapshot metadata. * * @param persistenceId id of persistent actor from which the snapshot was taken. * @param sequenceNr sequence number at which the snapshot was taken. * @param timestamp time at which the snapshot was saved, defaults to 0 when unknown. * in milliseconds from the epoch of 1970-01-01T00:00:00Z. */ final class SnapshotMetadata(val persistenceId: String, val sequenceNr: Long, val timestamp: Long) { override def toString: String = s"SnapshotMetadata($persistenceId,$sequenceNr,$timestamp)" } final case class DeleteSnapshotsCompleted(target: DeletionTarget) extends EventSourcedSignal { def getTarget(): DeletionTarget = target } final case class DeleteSnapshotsFailed(target: DeletionTarget, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getTarget(): DeletionTarget = target } final case class DeleteEventsCompleted(toSequenceNr: Long) extends EventSourcedSignal { def getToSequenceNr(): Long = toSequenceNr } final case class DeleteEventsFailed(toSequenceNr: Long, failure: Throwable) extends EventSourcedSignal { def getFailure(): Throwable = failure def getToSequenceNr(): Long = toSequenceNr }

當然,EventSourcedBehavior之所以能具備自我修復能力其中一項是因為它有對持久化的事件重演機制。如果每次啟動都需要對所有歷史事件進行重演的話會很不現實。必須用snapshot來濃縮歷史事件:

  def apply(): Behavior[Command] = Behaviors.supervise( Behaviors.setup[Command] { ctx => EventSourcedBehavior[Command, Event, CartLoad]( persistenceId = PersistenceId("10", "1013"), emptyState = CartLoad(), commandHandler = commandHandler, eventHandler = eventHandler ).onPersistFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) ).receiveSignal { case (state, RecoveryCompleted) => ctx.log.info("**************Recovery Completed with state: {}***************",state) case (state, SnapshotCompleted(meta))  => ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr) case (state,RecoveryFailed(err)) => ctx.log.error("recovery failed with: {}",err.getMessage) case (state,SnapshotFailed(meta,err)) => ctx.log.error("snapshoting failed with: {}",err.getMessage) }.snapshotWhen { case (state,CartPaid,seqnum) => ctx.log.info("*****************snapshot taken at: {} with state: {}",seqnum,state) true
          case (state,event,seqnum) => false }.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 100, keepNSnapshots = 2)) } ).onFailure( SupervisorStrategy .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1) .withMaxRestarts(3) .withResetBackoffAfter(10.seconds) )

下面是本次示範的源碼:

build.sbt

name := "learn-akka-typed"

version := "0.1"

scalaVersion := "2.13.1"
scalacOptions in Compile ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint")
javacOptions in Compile ++= Seq("-Xlint:unchecked", "-Xlint:deprecation")

val AkkaVersion = "2.6.5"
val AkkaPersistenceCassandraVersion = "1.0.0"


libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-cluster-sharding-typed" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-typed" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-query" % AkkaVersion,
  "com.typesafe.akka" %% "akka-serialization-jackson" % AkkaVersion,
  "com.typesafe.akka" %% "akka-persistence-cassandra" % AkkaPersistenceCassandraVersion,
  "com.typesafe.akka" %% "akka-slf4j" % AkkaVersion,
  "ch.qos.logback"     % "logback-classic"             % "1.2.3"
)

application.conf

akka.actor.allow-java-serialization = on
akka {
  loglevel = DEBUG
  actor {
    serialization-bindings {
      "com.learn.akka.CborSerializable" = jackson-cbor
    }
  }
  # use Cassandra to store both snapshots and the events of the persistent actors
  persistence {
    journal.plugin = "akka.persistence.cassandra.journal"
    snapshot-store.plugin = "akka.persistence.cassandra.snapshot"
  }

}
akka.persistence.cassandra {
  # don't use autocreate in production
  journal.keyspace = "poc"
  journal.keyspace-autocreate = on
  journal.tables-autocreate = on
  snapshot.keyspace = "poc_snapshot"
  snapshot.keyspace-autocreate = on
  snapshot.tables-autocreate = on
}

datastax-java-driver {
  basic.contact-points = ["192.168.11.189:9042"]
  basic.load-balancing-policy.local-datacenter = "datacenter1"
}

ShoppingCart.scala

package com.learn.akka

import akka.actor.typed._
import akka.persistence.typed._
import akka.actor.typed.scaladsl.Behaviors
import akka.persistence.typed.scaladsl._
import scala.concurrent.duration._

object ItemInfo {
  case class Item(name: String, price: Double)
}

object MyCart {
 import ItemInfo._

  sealed trait Command
  sealed trait Event extends CborSerializable
  sealed trait Response

  //commands
  case class AddItem(item: Item) extends Command
  case object PayCart extends Command
  case class CountItems(replyTo: ActorRef[Response]) extends Command

  //event
  case class ItemAdded(item: Item) extends Event
  case object CartPaid extends Event

  //state
  case class CartLoad(load: List[Item] = Nil)

  //response
  case class PickedItems(items: List[Item]) extends Response
  case object CartEmpty extends Response

  val commandHandler: (CartLoad, Command) => Effect[Event,CartLoad] = { (state, cmd) =>
    cmd match {
      case AddItem(item) =>
        Effect.persist(ItemAdded(item))
      case PayCart =>
        Effect.persist(CartPaid)
      case CountItems(replyTo) =>
        Effect.none.thenRun { cart =>
          cart.load match {
            case Nil =>
              replyTo ! CartEmpty
            case listOfItems =>
              replyTo ! PickedItems(listOfItems)
          }
        }
    }
  }

  val eventHandler: (CartLoad,Event) => CartLoad = { (state,evt) =>
    evt match {
      case ItemAdded(item) =>
         state.copy(load = item :: state.load)
      case CartPaid =>
        state.copy(load = Nil)
    }
  }

  def apply(): Behavior[Command] =
    Behaviors.supervise(
      Behaviors.setup[Command] { ctx =>
        EventSourcedBehavior[Command, Event, CartLoad](
          persistenceId = PersistenceId("10", "1013"),
          emptyState = CartLoad(),
          commandHandler = commandHandler,
          eventHandler = eventHandler
        ).onPersistFailure(
          SupervisorStrategy
            .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1)
            .withMaxRestarts(3)
            .withResetBackoffAfter(10.seconds)
        ).receiveSignal {
          case (state, RecoveryCompleted) =>
            ctx.log.info("**************Recovery Completed with state: {}***************",state)
          case (state, SnapshotCompleted(meta))  =>
            ctx.log.info("**************Snapshot Completed with state: {},id({},{})***************",state,meta.persistenceId, meta.sequenceNr)
          case (state,RecoveryFailed(err)) =>
            ctx.log.error("recovery failed with: {}",err.getMessage)
          case (state,SnapshotFailed(meta,err)) =>
            ctx.log.error("snapshoting failed with: {}",err.getMessage)
        }.snapshotWhen {
          case (state,CartPaid,seqnum) =>
            ctx.log.info("*****************snapshot taken at: {} with state: {}",seqnum,state)
            true
          case (state,event,seqnum) => false
        }.withRetention(RetentionCriteria.snapshotEvery(numberOfEvents = 100, keepNSnapshots = 2))
      }
    ).onFailure(
      SupervisorStrategy
        .restartWithBackoff(minBackoff = 10.seconds, maxBackoff = 60.seconds, randomFactor = 0.1)
        .withMaxRestarts(3)
        .withResetBackoffAfter(10.seconds)
    )
}

object Shopper {

  import ItemInfo._

  sealed trait Command extends CborSerializable

  case class GetItem(item: Item) extends Command
  case object Settle extends Command
  case object GetCount extends Command

  case class WrappedResponse(res: MyCart.Response) extends Command

  def apply(): Behavior[Command] = Behaviors.setup[Command] { ctx =>
    val shoppingCart = ctx.spawn(MyCart(), "shopping-cart")
    val cartRef: ActorRef[MyCart.Response] = ctx.messageAdapter(WrappedResponse)
    Behaviors.receiveMessage { msg =>
      msg match {
        case GetItem(item) =>
          shoppingCart ! MyCart.AddItem(item)
        case Settle =>
          shoppingCart ! MyCart.PayCart
        case GetCount =>
          shoppingCart ! MyCart.CountItems(cartRef)
        case WrappedResponse(res) => res match {
          case MyCart.PickedItems(items) =>
            ctx.log.info("**************Current Items in Cart: {}*************", items)
          case MyCart.CartEmpty =>
            ctx.log.info("**************shopping cart is empty!***************")
        }
      }
      Behaviors.same
    }
  }

}


object ShoppingCart extends App {
  import ItemInfo._
  val shopper = ActorSystem(Shopper(),"shopper")
  shopper ! Shopper.GetItem(Item("banana",11.20))
  shopper ! Shopper.GetItem(Item("watermelon",4.70))
  shopper ! Shopper.GetCount
  shopper ! Shopper.Settle
  shopper ! Shopper.GetCount
  scala.io.StdIn.readLine()

  shopper.terminate()

}

 

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

殼牌石油拚減碳 誓言最遲2050年達零排放

摘錄自2020年4月16日聯合報報導

國際石油天然氣巨擘荷蘭皇家殼牌集團(Royal Dutch Shell)今(16日)誓言,在2050年前要達成「碳中和」(Carbon Neutral)目標,和競爭對手英國石油公司(BP)的承諾一樣。

法新社報導,殼牌執行長范柏登(Ben vanBeurden)在聲明中表示,社會對於氣候變遷的期許瞬息萬變,殼牌需要再進一步自我要求,計畫最晚在2050年成為零排放的能源企業。殼牌將在2050年前把自家能源產品的「淨碳足跡」減少約65%;在2030年減少30%。

溫室氣體
能源議題
全球變遷
氣候變遷
能源轉型
國際新聞
殼牌
減碳宣言
零排放
石油

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

【其他文章推薦】

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

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

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

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

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

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