AspNetCore7.0源碼解讀之UseMiddleware

前言

本文編寫時源碼參考github倉庫主分支。

aspnetcore提供了Use方法供開發者自定義中間件,該方法接收一個委托對象,該委托接收一個RequestDelegate對象,并返回一個RequestDelegate對象,方法定義如下:

IApplicationBuilder?Use(Func<RequestDelegate,?RequestDelegate>?middleware);

委托RequestDelegate的定義

///?<summary>
///?A?function?that?can?process?an?HTTP?request.
///?</summary>
///?<param?name="context">The?<see?cref="HttpContext"/>?for?the?request.</param>
///?<returns>A?task?that?represents?the?completion?of?request?processing.</returns>
public?delegate?Task?RequestDelegate(HttpContext?context);

如果我們直接使用IApplicationBuilder.Use來寫中間件邏輯,可以使用lamda表達式來簡化代碼,如下:

app.Use((RequestDelegate?next)?=>
{return?(HttpContext?ctx)?=>{//?do?your?logicreturn?next(ctx);};
});

如果寫一些簡單的邏輯,這種方式最為方便,問題是如果需要寫的中間件代碼比較多,依然這樣去寫,會導致我們Program.cs文件代碼非常多,如果有多個中間件,那么最后我們的的Program.cs文件包含多個中間件代碼,看上去十分混亂。

將中間件邏輯獨立出來

為了解決我們上面的代碼不優雅,我們希望能將每個中間件業務獨立成一個文件,多個中間件代碼不混亂的搞到一起。我們需要這樣做。

單獨的中間件文件

// Middleware1.cs
public?class?Middleware1
{public?static?RequestDelegate?Logic(RequestDelegate?requestDelegate){return?(HttpContext?ctx)?=>{//?do?your?logicreturn?requestDelegate(ctx);};}
}

調用中間件

app.Use(Middleware1.Logic);
// 以下是其他中間件示例
app.Use(Middleware2.Logic);
app.Use(Middleware3.Logic);
app.Use(Middleware4.Logic);

這種方式可以很好的將各個中間件邏輯獨立出來,Program.cs此時變得十分簡潔,然而我們還不滿足這樣,因為我們的Logic方法中直接返回一個lamada表達式(RequestDelegate對象),代碼層級深了一層,每個中間件都多寫這一層殼似乎不太優雅,能不能去掉這層lamada表達式呢?

UseMiddlewareExtensions

為了解決上面提到的痛點,UseMiddlewareExtensions擴展類應運而生,它在Aspnetcore底層大量使用,它主要提供一個泛型UseMiddleware<T>方法用來方便我們注冊中間件,下面是該方法的定義

public?static?IApplicationBuilder?UseMiddleware<TMiddleware>(this?IApplicationBuilder?app,?params?object?[]?args)

如果只看這個方法的聲明,估計沒人知道如何使用,因為該方法接收的泛型參數TMiddleware沒有添加任何限制,而另一個args參數也是object類型,而且是可以不傳的,也就是它只需要傳任意一個類型都不會在編譯時報錯。 比如這樣,完全不會報錯:

d5dbc8edcf78500e0c3c8b71cec5b6b8.png當然,如果你這樣就運行程序,一定會收到下面的異常

System.InvalidOperationException:“No public 'Invoke' or 'InvokeAsync' method found for middleware of type 'System.String'.”

提示我們傳的類型沒有InvokeInvokeAsync公共方法,這里大概能猜到,底層應該是通過反射進行動態調用InvokeInvokeAsync公共方法的。

源碼分析

想要知道其本質,唯有查看源碼,以下源碼來自UseMiddlewareExtensions

如下,該擴展類一共提供兩個并且是重載的公共方法UseMiddleware,一般都只會使用第一個UseMiddleware,第一個UseMiddleware方法內部再去調用第二個UseMiddleware方法,源碼中對類型前面添加的[DynamicallyAccessedMembers(MiddlewareAccessibility)]屬性可以忽略,它的作用是為了告訴編譯器我們通過反射訪問的范圍,以防止對程序集對我們可能調用的方法或屬性等進行裁剪。

internal?const?string?InvokeMethodName?=?"Invoke";
internal?const?string?InvokeAsyncMethodName?=?"InvokeAsync";///?<summary>
///?Adds?a?middleware?type?to?the?application's?request?pipeline.
///?</summary>
///?<typeparam?name="TMiddleware">The?middleware?type.</typeparam>
///?<param?name="app">The?<see?cref="IApplicationBuilder"/>?instance.</param>
///?<param?name="args">The?arguments?to?pass?to?the?middleware?type?instance's?constructor.</param>
///?<returns>The?<see?cref="IApplicationBuilder"/>?instance.</returns>
public?static?IApplicationBuilder?UseMiddleware<[DynamicallyAccessedMembers(MiddlewareAccessibility)]?TMiddleware>(this?IApplicationBuilder?app,?params?object?[]?args)
{return?app.UseMiddleware(typeof(TMiddleware),?args);
}///?<summary>
///?Adds?a?middleware?type?to?the?application's?request?pipeline.
///?</summary>
///?<param?name="app">The?<see?cref="IApplicationBuilder"/>?instance.</param>
///?<param?name="middleware">The?middleware?type.</param>
///?<param?name="args">The?arguments?to?pass?to?the?middleware?type?instance's?constructor.</param>
///?<returns>The?<see?cref="IApplicationBuilder"/>?instance.</returns>
public?static?IApplicationBuilder?UseMiddleware(this?IApplicationBuilder?app,[DynamicallyAccessedMembers(MiddlewareAccessibility)]?Type?middleware,params?object?[]?args)
{if?(typeof(IMiddleware).IsAssignableFrom(middleware)){//?IMiddleware?doesn't?support?passing?args?directly?since?it's//?activated?from?the?containerif?(args.Length?>?0){throw?new?NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return?UseMiddlewareInterface(app,?middleware);}var?applicationServices?=?app.ApplicationServices;var?methods?=?middleware.GetMethods(BindingFlags.Instance?|?BindingFlags.Public);MethodInfo??invokeMethod?=?null;foreach?(var?method?in?methods){if?(string.Equals(method.Name,?InvokeMethodName,?StringComparison.Ordinal)?||?string.Equals(method.Name,?InvokeAsyncMethodName,?StringComparison.Ordinal)){if?(invokeMethod?is?not?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName,?InvokeAsyncMethodName));}invokeMethod?=?method;}}if?(invokeMethod?is?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName,?InvokeAsyncMethodName,?middleware));}if?(!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType)){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName,?InvokeAsyncMethodName,?nameof(Task)));}var?parameters?=?invokeMethod.GetParameters();if?(parameters.Length?==?0?||?parameters[0].ParameterType?!=?typeof(HttpContext)){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName,?InvokeAsyncMethodName,?nameof(HttpContext)));}var?state?=?new?InvokeMiddlewareState(middleware);return?app.Use(next?=>{var?middleware?=?state.Middleware;var?ctorArgs?=?new?object[args.Length?+?1];ctorArgs[0]?=?next;Array.Copy(args,?0,?ctorArgs,?1,?args.Length);var?instance?=?ActivatorUtilities.CreateInstance(app.ApplicationServices,?middleware,?ctorArgs);if?(parameters.Length?==?1){return?(RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate),?instance);}var?factory?=?Compile<object>(invokeMethod,?parameters);return?context?=>{var?serviceProvider?=?context.RequestServices????applicationServices;if?(serviceProvider?==?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}return?factory(instance,?context,?serviceProvider);};});
}

第一個UseMiddleware可以直接跳過,看第二個UseMiddleware方法,該方法一上來就先判斷我們傳的泛型類型是不是IMiddleware接口的派生類,如果是,直接交給UseMiddlewareInterface方法。

if?(typeof(IMiddleware).IsAssignableFrom(middleware)){//?IMiddleware?doesn't?support?passing?args?directly?since?it's//?activated?from?the?containerif?(args.Length?>?0){throw?new?NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return?UseMiddlewareInterface(app,?middleware);}

這里總算看到應該有的東西了,如果聲明UseMiddleware<T>方法時,對泛型T添加IMiddleware限制,我們不看源碼就知道如何編寫我們的中間件邏輯了,只需要寫一個類,繼承IMiddleware并實現InvokeAsync方法即可, UseMiddlewareInterface方法的實現比較簡單,因為我們繼承了接口,邏輯相對會簡單點。

private?static?IApplicationBuilder?UseMiddlewareInterface(IApplicationBuilder?app,Type?middlewareType)
{return?app.Use(next?=>{return?async?context?=>{var?middlewareFactory?=?(IMiddlewareFactory?)context.RequestServices.GetService(typeof(IMiddlewareFactory));if?(middlewareFactory?==?null){//?No?middleware?factorythrow?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));}var?middleware?=?middlewareFactory.Create(middlewareType);if?(middleware?==?null){//?The?factory?returned?null,?it's?a?broken?implementationthrow?new?InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(),?middlewareType));}try{await?middleware.InvokeAsync(context,?next);}finally{middlewareFactory.Release(middleware);}};});
}
public?interface?IMiddleware
{///?<summary>///?Request?handling?method.///?</summary>///?<param?name="context">The?<see?cref="HttpContext"/>?for?the?current?request.</param>///?<param?name="next">The?delegate?representing?the?remaining?middleware?in?the?request?pipeline.</param>///?<returns>A?<see?cref="Task"/>?that?represents?the?execution?of?this?middleware.</returns>Task?InvokeAsync(HttpContext?context,?RequestDelegate?next);
}

如果我們的類不滿足IMiddleware,繼續往下看

通過反射查找泛型類中InvokeInvokeAsync方法

var?applicationServices?=?app.ApplicationServices;
var?methods?=?middleware.GetMethods(BindingFlags.Instance?|?BindingFlags.Public);
MethodInfo??invokeMethod?=?null;
foreach?(var?method?in?methods)
{if?(string.Equals(method.Name,?InvokeMethodName,?StringComparison.Ordinal)?||?string.Equals(method.Name,?InvokeAsyncMethodName,?StringComparison.Ordinal)){// 如果Invoke和InvokeAsync同時存在,則拋出異常,也就是,我們只能二選一if?(invokeMethod?is?not?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName,?InvokeAsyncMethodName));}invokeMethod?=?method;}
}// 如果找不到Invoke和InvokeAsync則拋出異常,上文提到的那個異常。
if?(invokeMethod?is?null)
{throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName,?InvokeAsyncMethodName,?middleware));
}// 如果Invoke和InvokeAsync方法的返回值不是Task或Task的派生類,則拋出異常
if?(!typeof(Task).IsAssignableFrom(invokeMethod.ReturnType))
{throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName,?InvokeAsyncMethodName,?nameof(Task)));
}
Snippet// 如果Invoke和InvokeAsync方法沒有參數,或第一個參數不是HttpContext,拋異常
var?parameters?=?invokeMethod.GetParameters();
if?(parameters.Length?==?0?||?parameters[0].ParameterType?!=?typeof(HttpContext))
{throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName,?InvokeAsyncMethodName,?nameof(HttpContext)));
}

上面一堆邏輯主要就是檢查我們的InvokeInvokeAsync方法是否符合要求,即:必須是接收HttpContext參數,返回Task對象,這恰好就是委托RequestDelegate的定義。

構造RequestDelegate這部分源碼的解讀都注釋到相應的位置了,如下

var?state?=?new?InvokeMiddlewareState(middleware);
// 調用Use函數,向管道中注冊中間件
return?app.Use(next?=>
{var?middleware?=?state.Middleware;var?ctorArgs?=?new?object[args.Length?+?1];// next是RequestDelegate對象,作為構造函數的第一個參數傳入ctorArgs[0]?=?next;Array.Copy(args,?0,?ctorArgs,?1,?args.Length);// 反射實例化我們傳入的泛型類,并把next和args作為構造函數的參數傳入var?instance?=?ActivatorUtilities.CreateInstance(app.ApplicationServices,?middleware,?ctorArgs);// 如果我們的Invoke方法只有一個參數,則直接創建該方法的委托if?(parameters.Length?==?1){return?(RequestDelegate)invokeMethod.CreateDelegate(typeof(RequestDelegate),?instance);}// 當Invoke方法不止一個參數HttpContext,通過Compile函數創建動態表達式目錄樹,// 表達式目錄樹的構造此處略過,其目的是實現將除第一個參數的其他參數通過IOC注入var?factory?=?Compile<object>(invokeMethod,?parameters);return?context?=>{// 獲取serviceProvider用于在上面構造的表達式目錄樹中實現依賴注入var?serviceProvider?=?context.RequestServices????applicationServices;if?(serviceProvider?==?null){throw?new?InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));}// 將所需的參數傳入構造的表達式目錄樹工廠return?factory(instance,?context,?serviceProvider);};
});

至此,整個擴展類的源碼就解讀完了。

通過UseMiddleware注入自定義中間件

通過上面的源碼解讀,我們知道了其實我們傳入的泛型類型是有嚴格的要求的,主要有兩種

通過繼承IMiddleware

繼承IMiddleware并實現該接口的InvokeAsync函數

public?class?Middleware1?:?IMiddleware
{public?async?Task?InvokeAsync(HttpContext?context,?RequestDelegate?next){//?do?your?logicawait?next(context);}
}

通過反射

我們知道,在不繼承IMiddleware的情況下,底層會通過反射實例化泛型類型,并通過構造函數傳入RequestDelegate,而且要有一個公共函數InvokeInvokeAsync,并且接收的第一個參數是HttpContext,返回Task,根據要求我們將Middleware1.cs改造如下

public?class?Middleware1
{RequestDelegate?next;public?Middleware1(RequestDelegate?next){this.next?=?next;}public?async?Task?Invoke(HttpContext?httpContext){//?do?your?logicawait?this.next(httpContext);}
}

總結

通過源碼的學習,我們弄清楚底層注冊中間件的來龍去脈,兩種方式根據自己習慣進行使用,筆者認為通過接口的方式更加簡潔直觀簡單,并且省去了反射帶來的性能損失,推薦使用。既然通過繼承接口那么爽,為啥還費那么大勁實現反射的方式呢?由源碼可知,如果繼承接口的話,就不能進行動態傳參了。

if?(typeof(IMiddleware).IsAssignableFrom(middleware)){//?IMiddleware?doesn't?support?passing?args?directly?since?it's//?activated?from?the?containerif?(args.Length?>?0){throw?new?NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));}return?UseMiddlewareInterface(app,?middleware);}

所以在需要傳參的場景,則必須使用反射的方式,所以兩種方式都有其存在的必要。

如果本文對您有幫助,還請點贊轉發關注一波支持作者。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287816.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287816.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287816.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

邊工作邊刷題:70天一遍leetcode: day 11-3

Single Number I/II II的python解是網上抄的&#xff0c;其實可以AC&#xff0c;但是python不會像c/java那樣自動overflow&#xff0c;而是轉化成long。所以如果有負數的情況會得到一個巨大的正數解&#xff0c;比如 Input:[-2,-2,1,1,-3,1,-3,-3,-4,-2] Output:4294967292 Exp…

《零基礎看得懂的C語言入門教程 》——(五)C語言的變量、常量及運算

一、學習目標 了解C語言變量的其它創建方式了解C語言常量了解C語言的運算符 目錄 C語言真的很難嗎&#xff1f;那是你沒看這張圖&#xff0c;化整為零輕松學習C語言。 第一篇&#xff1a;&#xff08;一&#xff09;脫離學習誤區 第二篇&#xff1a;&#xff08;二&#xff…

實戰使用Axure設計App,使用WebStorm開發(4) – 實現頁面UI

系列文章 實戰使用Axure設計App,使用WebStorm開發(1) – 用Axure描述需求 實戰使用Axure設計App,使用WebStorm開發(2) – 創建 Ionic 項目 實戰使用Axure設計App,使用WebStorm開發(3) – 構建頁面架構 實戰使用Axure設計App,使用WebStorm開發(4) – 實現頁面UI 實戰使用Axu…

ArcGIS實驗教程——實驗二十:ArcGIS數字高程模型DEM建立

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據) 一、實驗描述 本實驗講述DEM的創建方法和過程。DEM的采集方法有四種:地面測量、攝影測量、空間站、地形圖數字化。 二、實驗內容 1、插值法DEM建立 2、創建TIN 3、TIN轉柵格 4、生成等高線 …

思科asa5515端口映射_Cisco ASA端口映射

SQL基礎--同義詞同義詞的概念: 同義詞是Oracle對象的別名,使用同義詞訪問相同的對象 可以為表.視圖.存儲過程.函數或另一同義詞等對象創建同義詞 方便訪問其它用戶的對象,隱藏了對象的身份 縮短對象名字的長度 同義 ...訪問本地json文件因跨域導致的問題我使用jquery的getJSON的…

英文詞頻統計預備,組合數據類型練習

實例: 下載一首英文的歌詞或文章&#xff0c;將所有,.&#xff1f;&#xff01;等替換為空格&#xff0c;將所有大寫轉換為小寫&#xff0c;統計某幾個單詞出現的次數&#xff0c;分隔出一個一個的單詞。2.列表實例&#xff1a;由字符串創建一個作業評分列表&#xff0c;做增刪…

ArcGIS實驗教程——實驗二十一:DEM分析

ArcGIS實驗視頻教程合集:《ArcGIS實驗教程從入門到精通》(附配套實驗數據) 一、實驗描述 表面分析主要通過生成新數據集,如等值線、坡度、坡向、山體陰影等派生數據,獲取更多的反應原始數據集中所暗含的空間特征、空間格局等信息。 二、實驗內容 1、地形因子計算 2、填…

《零基礎看得懂的C語言入門教程 》——(六)輕輕松松了解C語言的邏輯運算

一、學習目標 了解邏輯判斷的概念了解if語句的使用方法了解switch語句的使用方法了解邏輯運算符的使用方法 目錄 C語言真的很難嗎&#xff1f;那是你沒看這張圖&#xff0c;化整為零輕松學習C語言。 第一篇&#xff1a;&#xff08;一&#xff09;脫離學習誤區 第二篇&#…

.NET 6 在 Win7 系統證書鏈錯誤導致 HttpWebRequest 內存泄露

本文記錄我將應用遷移到 dotnet 6 之后&#xff0c;在 Win7 系統上&#xff0c;因為使用 HttpWebRequest 訪問一個本地服務&#xff0c;此本地服務開啟 https 且證書鏈在此 Win7 系統上錯誤&#xff0c;導致應用內存泄露問題。本文記錄此問題的原因以及調查過程核心原因核心原因…

個人成就故事

曾經參加過文藝演出&#xff0c;并且照片上過學校的文化墻。 中專時畫的園林景觀獲過獎。 在麥當勞打工時參加過大型活動&#xff0c;并且擔任小隊副隊長。 在學校堅持鍛煉練出了一些腹肌轉載于:https://www.cnblogs.com/mokutanuki/p/5486567.html

Linux下java -version版本不對

在服務器上更新了新的 jdk&#xff0c;也在 /etc/profile 中設置了新的 JAVA_HOME&#xff0c;并且 source /etc/profile 然后使用java -version 和 javac -version 發現版本還是老版本&#xff0c;死活沒有使用我新指定的。 中間各種排查&#xff0c;這里就不廢話了&#xff0…

JAVA-JSP內置對象

相關資料&#xff1a;《21天學通Java Web開發》 request 請求對象 類型javax.servlet.ServletRequest 作用域Requestresponse 響應對象 類型javax.servlet.SrvletResponse 作用域PagepageContext 頁面上下文對象 類型 javax.servlet.jsp.PageContext 作用域Pagesession 會話對象…

TCP之滑動窗口

一、滑動窗口的基本知識 TCP滑動窗口包含了發送窗口和接收窗口 1)、TCP滑動窗口的最大值 TCP數據包頭部里面有個窗口值,默認窗口是一個16bit位字段,表示窗口的字節容量,所以TCP滑動窗口的最大值是2^16-1=65535個字節,TCP里面也有窗口擴大因子可把原來16bit的窗口,擴大為…

《零基礎看得懂的C語言入門教程 》——(七)C語言的循環分分鐘上手

一、學習目標 了解循環的使用方法 目錄 C語言真的很難嗎&#xff1f;那是你沒看這張圖&#xff0c;化整為零輕松學習C語言。 第一篇&#xff1a;&#xff08;一&#xff09;脫離學習誤區 第二篇&#xff1a;&#xff08;二&#xff09;C語言沒那么難簡單開發帶你了解流程 第…

ArcGIS中數據存放相對路徑和絕對路徑的區別

配套藍光視頻教程:【ArcGIS風暴】數據相對路徑VS絕對路徑 問題舉例: 菜鳥們在使用ArcGIS時經常會碰到將地圖文檔(.mxd)拷貝到別的電腦上或改變一個路徑時,出現數據丟失的現象,具體表現為圖層前面出現一個紅色的感嘆號,如下圖所示。 出現以上問題的根本原因是數據GSS.ti…

TIOBE 5 月編程語言排行榜:C# 最受開發者歡迎,C++ 將沖擊 Top 3

技術迭代的速度越來越快&#xff0c;這一點在每月更新一次的編程語言排行榜榜單中體現得尤為明顯。今天&#xff0c;最新的 TIOBE 5 月編程語言榜單出爐&#xff0c;不妨一起來看一下又有哪些新的趨勢。C# 的使用量增幅最高&#xff0c;C 或將沖擊 Top 3和 4 月相比&#xff0c…

HTTP生命周期

HTTP生命周期   Http 請求   AspNet_ISAIP.DLL (ISAPI擴展&#xff0c;獨立于站點外&#xff0c;用于可擴展的橋梁)&#xff0c;   w3wp.exe (net工作進程) IIS6 以上&#xff0c;6以下為aspnet_wp.exe     判斷并創建AppDomain(包含程序集信息)&#xff0c;請求轉發…

SQL Server2016導出數據表數據

SQL Server2016導出數據表數據我們前面已經介紹了很多關于SQL Server的相關文章&#xff0c;今天我們主要介紹的是&#xff0c;如何導出數據庫下表中數據。我們所有的操作都是通過SSMS進行操作的。我們右擊需要導出數據的數據庫----任務----導出數據根據向導提示&#xff0c;下…

Jfinal 顯示歡迎頁 index.jsp

為什么80%的碼農都做不了架構師&#xff1f;>>> IndexController.index()方法&#xff0c;為什么是index()方法&#xff1f;其實這是一個約定 那么它是如何打開index.jsp文件的呢&#xff1f;我們來查看index()方法的代碼&#xff1a; public class IndexControlle…

Java之通過Collections.synchronizedMap創建線程安全的HashMap

1 問題 我們知道hashMap線程是不安全的&#xff0c;一般而言&#xff0c;我們怎么創建線程安全的HashMap呢&#xff1f; 2 解決辦法 我們可以使用Collections.synchronizedMap來創建HashMap,如下 static Map<String, String> results Collections.synchronizedMap(ne…