環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計最專業,超強功能平台可客製化
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※回頭車貨運收費標準
※推薦評價好的iphone維修中心
※教你寫出一流的銷售文案?
※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!
※台中搬家公司費用怎麼算?
摘錄自2020年5月19日自由時報報導
南韓LG化學再傳工安意外,位在忠清南道瑞山市的催化劑工廠,在當地時間今(19)日下午2時20分(台灣時間下午1時20分)左右爆炸失火,造成1死2傷。
《韓聯社》報導,該化工廠的催化劑疑似在過高的壓力下爆炸而引發大火,火勢已在下午3時30分(台灣時間下午2時30分)被撲滅,據消防部門表示,沒有有害化學物質外洩。目前該設施已關閉,警方和消防部門將在清理現場後調查確切事故原因。
本月7日,LG化學在印度投資的一家化工廠發生重大事故,廠內兩座5000公噸級苯乙烯儲存槽因不明原因發生嚴重外洩,造成12人死亡,1000多人住院。
建築
公害污染
生活環境
污染治理
國際新聞
南韓
化學工廠
工安事故
化工廠爆炸
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※回頭車貨運收費標準
※推薦評價好的iphone維修中心
※超省錢租車方案
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!
※推薦台中搬家公司優質服務,可到府估價
摘錄自2020年5月19日自由時報報導
美國國防部已對國會提出一項提案,擴大對稀土的投資上限,以停止對中國的依賴程度,這些稀土可以用來製造飛彈和彈藥、極音速武器,及相關電子產品。如果美國可以重新生產稀土,中國打「稀土牌」的威脅程度將大幅降低。
根據《國防新聞》報導,美國國防部希望提高《國防生產法》的支出上限,在開採稀土上提升至最高17.5億美元(約新台幣523億),在微電子晶片上增至3.5億美元(約新台幣104億),當涉及到極音速武器時,將會沒有上限。據悉,此提案已於本月初提出,已納入國會正在起草的年度國防政策法案。
美國防部副部長洛德(Ellen Lord)去年8月曾表示,國防部正與澳洲進行談判,要求其為美軍提供稀土。澳洲Lynas公司擁有稀土礦,同時在馬來西亞也有精煉廠,可能是此計劃的核心。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※回頭車貨運收費標準
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※推薦評價好的iphone維修中心
※教你寫出一流的銷售文案?
※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!
運動版全身大幅採用超高張力鋼板,並在車身關鍵部位進行了強化,安全性進一步提升。在主動安全性方面,T600運動版更將安全防護展現得淋漓盡致,安全配一應俱全。ESC車身穩定系統、HAC上坡輔助系統、前後倒車雷達及360°可視倒車影像等安全配置,與6方位安全氣囊、盲點信息系統、紅外夜視系統、TpMS智能胎壓監測、可選裝的HUD抬頭显示系統等尖端科技配備聯合上演重重壁壘,出色安全,呼之欲出,滿滿自信應對挑戰,盡享出行便利。
12月6日,中國汽車場地越野錦標賽(COC)廈門站比賽圓滿結束,也是最後一場分站賽,在各組別激烈的決賽搶分大戰中,眾泰T600越野車隊在汽油廠商組中奪得頭籌,車手鹿丙龍奪取該組冠軍,並與隊友徐瑩一起為車隊捧回了廠商杯冠軍殊榮,從而擴大了在年度積分方面的領先優勢,眾泰已經在今年分站比賽中已獲得六連冠,高歌猛進,一步步接近年度總冠軍。
作為本年度分站賽的最後一站,各個車隊之間的競爭日趨白熱化,尤其是之前比賽積分接近的車隊及隊員,比賽前已經是“劍拔弩張”,力爭本站取得更好排名和積分。眾泰車隊隊長楊逵如是向記者說道:“相對來說,我們在汽油改裝組的優勢要大一些,汽油廠商組和奇瑞車隊比較接近,由於總決賽採用雙倍積分的賽制,能否最終獲得全年總冠軍,廈門站比賽顯得尤為重要”。
【場地航拍圖】
【車隊大營】
【眾泰T600戰車】
汽油廠商組
本次比賽最大的變化就是之前因嚴重違規被禁賽的長安CS75車隊,重新回到了比賽。針對COC廈門站比賽形勢的變化,眾泰T600越野車隊對參賽陣容也進行了微調,喬旭與刁志剛攜手出擊汽油改裝組,鹿丙龍回歸汽油廠商組,和徐瑩搭檔。
在6圈的第一輪預賽中,車手們都拿出渾身解數,以求跑出好成績,從而得到決賽中最好的發車位置。眾泰車隊的鹿丙龍和徐瑩不負眾望,以小組第三、第四的成績闖進決賽,一起進入決賽的還有長安CS75車隊的文凡和孟斌。
“我們自身和車輛都調整到了最好狀態,對下午進行的決賽充滿信心”,眾泰T600越野車隊的車手鹿丙龍在決賽前向記者如是說。決賽中,鹿丙龍的表現堪稱“完美”,以絕對優勢力壓長安CS75車隊的孟斌和文凡獲得本組冠軍,其隊友徐瑩獲得本組季軍,獲得本組亞軍的是來自長安CS75車隊的孟斌,同時,鹿丙龍和徐瑩為眾泰T600越野車隊爭得了汽油廠商組的車隊團體冠軍獎盃。
汽油改裝組
眾泰T600越野車隊的喬旭在第一輪預賽中並不順利,他在第三圈的時候賽車出現失誤,賽車在幾處急彎都發生失控打橫,這極大地影響了喬旭的成績,儘管第二輪成績出色,但仍與決賽失之交臂,其隊友刁志剛以小組第三的成績征戰第二天進行的決賽。
6日下午的決賽中,車手刁志剛一人獨自面對其他三位車手的多面夾擊,面對發車位置不力的劣勢,刁志剛仍然奮起直追,最後以微小差距獲得了本小組的季軍,獲得本組冠亞軍的是來自另外兩支車隊的趙向前和童振榮。
作為“主流價值SUV”的眾泰T600,同眾泰車隊一樣,已然成為乘用車銷售市場上的佼佼者,早已進入月銷量“萬台俱樂部”,2016年1-10月份更是實現了94371台的銷量,以月均近萬台的銷量位居自主品牌中型SUV銷量榜首。
而且2016年眾泰汽車推出了更為年輕時尚的眾泰T600運動版,作為在眾泰T600優勢平台上推出的車型,眾泰T600運動版同樣以其年輕時尚又不乏沉穩的外觀、越級的配置在整個市場中還是有着普遍好評,銷量也是芝麻開花節節高。
眾泰T600運動版全系標配10寸中控彩色大屏,內容豐富。而Tye-net智控系統的優勢融入,實現手機操遠程控愛車,娛樂隨行,舒心便利。
此外,眾泰T600運動版還配備了一鍵啟動/無鑰匙進入、紅外夜視系統、腳步感應式電動尾門等尖端科技配備,讓駕乘人員充分享受科技智能帶來的便捷體驗。電動全景天窗、电子駐車系統、前排座椅分級加熱、雙區獨立自動恆溫空調、手機無線充電、方向盤/座椅/后視鏡三項聯動記憶功能、全液晶儀錶盤、定速巡航等帶來更加細緻入微的貼心關懷,讓出行一路無虞。
安全配置方面,眾泰T600運動版同級領先的安全性讓駕乘者無需前瞻後顧,無憂外出。運動版全身大幅採用超高張力鋼板,並在車身關鍵部位進行了強化,安全性進一步提升。在主動安全性方面,T600運動版更將安全防護展現得淋漓盡致,安全配一應俱全。ESC車身穩定系統、HAC上坡輔助系統、前後倒車雷達及360°可視倒車影像等安全配置,與6方位安全氣囊、盲點信息系統、紅外夜視系統、TpMS智能胎壓監測、可選裝的HUD抬頭显示系統等尖端科技配備聯合上演重重壁壘,出色安全,呼之欲出,滿滿自信應對挑戰,盡享出行便利。
而眾泰T600運動版不只是在外觀上吸引目光,在內飾的色彩搭配上,更是可圈可點,整個車內空間看起來既神秘又科技時尚。
眾泰T600運動版擁有的2807mm的傲人軸距,有效保證了車輛的駕乘空間。車內豐富的儲物空間為日常儲物提供了便利,而且後排座椅放倒後進一步拓展了後備箱空間,可以盡情享受眾泰T600運動版帶來的寬適空間。
動力方面,T600運動版提供1.5T及2.0T兩種發動機車型,1.5T渦輪增壓發動機與5速手動變速器搭配出黃金動力組合,最大功率達119KW,最大扭矩達215N·m。更加值得期待的是其2.0T車型,搭配使用旋鈕換擋式6速雙離合或5速手動變速器,最大功率140KW,最大扭矩250N·m,百公里加速只需9.26秒,充分提高了燃油的利用率,更加的節能環保,同時降低了用車成本。眾泰T600運動版,就是這樣讓你既有“面子”,又有“裡子”。
還有值得一說的是,眾泰T600在2015年J.D.power亞太公司發布的中國新車質量研究(IQS)報告,眾泰T600在中型SUV中pp100(每百車問題數)為100,優於中型SUV平均水平(pp100:106),全國綜合排名第13位,位列中型SUV中國品牌第二名。
2016年度COC總決賽將於12月中旬在廣西柳州打響,總決賽將實行雙倍積分制,各組別總決賽冠軍將收穫50分,這也讓之前積分落後並不太多的車手擁有了翻身逆轉的機會,那眾泰T600能否攜勢而來,獲得全年比賽的總冠軍,讓我們拭目以待!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※回頭車貨運收費標準
※網頁設計最專業,超強功能平台可客製化
※別再煩惱如何寫文案,掌握八大原則!
什麼是async/await?
await和async是.NET Framework4.5框架、C#5.0語法裏面出現的技術,目的是用於簡化異步編程模型。
async和await的關係?
async和await是成對出現的。
async出現在方法的聲明裡,用於批註一個異步方法。光有async是沒有意義的。
await出現在方法內部,Task前面。只能在使用async關鍵字批註的方法中使用await關鍵字。
private async Task DoSomething()
{
await Task.Delay(TimeSpan.FromSeconds(10));
}
async/await會創建新的線程嗎?
不會。async/await關鍵字本身是不會創建新的線程的,但是被await的方法內部一般會創建新的線程。
asp.net mvc/webapi action中使用async/await會提高請求的響應速度嗎?
不會。
我們都知道web應用不同於winform、wpf等客戶端應用,客戶端應用為了保證UI渲染的一致性往往都是採用單線程模式,這個UI線程稱為主線程,如果在主線程做耗時操作就會導致程序界面假死,所以客戶端開發中使用多線程異步編程非常必要。
可web應用本身就是多線程模式,服務器會為每個請求分配工作線程。
既然async/await不能創建新線程,又不能使提高請求的響應速度,那.NET Web應用中為什麼要使用async/await異步編程呢?
在 web 服務器上,.NET Framework 維護用於處理 ASP.NET 請求的線程池。 當請求到達時,將調度池中的線程以處理該請求。 如果以同步方式處理請求,則處理請求的線程將在處理請求時處於繁忙狀態,並且該線程無法處理其他請求。
在啟動時看到大量併發請求的 web 應用中,或具有突發負載(其中併發增長突然增加)時,使 web 服務調用異步會提高應用程序的響應能力。 異步請求與同步請求所需的處理時間相同。 如果請求發出需要兩秒鐘時間才能完成的 web 服務調用,則該請求將需要兩秒鐘,無論是同步執行還是異步執行。 但是,在異步調用期間,線程在等待第一個請求完成時不會被阻止響應其他請求。 因此,當有多個併發請求調用長時間運行的操作時,異步請求會阻止請求隊列和線程池的增長。
下面用代碼來實際測試一下:
public ActionResult Index()
{
DateTime startTime = DateTime.Now;//進入DoSomething方法前的時間
var startThreadId = Thread.CurrentThread.ManagedThreadId;//進入DoSomething方法前的線程ID
DoSomething();//耗時操作
DateTime endTime = DateTime.Now;//完成DoSomething方法的時間
var endThreadId = Thread.CurrentThread.ManagedThreadId;//完成DoSomething方法后的線程ID
return Content($"startTime:{ startTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } startThreadId:{ startThreadId }<br/>endTime:{ endTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } endThreadId:{ endThreadId }<br/><br/>");
}
/// <summary>
/// 耗時操作
/// </summary>
/// <returns></returns>
private void DoSomething()
{
Thread.Sleep(10000);
}
使用瀏覽器開3個標籤頁進行測試(因為瀏覽器對同一域名下的連接數有限制,一般是6個左右,所以就弄3個吧):
可以看到耗時都是10秒,開始和結束的線程ID一致。
public async Task<ActionResult> Index()
{
DateTime startTime = DateTime.Now;//進入DoSomething方法前的時間
var startThreadId = Thread.CurrentThread.ManagedThreadId;//進入DoSomething方法前的線程ID
await DoSomething();//耗時操作
DateTime endTime = DateTime.Now;//完成DoSomething方法的時間
var endThreadId = Thread.CurrentThread.ManagedThreadId;//完成DoSomething方法后的線程ID
return Content($"startTime:{ startTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } startThreadId:{ startThreadId }<br/>endTime:{ endTime.ToString("yyyy-MM-dd HH:mm:ss:fff") } endThreadId:{ endThreadId }<br/><br/>");
}
/// <summary>
/// 耗時操作
/// </summary>
/// <returns></returns>
private async Task DoSomething()
{
await Task.Run(() => Thread.Sleep(10000));
}
結果:
可以看到3次請求中,雖然耗時都是10秒,但是出現了開始和結束的線程ID不一致的情況,ID為22的這個線程工作了多次,這意味着使用異步方式在同一時間可以處理更多的請求!(這句話不太對,3個同步的併發請求必然會分配3個工作線程,但是使用異步的話,同一個線程可以被多個請求重複利用。因為線程池的線程數量是有上限的,所以在相同數量的線程下,使用異步方式能處理更多的請求。)
IIS默認隊列長度:
await關鍵字不會阻塞線程直到任務完成。 它將方法的其餘部分註冊為任務的回調,並立即返回。 當await的任務最終完成時,它將調用該回調,並因此在其中斷時繼續執行方法。
簡單來說:就是使用同步方法時,線程會被耗時操作一直佔有,直到耗時操作完成。而使用異步方法,程序走到await關鍵字時會立即return,釋放線程,餘下的代碼會放進一個回調中(Task.GetAwaiter()的UnsafeOnCompleted(Action)回調),耗時操作完成時才會回調執行,所以async/await是語法糖,其本質是一個狀態機。
那是不是所有的action都要用async/await呢?
不是。一般的磁盤IO或者網絡請求等耗時操作才考慮使用異步,不要為了異步而異步,異步也是需要消耗性能的,使用不合理會適得其反。
async/await異步編程不能提升響應速度,但是可以提升響應能力(吞吐量)。異步和同步各有優劣,要合理選擇,不要為了異步而異步。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※回頭車貨運收費標準
※網頁設計最專業,超強功能平台可客製化
※別再煩惱如何寫文案,掌握八大原則!
這個階段會給cute-dl添加循環層,使之能夠支持RNN–循環神經網絡. 具體目標包括:
RNN模型用來捕捉序列數據的特徵. 給定一個長度為T的輸入系列\(X=(x_1, x_2, .., X_T)\), RNN層輸出一個長度為T的序列\(H=(h_1, h_2, …, H_T)\), 對於任意時間步t, 可以表示為:
\[H_t = δ(X_tW_x + H_{t-1}W_h + b), \quad t = 2, 3, .., T \]
函數δ是sigmoid函數:
\[δ = \frac{1}{1 + e^{-x}} \]
\(H_t\)包含了前面第1到t-1步的所有信息。 和CNN層類似, CNN層在空間上共享參數, RNN層在時間步上共享參數\(W_x, W_h, b\).
RNN層中隱藏層的數量為T-2, 如果T較大(超過10), 反向傳播是很容易出現梯度爆炸. GRU和LSTM就是為了解決這個問題而誕生, 這兩種模型,可以讓RNN能夠支持長度超過1000的輸入序列。
GRU使用了不同功能的門控單元, 分別捕捉序列上不同時間跨度的的依賴關係。每個門控單元都會都有獨立的參數, 這些參數在時間步上共享。
GRU的門控單元有:
\(R_t = δ(X_tW^r_x + H_{t-1}W^r_h + b^r)\), 重置門用於捕捉短期依賴關係.
\(U_t = δ(X_tW^u_x + H_{t-1}W^u_h + b^u)\), 更新門用於捕捉長期依賴關係
\(\bar{H}_t = tanh(X_t\bar{W}_x + (R_t * H_{t-1})\bar{W}_h + \bar{b})\)
除此之外, 還有一個輸出單元:
\(H_t = U_t * H_{t-1} + (1-U_t)*\bar{H}_t\)
LSTM的設計思路和GRU類似, 同樣使用了多個門控單元:
\(I_t = δ(X_tW^i_x + H_{t-1}W^i_h + b^i)\), 輸入門,過濾記憶門的輸出.
\(F_t = δ(X_tW^f_x + H_{t-1}W^f_h + b^f)\), 遺忘門, 過濾前面時間步的記憶.
\(O_t = δ(X_tW^o_x + H_{t-1}W^o_h + b^o)\), 輸出門, 過濾當前時間步的記憶.
\(M_t = tanh(X_tW^m_x + H_{t-1}W^m_h + b^m)\), 記憶門.
它還有自己獨有的記憶單元和輸出單元:
\(\bar{M}_t = F_t * \bar{M}_{t-1} + I_t * M_t\)
\(H_t = O_t * tanh(\bar{M}_t)\)
設計要求:
文件: cutedl/rnn_layers.py, 類名: RNN
這個類是RNN層基類, 它主要功能是控制向前傳播和向後傳播的主流程.
初始化參數:
'''
out_units 輸出單元數
in_units 輸入單元數
stateful 保留當前批次的最後一個時間步的狀態作為下一個批次的輸入狀態, 默認False不保留
RNN 的輸入形狀是(m, t, in_units)
m: batch_size
t: 輸入系列的長度
in_units: 輸入單元數頁是輸入向量的維數
輸出形狀是(m, t, out_units)
'''
def __init__(self, out_units, in_units=None, stateful=False, activation='linear'):
向前傳播
def forward(self, in_batch, training):
m, T, n = in_batch.shape
out_units = self.__out_units
#所有時間步的輸出
hstatus = np.zeros((m, T, out_units))
#上一步的輸出
pre_hs = self.__pre_hs
if pre_hs is None:
pre_hs = np.zeros((m, out_units))
#隱藏層循環過程, 沿時間步執行
for t in range(T):
hstatus[:, t, :] = self.hiden_forward(in_batch[:,t,:], pre_hs, training)
pre_hs = hstatus[:, t, :]
self.__pre_hs = pre_hs
#pdb.set_trace()
if not self.stateful:
self.__pre_hs = None
return hstatus
反向傳播
def backward(self, gradient):
m, T, n = gradient.shape
in_units = self.__in_units
grad_x = np.zeros((m, T, in_units))
#pdb.set_trace()
#從最後一個梯度開始反向執行.
for t in range(T-1, -1, -1):
grad_x[:,t,:], grad_hs = self.hiden_backward(gradient[:,t,:])
#pdb.set_trace()
if t - 1 >= 0:
gradient[:,t-1,:] = gradient[:,t-1,:] + grad_hs
#pdb.set_trace()
return grad_x
\[sigmoid = \frac{1}{1+e^{-x}} \]
\[\frac{d}{dx}sigmoid = sigmoid(1-sigmoid) \]
\[tanh = \frac{e^x – e^{-x}}{e^x + e^{-x}} \]
\[\frac{d}{dx}tanh = 1 – tanh^2 \]
文件: cutedl/rnn_layers.py, 類名: GateUint
門控單元是RNN層基礎的參數單元. 和Dense層類似,它是Layer的子類,負責學習和使用參數。但在學習和使用參數的方式上有很大的不同:
下面我們會主要看一下GateUnit特別之處的代碼.
在__ init__方法中定義參數和棧:
#3個參數
self.__W = None #當前時間步in_batch權重參數
self.__Wh = None #上一步輸出的權重參數
self.__b = None #偏置量參數
#輸入棧
self.__hs = [] #上一步輸出
self.__in_batchs = [] #當前時間步的in_batch
正向傳播:
def forward(self, in_batch, hs, training):
W = self.__W.value
b = self.__b.value
Wh = self.__Wh.value
out = in_batch @ W + hs @ Wh + b
if training:
#向前傳播訓練時把上一個時間步的輸出和當前時間步的in_batch壓棧
self.__hs.append(hs)
self.__in_batchs.append(in_batch)
#確保反向傳播開始時參數的梯度為空
self.__W.gradient = None
self.__Wh.gradient = None
self.__b.gradient = None
return self.activation(out)
反向傳播:
def backward(self, gradient):
grad = self.activation.grad(gradient)
W = self.__W.value
Wh = self.__Wh.value
pre_hs = self.__hs.pop()
in_batch = self.__in_batchs.pop()
grad_in_batch = grad @ W.T
grad_W = in_batch.T @ grad
grad_hs = grad @ Wh.T
grad_Wh = pre_hs.T @ grad
grad_b = grad.sum(axis=0)
#反向傳播計算
if self.__W.gradient is None:
#當前批次第一次
self.__W.gradient = grad_W
else:
#累積當前批次的所有梯度
self.__W.gradient = self.__W.gradient + grad_W
if self.__Wh.gradient is None:
self.__Wh.gradient = grad_Wh
else:
self.__Wh.gradient = self.__Wh.gradient + grad_Wh
if self.__b.gradient is None:
self.__b.gradient = grad_b
else:
self.__b.gradient = self.__b.gradient + grad_b
return grad_in_batch, grad_hs
文件: cutedl/rnn_layers.py, 類名: GRU
隱藏單初始化:
def set_parent(self, parent):
super().set_parent(parent)
out_units = self.out_units
in_units = self.in_units
#pdb.set_trace()
#重置門
self.__g_reset = GateUnit(out_units, in_units)
#更新門
self.__g_update = GateUnit(out_units, in_units)
#候選輸出門
self.__g_cddout = GateUnit(out_units, in_units, activation='tanh')
self.__g_reset.set_parent(self)
self.__g_update.set_parent(self)
self.__g_cddout.set_parent(self)
#重置門乘法單元
self.__u_gr = MultiplyUnit()
#輸出單元
self.__u_out = GRUOutUnit()
向前傳播:
def hiden_forward(self, in_batch, pre_hs, training):
gr = self.__g_reset.forward(in_batch, pre_hs, training)
gu = self.__g_update.forward(in_batch, pre_hs, training)
ugr = self.__u_gr.forward(gr, pre_hs, training)
cddo = self.__g_cddout.forward(in_batch, ugr, training)
hs = self.__u_out.forward(gu, pre_hs, cddo, training)
return hs
反向傳播:
def hiden_backward(self, gradient):
grad_gu, grad_pre_hs, grad_cddo = self.__u_out.backward(gradient)
#pdb.set_trace()
grad_in_batch, grad_ugr = self.__g_cddout.backward(grad_cddo)
#計算梯度的過程中需要累積上一層輸出的梯度
grad_gr, g_pre_hs = self.__u_gr.backward(grad_ugr)
grad_pre_hs = grad_pre_hs + g_pre_hs
g_in_batch, g_pre_hs = self.__g_update.backward(grad_gu)
grad_in_batch = grad_in_batch + g_in_batch
grad_pre_hs = grad_pre_hs + g_pre_hs
g_in_batch, g_pre_hs = self.__g_reset.backward(grad_gr)
grad_in_batch = grad_in_batch + g_in_batch
grad_pre_hs = grad_pre_hs + g_pre_hs
#pdb.set_trace()
return grad_in_batch, grad_pre_hs
文件: cutedl/rnn_layers.py, 類名: LSTM
隱藏單元初始化:
def set_parent(self, layer):
super().set_parent(layer)
in_units = self.in_units
out_units = self.out_units
#輸入門
self.__g_in = GateUnit(out_units, in_units)
#遺忘門
self.__g_forget = GateUnit(out_units, in_units)
#輸出門
self.__g_out = GateUnit(out_units, in_units)
#記憶門
self.__g_memory = GateUnit(out_units, in_units, activation='tanh')
self.__g_in.set_parent(self)
self.__g_forget.set_parent(self)
self.__g_out.set_parent(self)
self.__g_memory.set_parent(self)
#記憶單元
self.__memory_unit =LSTMMemoryUnit()
#輸出單元
self.__out_unit = LSTMOutUnit()
向前傳播:
def hiden_forward(self, in_batch, hs, training):
g_in = self.__g_in.forward(in_batch, hs, training)
#pdb.set_trace()
g_forget = self.__g_forget.forward(in_batch, hs, training)
g_out = self.__g_out.forward(in_batch, hs, training)
g_memory = self.__g_memory.forward(in_batch, hs, training)
memory = self.__memory_unit.forward(g_forget, g_in, g_memory, training)
cur_hs = self.__out_unit.forward(g_out, memory, training)
return cur_hs
反向傳播:
def hiden_backward(self, gradient):
#pdb.set_trace()
grad_out, grad_memory = self.__out_unit.backward(gradient)
grad_forget, grad_in, grad_gm = self.__memory_unit.backward(grad_memory)
grad_in_batch, grad_hs = self.__g_memory.backward(grad_gm)
tmp1, tmp2 = self.__g_out.backward(grad_out)
grad_in_batch += tmp1
grad_hs += tmp2
tmp1, tmp2 = self.__g_forget.backward(grad_forget)
grad_in_batch += tmp1
grad_hs += tmp2
tmp1, tmp2 = self.__g_in.backward(grad_in)
grad_in_batch += tmp1
grad_hs += tmp2
return grad_in_batch, grad_hs
接下來, 驗證示例將會構建一個簡單的RNN模型, 使用該模型擬合一個正餘弦疊加函數:
#採樣函數
def sample_function(x):
y = 3*np.sin(2 * x * np.pi) + np.cos(x * np.pi) + np.random.uniform(-0.05,0.05,len(x))
return y
訓練數據集和測試數據集在這個函數的不同定義域區間內樣. 訓練數據集的採樣區間為[1, 200.01), 測試數據集的採樣區間為[200.02, 240.002). 模型任務是預測這個函數值的序列.
示例代碼在examples/rnn/fit_function.py文件中.
def fit_gru():
model = Model([
rnn.GRU(32, 1),
nn.Filter(),
nn.Dense(32),
nn.Dense(1, activation='linear')
])
model.assemble()
fit('gru', model)
訓練報告:
def fit_lstm():
model = Model([
rnn.LSTM(32, 1),
nn.Filter(),
nn.Dense(2),
nn.Dense(1, activation='linear')
])
model.assemble()
fit('lstm', model)
訓練報告:
這個階段,框架新增了RNN的兩個最常見的實現:GRU和LSTM, 相應地增加了它需要的激活函數. cute-dl已經具備了構建最基礎RNN模型的能力。通過驗證發現, GRU模型和LSTM模型在簡單任務上都表現出了很好的性能。會添加嵌入層,使框架能夠構建文本分類任務的模型,然後在imdb-review(電影評價)數據集上進行驗證.
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
※回頭車貨運收費標準
首先來介紹下什麼是優雅地停止,簡而言之,就是對應用進程發送停止指令之後,能保證正在執行的業務操作不受影響,可以繼續完成已有請求的處理,但是停止接受新請求。
在 Spring Boot 2.3 中增加了新特性優雅停止,目前 Spring Boot 內置的四個嵌入式 Web 服務器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反應式和基於 Servlet 的 Web 應用程序都支持優雅停止。
下面,我們先用新版本嘗試下:
首先創建一個 Spring Boot 的 Web 項目,版本選擇 2.3.0.RELEASE,Spring Boot 2.3.0.RELEASE 版本內置的 Tomcat 為 9.0.35。
然後需要在 application.yml 中添加一些配置來啟用優雅停止的功能:
# 開啟優雅停止 Web 容器,默認為 IMMEDIATE:立即停止
server:
shutdown: graceful
# 最大等待時間
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
其中,平滑關閉內置的 Web 容器(以 Tomcat 為例)的入口代碼在 org.springframework.boot.web.embedded.tomcat 的 GracefulShutdown 里,大概邏輯就是先停止外部的所有新請求,然後再處理關閉前收到的請求,有興趣的可以自己去看下。
內嵌的 Tomcat 容器平滑關閉的配置已經完成了,那麼如何優雅關閉 Spring 容器了,就需要 Actuator 來實現 Spring 容器的關閉了。
然後加入 actuator 依賴,依賴如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然後接着再添加一些配置來暴露 actuator 的 shutdown 接口:
# 暴露 shutdown 接口
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
其中通過 Actuator 關閉 Spring 容器的入口代碼在 org.springframework.boot.actuate.context 包下 ShutdownEndpoint 類中,主要的就是執行 doClose() 方法關閉並銷毀 applicationContext,有興趣的可以自己去看下。
配置搞定后,然後在 controller 包下創建一個 WorkController 類,並有一個 work 方法,用來模擬複雜業務耗時處理流程,具體代碼如下:
@RestController
public class WorkController {
@GetMapping("/work")
public String work() throws InterruptedException {
// 模擬複雜業務耗時處理流程
Thread.sleep(10 * 1000L);
return "success";
}
}
然後,我們啟動項目,先用 Postman 請求 http://localhost:8080/work 處理業務:
然後在這個時候,調用 http://localhost:8080/actuator/shutdown 就可以執行優雅地停止,返回結果如下:
{
"message": "Shutting down, bye..."
}
如果在這個時候,發起新的請求 http://localhost:8080/work,會沒有反應:
再回頭看第一個請求,返回了結果:success。
其中有幾條服務日誌如下:
2020-05-20 23:05:15.163 INFO 102724 --- [ Thread-253] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
2020-05-20 23:05:15.287 INFO 102724 --- [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
2020-05-20 23:05:15.295 INFO 102724 --- [ Thread-253] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
從日誌中也可以看出來,當調用 shutdown 接口的時候,會先等待請求處理完畢后再優雅地停止。
到此為止,Spring Boot 2.3 的優雅關閉就講解完了,是不是很簡單呢?如果是在之前不支持優雅關閉的版本如何去做呢?
在這裏介紹 GitHub 上 issue 里 Spring Boot 開發者提供的一種方案:
選取的 Spring Boot 版本為 2.2.6.RELEASE,首先要實現 TomcatConnectorCustomizer 接口,該接口是自定義 Connector 的回調接口:
@FunctionalInterface
public interface TomcatConnectorCustomizer {
void customize(Connector connector);
}
除了定製 Connector 的行為,還要實現 ApplicationListener<ContextClosedEvent> 接口,因為要監聽 Spring 容器的關閉事件,即當前的 ApplicationContext 執行 close() 方法,這樣我們就可以在請求處理完畢後進行 Tomcat 線程池的關閉,具體的實現代碼如下:
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
private static class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
有了定製的 Connector 回調,還需要在啟動過程中添加到內嵌的 Tomcat 容器中,然後等待監聽到關閉指令時執行,addConnectorCustomizers 方法可以把定製的 Connector 行為添加到內嵌的 Tomcat 中,具體代碼如下:
@Bean
public ConfigurableServletWebServerFactory tomcatCustomizer() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown());
return factory;
}
到此為止,內置的 Tomcat 容器平滑關閉的操作就完成了,Spring 容器優雅停止上面已經說過了,再次就不再贅述了。
通過測試,同樣可以達到上面那樣優雅停止的效果。
本文主要講解了 Spring Boot 2.3 版本和舊版本的優雅停止,避免強制停止導致正在處理的業務邏輯會被中斷,進而導致產生業務異常的情形。
另外使用 Actuator 的同時要注意安全問題,比如可以通過引入 security 依賴,打開安全限制並進行身份驗證,設置單獨的 Actuator 管理端口並配置只對內網開放等。
本文的完整代碼在 https://github.com/wupeixuan/SpringBoot-Learn 的 graceful-shutdown 目錄下。
最好的關係就是互相成就,大家的在看、轉發、留言三連就是我創作的最大動力。
參考
https://github.com/spring-projects/spring-boot/issues/4657
https://github.com/wupeixuan/SpringBoot-Learn
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※回頭車貨運收費標準
最近在整理優化.net代碼時,發現幾個很不友好的處理現象:登錄判斷、權限認證、日誌記錄、異常處理等通用操作,在項目中的action中到處都是。在代碼優化上,這一點是很重要着力點。這時.net中的過濾器、攔截器(Filter)就派上用場了。現在根據這幾天的實際工作,對其做了一個簡單的梳理,分享出來,以供大家參考交流,如有寫的不妥之處,多多指出,多多交流。
.net中的Filter中主要包括以下4大類:Authorize(授權),ActionFilter(自定義),HandleError(錯誤處理)。
|
過濾器 |
類名 |
實現接口 |
描述 |
|
授權 |
AuthorizeAttribute |
IAuthorizationFilter |
此類型(或過濾器)用於限制進入控制器或控制器的某個行為方法,比如:登錄、權限、訪問控制等等 |
|
異常 |
HandleErrorAttribute |
IExceptionFilter |
用於指定一個行為,這個被指定的行為處理某個行為方法或某個控制器裏面拋出的異常,比如:全局異常統一處理。 |
|
自定義 |
ActionFilterAttribute |
IActionFilter和IResultFilter |
用於進入行為之前或之後的處理或返回結果的之前或之後的處理,比如:用戶請求日誌詳情日誌記錄 |
認證授權主要是對所有action的訪問第一入口認證,對用戶的訪問做第一道監管過濾攔截閘口。
實現方式:需要自定義一個類,繼承AuthorizeAttribute並重寫OnAuthorization,在OnAuthorization中能夠獲取到用戶請求的所有Request信息,其實我們做的所有認證攔截操作,其所有數據支撐都是來自Request中。
具體驗證流程設計:
IP白名單:這個主要針對的是API做IP限制,只有指定IP才可訪問,非指定IP直接返回
請求頻率控制:這個主要是控制用戶的訪問頻率,主要是針對API做,超出請求頻率直接返回。
登錄認證:登錄認證一般我們採用的是通過在請求的header中傳遞token的方式來進行驗證,這樣即使用與一般的MVC登錄認證,也使用與API接口的Auth認證,並且也不依賴於用戶前端js設置等。
授權認證:授權認證就簡單了,主要是驗證該用戶是否具有該權限,如果不具有,直接做下相應的返回處理。
MVC和API異同:
命名空間:MVC:System.Web.Http.Filters;API:System.Web.Mvc
注入方式:在注入方式上,主要包括:全局->控制器Controller->行為Action
全局註冊:針對所有系統的所有Aciton都使用
Controller:只針對該Controller下的Action起作用
Action:只針對該Action起作用
其中全局註冊,針對MVC和API還有一些差異:
MVC在 FilterConfig.cs中注入
filters.Add(new XYHMVCAuthorizeAttribute());
API 在 WebApiConfig.cs 中注入
config.Filters.Add(new XYHAPIAuthorizeAttribute());
注意事項:在實際使用中,針對認證授權,我們一般都是添加全局認證,但是,有的action又不需要做認證,比如本來的登錄Action等等,那麼該如何排除呢?其實也很簡單,我們只需要在自定定義一個Attribute集成Attribute,或者系統的AllowAnonymousAttribute,在不需要驗證的action中只需要註冊上對於的Attribute,並在驗證前做一個過濾即可,比如:
// 有 AllowAnonymous 屬性的接口直接開綠燈
if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
{
return;
}
API AuthFilterAttribute實例代碼
/// <summary> /// 授權認證過濾器 /// </summary> public class XYHAPIAuthFilterAttribute : AuthorizationFilterAttribute { /// <summary> /// 認證授權驗證 /// </summary> /// <param name="actionContext">請求上下文</param> public override void OnAuthorization(HttpActionContext actionContext) { // 有 AllowAnonymous 屬性的接口直接開綠燈 if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any()) { return; } // 在請求前做一層攔截,主要驗證token的有效性和驗簽 HttpRequest httpRequest = HttpContext.Current.Request; // 獲取apikey var apikey = httpRequest.QueryString["apikey"]; // 首先做IP白名單校驗 MBaseResult<string> result = new AuthCheckService().CheckIpWhitelist(FilterAttributeHelp.GetIPAddress(actionContext.Request), apikey); // 檢驗時間戳 string timestamp = httpRequest.QueryString["Timestamp"]; if (result.Code == MResultCodeEnum.successCode) { // 檢驗時間戳 result = new AuthCheckService().CheckTimestamp(timestamp); } if (result.Code == MResultCodeEnum.successCode) { // 做請求頻率驗證 string acitonName = actionContext.ActionDescriptor.ActionName; string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName; result = new AuthCheckService().CheckRequestFrequency(apikey, $"api/{controllerName.ToLower()}/{acitonName.ToLower()}"); } if (result.Code == MResultCodeEnum.successCode) { // 簽名校驗 // 獲取全部的請求參數 Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters(); result = new AuthCheckService().SignCheck(queryParameters, apikey); if (result.Code == MResultCodeEnum.successCode) { // 如果有NoChekokenFilterAttribute 標籤 那麼直接不做token認證 if (actionContext.ActionDescriptor.GetCustomAttributes<XYHAPINoChekokenFilterAttribute>().Any()) { return; } // 校驗token的有效性 // 獲取一個 token string token = httpRequest.Headers.GetValues("Token") == null ? string.Empty : httpRequest.Headers.GetValues("Token")[0]; result = new AuthCheckService().CheckToken(token, apikey, httpRequest.FilePath); } } // 輸出 if (result.Code != MResultCodeEnum.successCode) { // 一定要實例化一個response,是否最終還是會執行action中的代碼 actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK); //需要自己指定輸出內容和類型 HttpContext.Current.Response.ContentType = "text/html;charset=utf-8"; HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result)); HttpContext.Current.Response.End(); // 此處結束響應,就不會走路由系統 } } }
MVC AuthFilterAttribute實例代碼
/// <summary> /// MVC自定義授權 /// 認證授權有兩個重寫方法 /// 具體的認證邏輯實現:AuthorizeCore 這個裡面寫具體的認證邏輯,認證成功返回true,反之返回false /// 認證失敗處理邏輯:HandleUnauthorizedRequest 前一步返回 false時,就會執行到該方法中 /// 但是,我平時在應用過程中,一般都是在AuthorizeCore根據不同的認證結果,直接做認證后的邏輯處理 /// </summary> public class XYHMVCAuthorizeAttribute : AuthorizeAttribute { /// <summary> /// 認證邏輯 /// </summary> /// <param name="filterContext">過濾器上下文</param> public override void OnAuthorization(AuthorizationContext filterContext) { // 此處主要寫認證授權的相關驗證邏輯 // 該部分的驗證一般包括兩個部分 // 登錄權限校驗 // --我們的一般處理方式是,通過header中傳遞一個token來進行邏輯驗證 // --當然不同的系統在設計上也不盡相同,有的也會採用session等方式來驗證 // --所以最終還是根據其項目本身的實際情況來進行對應的邏輯操作 // 具體的頁面權限校驗 // --該部分的驗證是具體的到頁面權限驗證 // --我看有得小夥伴沒有做到這一個程度,直接將這一步放在前端js來驗證,這樣不是很安全,但是可以攔住小白用戶 // --當然有的系統根本就沒有做權限控制,那就更不需要這一個邏輯了。 // --所以最終還是根據其項目本身的實際情況來進行對應的邏輯操作 // 現在用一個粗暴的方式來簡單模擬實現過,用系統當前時間段秒廚藝3,取餘數 // 當餘數為0:認證授權通過 // 1:代表為登錄,調整至登錄頁面 // 2:代表無訪問權限,調整至無權限提示頁面 // 當然,在這也還可以做一些IP白名單,IP黑名單驗證 請求頻率驗證等等 // 說到這而,還有一點需要注意,如果我們選擇的是全局註冊該過濾器,那麼如果有的頁面根本不需要權限認證,比如登錄頁面,那麼我們可以給不需要權限的認證的控制器或者action添加一個特殊的註解 AllowAnonymous ,來排除 // 獲取Request的幾個關鍵信息 HttpRequest httpRequest = HttpContext.Current.Request; string acitonName = filterContext.ActionDescriptor.ActionName; string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; // 注意:如果認證不通過,需要設置filterContext.Result的值,否則還是會執行action中的邏輯 filterContext.Result = null; int thisSecond = System.DateTime.Now.Second; switch (thisSecond % 3) { case 0: // 認證授權通過 break; case 1: // 代表為登錄,調整至登錄頁面 // 只有設置了Result才會終結操作 filterContext.Result = new RedirectResult("/html/Login.html"); break; case 2: // 代表無訪問權限,調整至無權限提示頁面 filterContext.Result = new RedirectResult("/html/NoAuth.html"); break; } } }
自定義過濾器,主要是監控action請求前後,處理結果返回前後的事件。其中API只有請求前後的兩個方法。
|
重新方法 |
方法功能描述 |
使用於 |
|
OnActionExecuting |
一個請求在進入到aciton邏輯前執行 |
MVC、API |
|
OnActionExecuted |
一個請求aciton邏輯執行后執行 |
MVC、API |
|
OnResultExecuting |
對應的view視圖渲染前執行 |
MVC |
|
OnResultExecuted |
對應的view視圖渲染后執行 |
MVC |
在這幾個方法中,我們一般主要用來記錄交互日誌,記錄每一個步驟的耗時情況,以便後續系統優化使用。具體的使用,根據自身的業務場景使用。
其中MVC和API的異同點,和上面說的認證授權的異同類似,不在詳細說明。
下面的一個實例代碼:
API定義過濾器實例DEMO代碼
/// <summary> /// Action過濾器 /// </summary> public class XYHAPICustomActionFilterAttribute : ActionFilterAttribute { /// <summary> /// Action執行開始 /// </summary> /// <param name="actionContext"></param> public override void OnActionExecuting(HttpActionContext actionContext) { } /// <summary> /// action執行以後 /// </summary> /// <param name="actionContext"></param> public override void OnActionExecuted(HttpActionExecutedContext actionContext) { try { // 構建一個日誌數據模型 MApiRequestLogs apiRequestLogsM = new MApiRequestLogs(); // API名稱 apiRequestLogsM.API = actionContext.Request.RequestUri.AbsolutePath; // apiKey apiRequestLogsM.API_KEY = HttpContext.Current.Request.QueryString["ApiKey"]; // IP地址 apiRequestLogsM.IP = FilterAttributeHelp.GetIPAddress(actionContext.Request); // 獲取token string token = HttpContext.Current.Request.Headers.GetValues("Token") == null ? string.Empty : HttpContext.Current.Request.Headers.GetValues("Token")[0]; apiRequestLogsM.TOKEN = token; // URL apiRequestLogsM.URL = actionContext.Request.RequestUri.AbsoluteUri; // 返回信息 var objectContent = actionContext.Response.Content as ObjectContent; var returnValue = objectContent.Value; apiRequestLogsM.RESPONSE_INFOR = returnValue.ToString(); // 由於數據庫中最大隻能存儲4000字符串,所以對返回值做一個截取 if (!string.IsNullOrEmpty(apiRequestLogsM.RESPONSE_INFOR) && apiRequestLogsM.RESPONSE_INFOR.Length > 4000) { apiRequestLogsM.RESPONSE_INFOR = apiRequestLogsM.RESPONSE_INFOR.Substring(0, 2000); } // 請求參數 apiRequestLogsM.REQUEST_INFOR = actionContext.Request.RequestUri.Query; // 定義一個異步委託 ,異步記錄日誌 // Func<MApiRequestLogs, string> action = AddApiRequestLogs;//聲明一個委託 // IAsyncResult ret = action.BeginInvoke(apiRequestLogsM, null, null); } catch (Exception ex) { } } }
異常處理對於我們來說很常用,很好的利用異常處理,可以很好的避免全篇的try/catch。異常處理箱單很簡單,值需要自定義集成:ExceptionFilterAttribute,並自定義實現:OnException方法即可。
在OnException我們可以根據自身需要,做一些相應的邏輯處理,比如記錄異常日誌,便於後續問題分析跟進。
OnException還有一個很重要的處理,那就是對異常結果的統一包裝,返回一個很友好的結果給用戶,避免把一些不必要的信息返回給用戶。比如:針對MVC,那麼跟進不同異常,統一調整至友好的提示頁面等等;針對API,那麼我們可以一個統一的返回幾個封裝,便於用戶統一處理結果。
MVC 的異常處理實例代碼:
/// <summary> /// MVC自定義異常處理機制 /// 說道異常處理,其實我們腦海中的第一反應,也該是try/cache操作 /// 但是在實際開發中,很有可能地址錯誤根本就進入不到try中,又或者沒有被try處理到異常 /// 該類就發揮了作用,能夠很好的未經捕獲的異常,並做相應的邏輯處理 /// 自定義異常機制,主要集成HandleErrorAttribute 重寫其OnException方法 /// </summary> public class XYHMVCHandleError : HandleErrorAttribute { /// <summary> /// 處理異常 /// </summary> /// <param name="filterContext">異常上下文</param> public override void OnException(ExceptionContext filterContext) { // 我們在平時的項目中,異常處理一般有兩個作用 // 1:記錄異常的詳細日誌,便於事後分析日誌 // 2:對異常的統一友好處理,比如根據異常類型重定向到友好提示頁面 // 在這裏面既能獲取到未經處理的異常信息,也能獲取到請求信息 // 在此可以根據實際項目需要做相應的邏輯處理 // 下面簡單的列舉了幾個關鍵信息獲取方式 // 控制器名稱 注意,這樣獲取出來的是一個文件的全路徑 string contropath = filterContext.Controller.ToString(); // 訪問目錄的相對路徑 string filePath = filterContext.HttpContext.Request.FilePath; // url完整地址 string url = (filterContext.HttpContext.Request.Url.AbsoluteUri).ExUrlDeCode(); // 請求方式 post get string httpMethod = filterContext.HttpContext.Request.HttpMethod; // 請求IP地址 string ip = filterContext.HttpContext.Request.GetIPAddress(); // 獲取全部的請求參數 HttpRequest httpRequest = HttpContext.Current.Request; Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters(); // 獲取異常對象 Exception ex = filterContext.Exception; // 異常描述信息 string exMessage = ex.Message; // 異常堆棧信息 string stackTrace = ex.StackTrace; // 根據實際情況記錄日誌(文本日誌、數據庫日誌,建議具體步驟採用異步方式來完成) filterContext.ExceptionHandled = true; // 模擬根據不同的做對應的邏輯處理 int statusCode = filterContext.HttpContext.Response.StatusCode; if (statusCode>=400 && statusCode<500) { filterContext.Result = new RedirectResult("/html/404.html"); } else { filterContext.Result = new RedirectResult("/html/500.html"); } } }
API 的異常處理實例代碼:
/// <summary> /// API自定義異常處理機制 /// 說道異常處理,其實我們腦海中的第一反應,也該是try/cache操作 /// 但是在實際開發中,很有可能地址錯誤根本就進入不到try中,又或者沒有被try處理到異常 /// 該類就發揮了作用,能夠很好的未經捕獲的異常,並做相應的邏輯處理 /// 自定義異常機制,主要集成ExceptionFilterAttribute 重寫其OnException方法 /// </summary> public class XYHAPIHandleError : ExceptionFilterAttribute { /// <summary> /// 處理異常 /// </summary> /// <param name="actionExecutedContext">異常上下文</param> public override void OnException(HttpActionExecutedContext actionExecutedContext) { // 我們在平時的項目中,異常處理一般有兩個作用 // 1:記錄異常的詳細日誌,便於事後分析日誌 // 2:對異常的統一友好處理,比如根據異常類型重定向到友好提示頁面 // 在這裏面既能獲取到未經處理的異常信息,也能獲取到請求信息 // 在此可以根據實際項目需要做相應的邏輯處理 // 下面簡單的列舉了幾個關鍵信息獲取方式 // action名稱 string actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName; // 控制器名稱 string controllerName =actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName; // url完整地址 string url = (actionExecutedContext.Request.RequestUri.AbsoluteUri).ExUrlDeCode(); // 請求方式 post get string httpMethod = actionExecutedContext.Request.Method.Method; // 請求IP地址 string ip = actionExecutedContext.Request.GetIPAddress(); // 獲取全部的請求參數 HttpRequest httpRequest = HttpContext.Current.Request; Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters(); // 獲取異常對象 Exception ex = actionExecutedContext.Exception; // 異常描述信息 string exMessage = ex.Message; // 異常堆棧信息 string stackTrace = ex.StackTrace; // 根據實際情況記錄日誌(文本日誌、數據庫日誌,建議具體步驟採用異步方式來完成) // 自己的記錄日誌落地邏輯略 ...... // 構建統一的內部異常處理機制,相當於對異常做一層統一包裝暴露 MBaseResult<string> result = new MBaseResult<string>() { Code = MResultCodeEnum.systemErrorCode, Message = MResultCodeEnum.systemError }; actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.OK); //需要自己指定輸出內容和類型 HttpContext.Current.Response.ContentType = "text/html;charset=utf-8"; HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result)); HttpContext.Current.Response.End(); // 此處結束響應,就不會走路由系統 } }
.net過濾器,我個人的一句話理解就是:對action的各個階段進行統一的監控處理等操作。.net過濾器中,其中每一個種過濾器的執行先後順序為:Authorize(授權)–>ActionFilter(自定義)–>HandleError(錯誤處理)
好了,就先聊到這而,如果什麼地方說的不對之處,多多指點和多多包涵。我自己寫了一個練習DEMO,裏面會有每一種情況的處理說明。有興趣的可以取下載下來看一看,謝謝。
DEMO在GitHub地址為:https://github.com/xuyuanhong0902/XYH.FilterTest.git
END
為了更高的交流,歡迎大家關注我的公眾號,掃描下面二維碼即可關注,謝謝:
認證授權
時間戳
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※回頭車貨運收費標準
※台中搬家公司費用怎麼算?
環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:ENS
美國普渡大學開發出更環境友善且具商業可行性的稀土開採技術。新技術已獲專利,有機會改寫產業生態,幫助美國創造更穩定的本土供給。
舉凡電腦、手機、DVD、充電電池、觸媒轉換器、磁鐵、風力渦輪機和日光燈、雷射測距儀、導航系統和精準武器都需要稀土金屬,但是在自然界中往往濃度不高,很難進行商業開採。
此外,目前以酸為基礎的稀土金屬分離和純化技術不利環境,世界各地的大多數公司無法進入市場。
碳酸鑭的結晶體,鑭是稀土之一。照片來源:ZEISS Microscopy(CC BY-NC-ND 2.0)
中國是全球稀土主要生產者。17種稀土金屬的全球儲藏量,有36%掌握在中國手上。目前中國已不再像1980至1990年代,以低於生產成本的價格出售稀土,其他國家也有機會成為稀土生產者。
但是當中國在2010年降低稀土金屬的出口配額時,一台風力渦輪機的稀土磁體成本從8萬美元飆升至50萬美元。18個月後中國放鬆了出口限制,價格又回到了低於2010年的水準。
中國目前仍是本土和出口市場中,用來生產電子產品的稀土金屬的主要消費國,緊接著是日本和美國。
幾乎60%的稀土金屬都用於生產磁鐵。具有永磁性的稀土磁鐵幾乎日常生活無所不在,不論是電子產品、飛機、油電混合車或風機。開發出新技術的普渡大學化學教授王念華(音譯)說:「目前稀土只有一個海外供給,如果因故供給受限,將嚴重影響人們的生活。美國也有這種資源,但需要一種更好、更乾淨的方式來處理這些稀土金屬。」
新的專利提取和純化技術使用配體輔助層析法,經證實可以安全有效地從煤灰、回收磁鐵和原礦石中分離出稀土金屬,對環境幾乎沒有不利影響。
鎦是一種銀白色金屬,稀土金屬之一。照片來源:
維基百科/Alchemist-hp(CC BY-NC-ND 3.0)
稀土金屬的生產每年全球有40億美元的市場。隨著新的電子產品、飛機、軍艦、電動汽車、磁鐵和其他需要稀土金屬的重要產品的開發,這個市場繼續成長中。每年靠稀土金屬作用的產品價值超過4兆美元。
王念華解釋:「傳統生產高純度稀土元素方法採用兩階段液相萃取法,這需要用到數千個串聯或併聯的混合沈降槽,產生大量有毒廢物。我們的兩區域配體輔助置換層析系統使用一種新的分區方法,可生產高純度(> 99%)金屬並達到高產率(> 99%)。」
王念華的配體輔助方法有機會從廢磁鐵和礦石回收高效、環保地純化稀土金屬,並有助使稀土加工成為循環永續的過程。
普渡大學化學工程教授喬.佩克尼(Joe Pekny)說,王念華的創新技術使美國能以環境友善、安全和永續的方式重新進入稀土金屬市場。佩克尼說:「美國的稀土金屬可以滿足美國和全球其他市場不斷成長的需求,減少對外國資源的依賴。」
這項研究部分由美國國防部(Department of Defense﹐DoD)資助。
現在美國國防部正在與美國稀土礦供應鏈簽訂新合約,加州沙漠中一度停產的鈾礦礦場,也是北美唯一的稀土礦開採和加工基地可望重新啟用。
Greener Process Grows U.S. Supply of Rare Earth Metals WEST LAFAYETTE, Indiana, May 11, 2020(ENS)
Mining for rare earth metals is about to become more environmentally and economically feasible though a process newly developed and patented at Purdue University.
These new environmentally-friendly technologies promise to be game-changers in this field and could enable the United States to create a more stable and reliable domestic source of these essential metals.
Used in computers, cell phones, DVDs, rechargeable batteries, catalytic converters, magnets, wind turbines, and fluorescent lights, and for defense in laser range-finders, guidance systems, and precision-guided weapons, these metals are difficult to mine because it is unusual to find them in concentrations high enough for economical extraction.
In addition, the detrimental environmental impact of current acid-based separation and purification of rare earth metals prohibits most companies everywhere in the world from entering the market.
China is currently the world leader in rare earth production, although it controls just 36 percent of the world’s reserves of these 17 metals. This provides an opportunity for other countries to become producers now that China is not selling rare earth materials below the cost of production as it did in the 1980s and ’90s.
But when China reduced the export quotas for rare earth metals in 2010, the costs of rare earth magnets for one wind turbine soared from $80,000 to $500,000. After China relaxed the export restrictions 18 months later, prices returned to lower levels than in 2010.
China is also the dominant consumer of rare earth metals for manufacturing electronics products for domestic and export markets. Today Japan and the United States are the second and third largest consumers of rare earth materials.
“About 60 percent of rare earth metals are used in magnets that are needed in almost everyone’s daily lives. These metals are used in electronics, airplanes, hybrid cars and even windmills,” said Nien-Hwa Linda Wang, the Purdue professor of chemistry who developed the new processes.
“We currently have one dominant foreign source for these metals and if the supply were to be limited for any reason, it would be devastating to people’s lives,” Wang said. “It’s not that the resource isn’t available in the U.S., but that we need a better, cleaner way to process these rare earth metals.”
The new patented extraction and purifying processes use ligand-assisted chromatography, a separation method that has been shown to remove and purify rare earth metals from coal ash, recycled magnets, and raw ore safely, efficiently and with virtually no detrimental environmental impact.
The production of rare earth metals is a global US$4 billion annual market that continues to grow as new electronics, computerized engines for aircraft, warships, electric automobiles, magnets, and other critical products are developed that require rare earth metals to perform. The value of the products using rare earth metals to function is valued at more than $4 trillion per year.
“Conventional methods for producing high-purity rare earth elements employ two-phase liquid–liquid extraction methods, which require thousands of mixer-settler units in series or in parallel and generate large amounts of toxic waste,” Wang said.
“We use a two-zone ligand-assisted displacement chromatography system with a new zone-splitting method that is producing high-purity (>99%) metals with high yields (>99%).”
Wang’s ligand assisted method has the potential for efficient and environmentally friendly purification of the rare earth metals from all sources of recyclates, such as waste magnets and ore-based sources and helps transform rare earth processing to a circular, sustainable process.
Joe Pekny, a Purdue professor of chemical engineering, said Wang’s innovation enables the United States to reenter the rare earth metals market in an earth-friendly, safe and sustainable way. “What’s exciting is that the U.S. has the rare earth metals to meet the growing demands of the U.S. market and other markets around the globe and reduces our dependence on foreign sources,” Pekny said.
This research was funded in part by the U.S. Department of Defense, DoD.
Now, the Defense Department is supporting the U.S. rare earth supply chain with a new contract to a once defunct uranium mine in the California desert that is now the only rare earth mining and processing site in North America.
※ 全文及圖片詳見:ENS
稀土金屬
開採
專利
友善環境
礦業
國際新聞
美國
生活環境
環境經濟
循環經濟
如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。
於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。
延伸閱讀
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?
※別再煩惱如何寫文案,掌握八大原則!
Spring容器中的組件默認是單例的,在Spring啟動時就會實例化並初始化這些對象,將其放到Spring容器中,之後,每次獲取對象時,直接從Spring容器中獲取,而不再創建對象。如果每次從Spring容器中獲取對象時,都要創建一個新的實例對象,該如何處理呢?此時就需要使用@Scope註解設置組件的作用域。
項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation
@Scope註解能夠設置組件的作用域,我們先來看@Scope註解類的源碼,如下所示。
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
@AliasFor("scopeName")
String value() default "";
/**
* Specifies the name of the scope to use for the annotated component/bean.
* <p>Defaults to an empty string ({@code ""}) which implies
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
* @since 4.2
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* @see #value
*/
@AliasFor("value")
String scopeName() default "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
從源碼中可以看出,在@Scope註解中可以設置如下值。
ConfigurableBeanFactory#SCOPE_PROTOTYPE
ConfigurableBeanFactory#SCOPE_SINGLETON
org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
很明顯,在@Scope註解中可以設置的值包括ConfigurableBeanFactory接口中的SCOPE_PROTOTYPE和SCOPE_SINGLETON,以及WebApplicationContext類中SCOPE_REQUEST和SCOPE_SESSION。這些都是什麼鬼?別急,我們來一個個查看。
首先,我們進入到ConfigurableBeanFactory接口中,發現在ConfigurableBeanFactory類中存在兩個常量的定義,如下所示。
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
/*****************此處省略N多行代碼*******************/
}
沒錯,SCOPE_SINGLETON就是singleton,SCOPE_PROTOTYPE就是prototype。
那麼,WebApplicationContext類中SCOPE_REQUEST和SCOPE_SESSION又是什麼鬼呢?就是說,當我們使用了Web容器來運行Spring應用時,在@Scope註解中可以設置WebApplicationContext類中SCOPE_REQUEST和SCOPE_SESSION的值,而SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session。
綜上,在@Scope註解中的取值如下所示。
其中,request和session作用域是需要Web環境支持的,這兩個值基本上使用不到,如果我們使用Web容器來運行Spring應用時,如果需要將組件的實例對象的作用域設置為request和session,我們通常會使用request.setAttribute(“key”,object)和session.setAttribute(“key”, object)的形式來將對象實例設置到request和session中,通常不會使用@Scope註解來進行設置。
首先,我們在io.mykit.spring.plugins.register.config包下創建PersonConfig2配置類,在PersonConfig2配置類中實例化一個Person對象,並將其放置在Spring容器中,如下所示。
package io.mykit.spring.plugins.register.config;
import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author binghe
* @version 1.0.0
* @description 測試@Scope註解設置的作用域
*/
@Configuration
public class PersonConfig2 {
@Bean("person")
public Person person(){
return new Person("binghe002", 18);
}
}
接下來,在SpringBeanTest類中創建testAnnotationConfig2()測試方法,在testAnnotationConfig2()方法中,創建ApplicationContext對象,創建完畢后,從Spring容器中按照id獲取兩個Person對象,並打印兩個對象是否是同一個對象,代碼如下所示。
@Test
public void testAnnotationConfig2(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
//從Spring容器中獲取到的對象默認是單實例的
Object person1 = context.getBean("person");
Object person2 = context.getBean("person");
System.out.println(person1 == person2);
}
由於對象在Spring容器中默認是單實例的,所以,Spring容器在啟動時就會將實例對象加載到Spring容器中,之後,每次從Spring容器中獲取實例對象,直接將對象返回,而不必在創建新對象實例,所以,此時testAnnotationConfig2()方法會輸出true。如下所示。
這也驗證了我們的結論:對象在Spring容器中默認是單實例的,Spring容器在啟動時就會將實例對象加載到Spring容器中,之後,每次從Spring容器中獲取實例對象,直接將對象返回,而不必在創建新對象實例。
修改Spring容器中組件的作用域,我們需要藉助於@Scope註解,此時,我們將PersonConfig2類中Person對象的作用域修改成prototype,如下所示。
@Configuration
public class PersonConfig2 {
@Scope("prototype")
@Bean("person")
public Person person(){
return new Person("binghe002", 18);
}
}
其實,使用@Scope設置作用域就等同於在XML文件中為bean設置scope作用域,如下所示。
此時,我們再次運行SpringBeanTest類的testAnnotationConfig2()方法,此時,從Spring容器中獲取到的person1對象和person2對象還是同一個對象嗎?
通過輸出結果可以看出,此時,輸出的person1對象和person2對象已經不是同一個對象了。
接下來,我們驗證下在單實例作用域下,Spring是在什麼時候創建對象的呢?
首先,我們將PersonConfig2類中的Person對象的作用域修改成單實例,並在返回Person對象之前打印相關的信息,如下所示。
@Configuration
public class PersonConfig2 {
@Scope
@Bean("person")
public Person person(){
System.out.println("給容器中添加Person....");
return new Person("binghe002", 18);
}
}
接下來,我們在SpringBeanTest類中創建testAnnotationConfig3()方法,在testAnnotationConfig3()方法中,我們只創建Spring容器,如下所示。
@Test
public void testAnnotationConfig3(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
}
此時,我們運行SpringBeanTest類中的testAnnotationConfig3()方法,輸出的結果信息如下所示。
從輸出的結果信息可以看出,Spring容器在創建的時候,就將@Scope註解標註為singleton的組件進行了實例化,並加載到Spring容器中。
接下來,我們運行SpringBeanTest類中的testAnnotationConfig2(),結果信息如下所示。
說明,Spring容器在啟動時,將單實例組件實例化之後,加載到Spring容器中,以後每次從容器中獲取組件實例對象,直接返回相應的對象,而不必在創建新對象。
如果我們將對象的作用域修改成多實例,那什麼時候創建對象呢?
此時,我們將PersonConfig2類的Person對象的作用域修改成多實例,如下所示。
@Configuration
public class PersonConfig2 {
@Scope("prototype")
@Bean("person")
public Person person(){
System.out.println("給容器中添加Person....");
return new Person("binghe002", 18);
}
}
我們再次運行SpringBeanTest類中的testAnnotationConfig3()方法,輸出的結果信息如下所示。
可以看到,終端並沒有輸出任何信息,說明在創建Spring容器時,並不會實例化和加載多實例對象,那多實例對象是什麼時候實例化的呢?接下來,我們在SpringBeanTest類中的testAnnotationConfig3()方法中添加一行獲取Person對象的代碼,如下所示。
@Test
public void testAnnotationConfig3(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Object person1 = context.getBean("person");
}
此時,我們再次運行SpringBeanTest類中的testAnnotationConfig3()方法,結果信息如下所示。
從結果信息中,可以看出,當向Spring容器中獲取Person實例對象時,Spring容器實例化了Person對象,並將其加載到Spring容器中。
那麼,問題來了,此時Spring容器是否只實例化一個Person對象呢?我們在SpringBeanTest類中的testAnnotationConfig3()方法中再添加一行獲取Person對象的代碼,如下所示。
@Test
public void testAnnotationConfig3(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Object person1 = context.getBean("person");
Object person2 = context.getBean("person");
}
此時,我們再次運行SpringBeanTest類中的testAnnotationConfig3()方法,結果信息如下所示。
從輸出結果可以看出,當對象的Scope作用域為多實例時,每次向Spring容器獲取對象時,都會創建一個新的對象並返回。此時,獲取到的person1和person2就不是同一個對象了,我們也可以打印結果信息來進行驗證,此時在SpringBeanTest類中的testAnnotationConfig3()方法中打印兩個對象是否相等,如下所示。
@Test
public void testAnnotationConfig3(){
ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig2.class);
Object person1 = context.getBean("person");
Object person2 = context.getBean("person");
System.out.println(person1 == person2);
}
此時,我們再次運行SpringBeanTest類中的testAnnotationConfig3()方法,結果信息如下所示。
可以看到,當對象是多實例時,每次從Spring容器中獲取對象時,都會創建新的實例對象,並且每個實例對象都不相等。
單例bean是整個應用共享的,所以需要考慮到線程安全問題,之前在玩springmvc的時候,springmvc中controller默認是單例的,有些開發者在controller中創建了一些變量,那麼這些變量實際上就變成共享的了,controller可能會被很多線程同時訪問,這些線程併發去修改controller中的共享變量,可能會出現數據錯亂的問題;所以使用的時候需要特別注意。
多例bean每次獲取的時候都會重新創建,如果這個bean比較複雜,創建時間比較長,會影響系統的性能,這個地方需要注意。
如果Spring內置的幾種sope都無法滿足我們的需求的時候,我們可以自定義bean的作用域。
自定義Scope主要分為三個步驟,如下所示。
(1)實現Scope接口
我們先來看下Scope接口的定義,如下所示。
package org.springframework.beans.factory.config;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.lang.Nullable;
public interface Scope {
/**
* 返回當前作用域中name對應的bean對象
* name:需要檢索的bean的名稱
* objectFactory:如果name對應的bean在當前作用域中沒有找到,那麼可以調用這個ObjectFactory來創建這個對象
**/
Object get(String name, ObjectFactory<?> objectFactory);
/**
* 將name對應的bean從當前作用域中移除
**/
@Nullable
Object remove(String name);
/**
* 用於註冊銷毀回調,如果想要銷毀相應的對象,則由Spring容器註冊相應的銷毀回調,而由自定義作用域選擇是不是要銷毀相應的對象
*/
void registerDestructionCallback(String name, Runnable callback);
/**
* 用於解析相應的上下文數據,比如request作用域將返回request中的屬性。
*/
@Nullable
Object resolveContextualObject(String key);
/**
* 作用域的會話標識,比如session作用域將是sessionId
*/
@Nullable
String getConversationId();
}
(2)將Scope註冊到容器
需要調用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope的方法,看一下這個方法的聲明
/**
* 向容器中註冊自定義的Scope
*scopeName:作用域名稱
* scope:作用域對象
**/
void registerScope(String scopeName, Scope scope);
(3)使用自定義的作用域
定義bean的時候,指定bean的scope屬性為自定義的作用域名稱。
例如,我們來實現一個線程級別的bean作用域,同一個線程中同名的bean是同一個實例,不同的線程中的bean是不同的實例。
這裏,要求bean在線程中是共享的,所以我們可以通過ThreadLocal來實現,ThreadLocal可以實現線程中數據的共享。
此時,我們在io.mykit.spring.plugins.register.scope包下新建ThreadScope類,如下所示。
package io.mykit.spring.plugins.register.scope;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.lang.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 自定義本地線程級別的bean作用域,不同的線程中對應的bean實例是不同的,同一個線程中同名的bean是同一個實例
*/
public class ThreadScope implements Scope {
public static final String THREAD_SCOPE = "thread";
private ThreadLocal<Map<String, Object>> beanMap = new ThreadLocal() {
@Override
protected Object initialValue() {
return new HashMap<>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object bean = beanMap.get().get(name);
if (Objects.isNull(bean)) {
bean = objectFactory.getObject();
beanMap.get().put(name, bean);
}
return bean;
}
@Nullable
@Override
public Object remove(String name) {
return this.beanMap.get().remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
//bean作用域範圍結束的時候調用的方法,用於bean清理
System.out.println(name);
}
@Nullable
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Nullable
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
在ThreadScope類中,我們定義了一個常量THREAD_SCOPE,在定義bean的時候給scope使用。
接下來,我們在io.mykit.spring.plugins.register.config包下創建PersonConfig3類,並使用@Scope(“thread”)註解標註Person對象的作用域為Thread範圍,如下所示。
package io.mykit.spring.plugins.register.config;
import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
* @author binghe
* @version 1.0.0
* @description 測試@Scope註解設置的作用域
*/
@Configuration
public class PersonConfig3 {
@Scope("thread")
@Bean("person")
public Person person(){
System.out.println("給容器中添加Person....");
return new Person("binghe002", 18);
}
}
最後,我們在SpringBeanTest類中創建testAnnotationConfig4()方法,在testAnnotationConfig4()方法中創建Spring容器,並向Spring容器中註冊ThreadScope對象,接下來,使用循環創建兩個Thread線程,並分別在每個線程中獲取兩個Person對象,如下所示。
@Test
public void testAnnotationConfig4(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig3.class);
//向容器中註冊自定義的scope
context.getBeanFactory().registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());
//使用容器獲取bean
for (int i = 0; i < 2; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread() + "," + context.getBean("person"));
System.out.println(Thread.currentThread() + "," + context.getBean("person"));
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
此時,我們運行SpringBeanTest類的testAnnotationConfig4()方法,輸出的結果信息如下所示。
從輸出中可以看到,bean在同樣的線程中獲取到的是同一個bean的實例,不同的線程中bean的實例是不同的。
注意:這裏,我將Person類進行了相應的調整,去掉Lombok的註解,手動寫構造函數和setter與getter方法,如下所示。
package io.mykit.spring.bean;
import java.io.Serializable;
/**
* @author binghe
* @version 1.0.0
* @description 測試實體類
*/
public class Person implements Serializable {
private static final long serialVersionUID = 7387479910468805194L;
private String name;
private Integer age;
public Person() {
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!
項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation
如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※教你寫出一流的銷售文案?
※超省錢租車方案
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※回頭車貨運收費標準