全新升級的AOP框架Dora.Interception[2]: 基于約定的攔截器定義方式

Dora.Interception(github地址,覺得不錯不妨給一顆星)有別于其他AOP框架的最大的一個特點就是采用針對“約定”的攔截器定義方式。如果我們為攔截器定義了一個接口或者基類,那么攔截方法將失去任意注冊依賴服務的靈活性。除此之外,由于我們采用了動態代碼生成的機制,我們可以針對每一個目標方法生成對應的方法調用上下文,所以定義在攔截上下文上針對參數和返回值的提取和設置都是泛型方法,這樣可以避免無謂的裝箱和拆箱操作,進而將引入攔截帶來的性能影響降到最低。

目錄
一、方法調用上下文
二、攔截器類型約定
三、提取調用上下文信息
四、修改輸出參數和返回值
五、控制攔截器的執行順序
六、短路返回
七、構造函數注入
八、方法注入
九、ASP.NET Core應用的適配

一、方法調用上下文

針對同一個方法調用的所有攔截器都是在同一個方法調用上下文中進行的,我們將這個上下文定義成如下這個InvocationContext基類。我們可以利用Target和MethodInfo屬性得到當前方法調用的目標對象和目標方法。泛型的GetArgument和SetArgument用于返回和修改傳入的參數,針對返回值的提取和設置則通過GetReturnValue和SetReturnValue方法來完成。如果需要利用此上下文傳遞數據,可以將其置于Properties屬性返回的字典中。InvocationServices屬性返回針對當前方法調用范圍的IServiceProvider。如果在ASP.NET Core應用中,這個屬性將返回針對當前請求的IServiceProvider,否則Dora.Interception會為每次方法調用創建一個服務范圍,并返回該范圍內的IServiceProvider對象。

public?abstract?class?InvocationContext
{public?object?Target?{?get;?}public?abstract?MethodInfo?MethodInfo?{?get;?}public?abstract?IServiceProvider?InvocationServices?{?get;?}public?IDictionary<object,?object>?Properties?{?get;?}?public?abstract?TArgument?GetArgument<TArgument>(string?name);public?abstract?TArgument?GetArgument<TArgument>(int?index);public?abstract?InvocationContext?SetArgument<TArgument>(string?name,?TArgument?value);public?abstract?InvocationContext?SetArgument<TArgument>(int?index,?TArgument?value);public?abstract?TReturnValue?GetReturnValue<TReturnValue>();public?abstract?InvocationContext?SetReturnValue<TReturnValue>(TReturnValue?value);protected?InvocationContext(object?target);public?ValueTask?ProceedAsync()?=>?Next.Invoke(this);
}

和ASP.NET Core的中間件管道類似,應用到同一個方法上的所有攔截器最終也會根據指定的順序構建成管道。對于某個具體的攔截器來說,是否需要指定后續管道的操作是由它自己決定的。我們知道ASP.NET Core的中間件最終體現為一個Func<RequestDelegate,RequestDelegate>委托,作為輸入的RequestDelegate委托代表后續的中間件管道,當前中間件利用它實現針對后續管道的調用。Dora.Interception針對攔截器采用了更為簡單的設計,將其表示為如下這個InvokeDelegate(相當于RequestDelegate),因為InvocationContext(相當于HttpContext)的ProceedAsync方法直接可以幫助我們完整針對后續管道的調用。

public?delegate?ValueTask?InvokeDelegate(InvocationContext?context);

二、攔截器類型約定

雖然攔截器最終體現為一個InvokeDelegate對象,但是我們傾向于將其定義成一個類型。作為攔截器的類型具有如下的約定:

  • 必須是一個公共的實例類型;

  • 必須包含一個或者多個公共構造函數,針對構造函數的選擇由依賴注入框架決定。被選擇的構造函數可以包含任意參數,參數在實例化的時候由依賴注入容器提供或者手工指定。

  • 攔截方法被定義在命名為InvokeAsync的公共實例方法中,此方法的返回類型為ValueTask,其中包含一個表示方法調用上下文的InvocationContext類型的參數,能夠通過依賴注入容器提供的服務均可以注入在此方法中。

三、提取調用上下文信息

由于攔截器類型的InvokeAsync方法提供了表示調用上下文的InvocationContext參數,我們可以利用它提取基本的調用上下文信息,包括當前調用的目標對象和方法,以及傳入的參數和設置的返回值。如下這個FoobarInterceptor類型表示的攔截器會將上述的這些信息輸出到控制臺上。

public?class?FoobarInterceptor
{public?async?ValueTask?InvokeAsync(InvocationContext?invocationContext){var?method?=?invocationContext.MethodInfo;var?parameters?=?method.GetParameters();Console.WriteLine($"Target:?{invocationContext.Target}");Console.WriteLine($"Method:?{method.Name}({string.Join(",?",?parameters.Select(it?=>?it.ParameterType.Name))})");if?(parameters.Length?>?0){Console.WriteLine("Arguments?(by?index)");for?(int?index?=?0;?index?<?parameters.Length;?index++){Console.WriteLine($"{index}:{invocationContext.GetArgument<object>(index)}");}Console.WriteLine("Arguments?(by?name)");foreach?(var?parameter?in?parameters){var?parameterName?=?parameter.Name!;Console.WriteLine($"{parameterName}:{invocationContext.GetArgument<object>(parameterName)}");}}await?invocationContext.ProceedAsync();if?(method.ReturnType?!=?typeof(void)){Console.WriteLine($"Return:?{invocationContext.GetReturnValue<object>()}");}}
}

我們利用InterceptorAttribute特性將這個攔截器應用到如下這個Calculator類型的Add方法中。由于我們沒有為它定義接口,只能將它定義成虛方法才能被攔截。

public?class?Calculator
{[Interceptor(typeof(FoobarInterceptor))]public?virtual?int?Add(int?x,?int?y)?=>?x?+?y;
}

在如下這段演示程序中,在將Calculator作為服務注冊到創建的ServiceCollection集合后,我們調用BuildInterceptableServiceProvider擴展方法構建一個IServiceCollection對象。在利用它得到Calculator對象之后,我們調用其Add方法。

using?App;
using?Microsoft.Extensions.DependencyInjection;var?calculator?=?new?ServiceCollection().AddSingleton<Calculator>().BuildInterceptableServiceProvider().GetRequiredService<Calculator>();Console.WriteLine($"1?+?1?=?{calculator.Add(1,?1)}");

針對Add方法的調用會被FoobarInterceptor攔截下來,后者會將方法調用上下文信息以如下的形式輸出到控制臺上(源代碼)。

403a6e146e5132732c9b3c2b89fac6db.png

四、修改輸出參數和返回值

攔截器可以篡改輸出的參數值,比如我們將上述的FoobarInterceptor類型改寫成如下的形式,它的InvokeAsync方法會將輸入的兩個參數設置為0(源代碼)。

public?class?FoobarInterceptor
{public?ValueTask?InvokeAsync(InvocationContext?invocationContext){invocationContext.SetArgument("x",?0);invocationContext.SetArgument("y",?0);return?invocationContext.ProceedAsync();}
}

再次執行上面的程序后就會出現1+1=0的現象。

5708243dcfc207be18e25ba798653eb8.png

在完成目標方法的調用后,返回值會存儲到上下文中,攔截器也可以將其篡改。如下這個改寫的FoobarInterceptor選擇將返回值設置為0。程序執行后也會出現上面的輸出結果(源代碼)。

public?class?FoobarInterceptor
{public?async?ValueTask?InvokeAsync(InvocationContext?invocationContext){await?invocationContext.ProceedAsync();invocationContext.SetReturnValue(0);}
}

五、控制攔截器的執行順序

攔截器最終被應用到某個方法上,多個攔截器最終會構成一個由InvokeDelegate委托表示的執行管道,構造管道的攔截器的順序可以由指定的序號來控制。如下所示的代碼片段定義了三個派生于同一個基類的攔截器類型(FooInterceptor、BarInterceptor、BazInterceptor),它們會在目標方法之前后輸出當前的類型進而確定它們的執行順序。

public?class?InterceptorBase
{public?async?ValueTask?InvokeAsync(InvocationContext?invocationContext){Console.WriteLine($"[{GetType().Name}]:?Before?invoking");await?invocationContext.ProceedAsync();Console.WriteLine($"[{GetType().Name}]:?After?invoking");}
}public?class?FooInterceptor?:?InterceptorBase?{?}
public?class?BarInterceptor?:?InterceptorBase?{?}
public?class?BazInterceptor?:?InterceptorBase?{?}

我們利用InterceptorAttribute特性將這三個攔截器應用到如下這個Invoker類型的Invoke方法上。指定的Order屬性最終決定了對應的攔截器在構建管道的位置,進而決定了它們的執行順序。

public?class?Invoker
{[Interceptor(typeof(BarInterceptor),?Order?=?2)][Interceptor(typeof(BazInterceptor),?Order?=?3)][Interceptor(typeof(FooInterceptor),?Order?=?1)]public?virtual?void?Invoke()?=>?Console.WriteLine("Invoker.Invoke()");
}

在如下所示的演示程序中,我們按照上述的方式得到Invoker對象,并調用其Invoke方法。

var?invoker?=?new?ServiceCollection().AddSingleton<Invoker>().BuildInterceptableServiceProvider().GetRequiredService<Invoker>();invoker.Invoke();

按照標注InterceptorAttribute特性指定的Order屬性,三個攔截器執行順序依次是:FooInterceptor、BarInterceptor、BazInterceptor,如下所示的輸出結果體現了這一點(源代碼)。

eabccb5623c389c01e3dd70950623c32.png

六、短路返回

任何一個攔截器都可以根據需要選擇是否繼續執行后續的攔截器以及目標方法,比如入門實例中的緩存攔截器將緩存結果直接設置為調用上下文的返回值,并不再執行后續的操作。對上面定義的三個攔截器類型,我們將第二個攔截器BarInterceptor改寫成如下的形式。它的InvokeAsync在輸出一段指示性文字后,不再調用上下文的ProceedAsync方法,而是直接返回一個ValueTask對象。

public?class?BarInterceptor
{public?virtual??ValueTask?InvokeAsync(InvocationContext?invocationContext){Console.WriteLine($"[{GetType().Name}]:?InvokeAsync");return?ValueTask.CompletedTask;}
}

再次執行我們的演示程序后會發現FooInterceptor和BarInterceptor會正常執行,但是BazInterceptor目標方法均不會執行(源代碼)。

8bc0cb1b773d9f0f7d5dfff1426b32ba.png

七、構造函數注入

由于攔截器是由依賴注入容器創建的,其構造函數中可以注入依賴服務。但是攔截器具有全局生命周期,所以我們不能將生命周期模式為Scoped的服務對象注入到構造函數中。我們可以利用一個簡單的實例來演示這一點。我們定義了如下一個攔截器類型FoobarInspector,其構造函數中注入了依賴服務FoobarSerivice。FoobarInspector被采用如下的方式利用InterceptorAttribute特性應用到Invoker類型的Invoke方法上。

public?class?FoobarInterceptor
{public?FoobarInterceptor(FoobarService?foobarService)=>?Debug.Assert(foobarService?!=?null);public?async??ValueTask?InvokeAsync(InvocationContext?invocationContext){Console.WriteLine($"[{GetType().Name}]:?Before?invoking");await?invocationContext.ProceedAsync();Console.WriteLine($"[{GetType().Name}]:?After?invoking");}
}public?class?FoobarService?{?}public?class?Invoker
{[Interceptor(typeof(FoobarInterceptor))]public?virtual?void?Invoke()?=>?Console.WriteLine("Invoker.Invoke()");
}

在如下的演示程序中,我們利用命令行參數(0,1,2)來指定依賴服務FoobarService采用的生命周期,然后將其作為參數調用輔助方法Invoke方法完成必要的服務注冊,利用構建的依賴注入容器提取Invoker對象,并調用應用了FoobarInspector攔截器的Invoke方法。

var?lifetime?=?(ServiceLifetime)int.Parse(args.FirstOrDefault()????"0");
Invoke(lifetime);static?void?Invoke(ServiceLifetime?lifetime)
{Console.WriteLine(lifetime);try{var?services?=?new?ServiceCollection().AddSingleton<Invoker>();services.Add(ServiceDescriptor.Describe(typeof(FoobarService),?typeof(FoobarService),?lifetime));var?invoker?=?services.BuildInterceptableServiceProvider().GetRequiredService<Invoker>();invoker.Invoke();}catch?(Exception?ex){Console.WriteLine(ex.Message);}
}

我們以命令行參數的形式啟動程序,并指定三種不同的生命周期模式。從輸出結果可以看出,如果注冊的FoobarService服務采用Scoped生命周期模式會拋出異常(源代碼)。

36e0558640cdb887c25354c7f0dbf44c.png

八、方法注入

如果FoobarInspector依賴一個Scoped服務,或者依賴的服務采用Transient生命周期模式,但是希望在每次調用的時候創建新的對象(如果將生命周期模式設置為Transient,實際上是希望采用這樣的服務消費方式)。此時可以利用InvocationContext的InvocationServices返回的IServiceProvider對象。在如下的實例演示中,我們定義了派生于ServiceBase 的三個將會注冊為對應生命周期的服務類型SingletonService 、ScopedService 和TransientService 。為了確定依賴服務實例被創建和釋放的時機,ServiceBase實現了IDisposable接口,并在構造函數和Dispose方法中輸出相應的文字。在攔截器類型FoobarInterceptor的InvokeAsync方法中,我們利用InvocationContext的InvocationServices返回的IServiceProvider對象兩次提取這三個服務實例。FoobarInterceptor依然應用到Invoker類型的Invoke方法中。

public?class?FoobarInterceptor
{public?async??ValueTask?InvokeAsync(InvocationContext?invocationContext){var?provider?=?invocationContext.InvocationServices;_?=?provider.GetRequiredService<SingletonService>();_?=?provider.GetRequiredService<SingletonService>();_?=?provider.GetRequiredService<ScopedService>();_?=?provider.GetRequiredService<ScopedService>();_?=?provider.GetRequiredService<TransientService>();_?=?provider.GetRequiredService<TransientService>();Console.WriteLine($"[{GetType().Name}]:?Before?invoking");await?invocationContext.ProceedAsync();Console.WriteLine($"[{GetType().Name}]:?After?invoking");}
}public?class?ServiceBase?:?IDisposable
{public?ServiceBase()=>Console.WriteLine($"{GetType().Name}.new()");public?void?Dispose()?=>?Console.WriteLine($"{GetType().Name}.Dispose()");
}public?class?SingletonService?:?ServiceBase?{?}
public?class?ScopedService?:?ServiceBase?{?}
public?class?TransientService?:?ServiceBase?{?}public?class?Invoker
{[Interceptor(typeof(FoobarInterceptor))]public?virtual?void?Invoke()?=>?Console.WriteLine("Invoker.Invoke()");
}

在如下的演示程序中,我們將三個服務按照對應的生命周期模式添加到創建的ServiceCollection集合中。在構建出作為依賴注入容器的IServiceProvider對象后,我們利用它提取出Invoker對象,并先后兩次調用應用了攔截器的Invoke方法。為了釋放所有由ISerivceProvider對象提供的服務實例,我們調用了它的Dispose方法。

var?provider?=?new?ServiceCollection().AddSingleton<SingletonService>().AddScoped<ScopedService>().AddTransient<TransientService>().AddSingleton<Invoker>().BuildInterceptableServiceProvider();
using?(provider?as?IDisposable)
{var?invoker?=?provider?.GetRequiredService<Invoker>();invoker.Invoke();Console.WriteLine();invoker.Invoke();
}

程序運行后會在控制臺上輸出如下的結果,可以看出SingletonService 對象只會創建一次,并最終在作為跟容器的ISerivceProvider對象被釋放時隨之被釋放。ScopedSerivce對象每次方法調用都會創建一次,并在調用后自動被釋放。每次提取TransientService 都會創建一個新的實例,它們會在方法調用后與ScopedSerivce對象一起被釋放(源代碼)。

e4def5bc7966d850258b87de1d752f5f.png

其實利用InvocationServices提取所需的依賴服務并不是我們推薦的編程方式,更好的方式是以如下的方式將依賴服務注入攔截器的InvokeAsync方法中。上面演示程序的FoobarInterceptor改寫成如下的方式后,執行后依然會輸出如上的結果(源代碼)。

public?class?FoobarInterceptor
{public?async??ValueTask?InvokeAsync(InvocationContext?invocationContext,SingletonService?singletonService1,?SingletonService?singletonService2,ScopedService?scopedService1,?ScopedService?scopedService2,TransientService?transientService1,?TransientService?transientService2){Console.WriteLine($"[{GetType().Name}]:?Before?invoking");await?invocationContext.ProceedAsync();Console.WriteLine($"[{GetType().Name}]:?After?invoking");}
}

九、ASP.NET Core應用的適配

對于上面演示實例來說,Scoped服務所謂的“服務范圍”被綁定為單次方法調用,但是在ASP.NET Core應用應該綁定為當前的請求上下文,Dora.Interception對此做了相應的適配。我們將上面定義的FoobarInterceptor和Invoker對象應用到一個ASP.NET Core MVC程序中。為此我們定義了如下這個HomeController,其Action方法Index中注入了Invoker對象,并先后兩次調用了它的Invoke方法。

public?class?HomeController
{[HttpGet("/")]public?string?Index([FromServices]?Invoker?invoker){invoker.Invoke();Console.WriteLine();invoker.Invoke();return?"OK";}
}

MVC應用的啟動程序如下。

var?builder?=?WebApplication.CreateBuilder(args);
builder.Host.UseInterception();
builder.Services.AddLogging(logging=>logging.ClearProviders()).AddSingleton<Invoker>().AddSingleton<SingletonService>().AddScoped<ScopedService>().AddTransient<TransientService>().AddControllers();
var?app?=?builder.Build();
app.UseRouting().UseEndpoints(endpint?=>?endpint.MapControllers());
app.Run();

啟動程序后針對根路徑“/”(只想HomeController的Index方法)的請求(非初次請求)會在服務端控制臺上輸出如下的結果,可以看出ScopedSerivce對象針對每次請求只會被創建一次。

051588ad422cce09cba1c9cb8fac9604.png

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

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

相關文章

redis watch使用場景_redis不得不會的事務玩法

我們都知道redis追求的是簡單&#xff0c;快速&#xff0c;高效&#xff0c;在這種情況下也就拒絕了支持window平臺&#xff0c;學sqlserver的時候&#xff0c;我們知道事務還算是個比較復雜的東西&#xff0c;所以這吊毛要是照搬到redis中去&#xff0c;理所當然redis就不是那…

加快Android Studio的編譯速度

從Eclipse切換到Android Studio后&#xff0c;感覺Android Studio的build速度比Eclipse慢很多&#xff0c;以下幾個方法可以提高Android Studio的編譯速度使用Gradle 2.4Gradle 2.4對執行性能有很大的優化&#xff0c;但Android Studio現在默認使用的是Gradle 2.2,所以我們需要…

開發中 MySQL 規范

一、建表規范 1、數據庫名、表名、字段名必須使用小寫字母或數字&#xff0c;并且禁止以數字開頭 示例&#xff1a;goods_category、agent_operate_201812_log 2、數據庫名、表名、字段名要做到見名識意 示例&#xff1a;goods_category&#xff0c;不能 gc 3、配置表建議以 …

PaddleOCR在 Linux下的webAPI部署方案

很多小伙伴在使用OCR時都希望能采用API的方式調用&#xff0c;這樣就可以跨端跨平臺了。本文將介紹一種基于python的PaddleOCR識別WebAPI部署方案。喜歡的可以關注公眾號&#xff0c;獲取更多內容。一、 Linux環境下部署1.環境要求操作系統&#xff1a;CenterOS7&#xff1b;主…

影響程序員生涯的三個錯誤觀念,你千萬不要犯!

程序員在社會上&#xff0c;到底是怎樣一個生活群體&#xff1f;是否能找到自己方向&#xff1f;其實&#xff0c;路一直都在那里&#xff0c;只是你看不到而已&#xff01; 當初的你&#xff0c;可能一直被一些技術牽著鼻子走&#xff0c;并不是自己在做著自己想做的&#xff…

心電圖計算心率公式_心電圖到底能反應啥問題,看過之后你也能當“醫生”

只要是經歷過健康體檢的健康人&#xff0c;或者做過手術的患者&#xff0c;基本都做過心電圖檢查。都說久病成醫&#xff0c;所以有些人對血、尿常規等各項檢查的結果都門清兒得很&#xff0c;最起碼看一眼也能說出個大概齊。偏偏心電圖這種常做的檢查&#xff0c;不但老病號如…

獲取正在運行的服務

手機上安裝的App&#xff0c;在后臺運行著很多不同功能的服務&#xff0c;最常見的例如消息推送相關的服務。如何查看這些服務&#xff1f;如何判斷某個服務是否正在運行&#xff1f;如何停止某一個服務呢&#xff1f;請看下面的方法&#xff1a; package com.example.servicel…

openstack的vnc啟動ssl

1、制作ssl證書# cd /etc/pki/tls/certs [rootwww certs]# make vnc.key Enter pass phrase:# 輸入密碼 Verifying - Enter pass phrase:#確認# 從private key 中刪除密碼# openssl rsa -in vnc.key -out vnc.key # make vnc.csr Country Name (2 letter code) [XX]:CN# 國家 S…

開發composer包

一、初始化&#xff08;生成composer.json文件&#xff09; composer init#輸入你要創建的composer包項目命名空間 Package name (<vendor>/<name>) [root/tiny-laravel]: #haveyb/tiny-laravel #輸入composer包的描述 Description []:#this is a tiny laravel h…

Linux本地yum源配置以及使用yum源安裝gcc編譯環境

本文檔是圖文安裝本地yum源的教程&#xff0c;以安裝gcc編譯環境為例。 適用范圍&#xff1a;所有的cetos,紅帽,fedroa版本 適用人群&#xff1a;有一點linux基礎的小白 范例系統版本&#xff1a;CentOS Linux release 7.3.1611 (Core) 范例環境&#xff1a;vmware 虛擬機 安裝…

word如何設置上標形式_如何在word中設置特殊頁碼

獲取更多業界資訊和深度好文● 點擊藍字關注我們 ●在日常工作中&#xff0c;我們編輯的word文檔經常需要設置頁碼&#xff0c;但有時文檔的第一頁是封面&#xff0c;第二頁才是正文&#xff0c;或者第二頁是目錄&#xff0c;第三頁才是正文&#xff0c;如下圖所示&#xff0c;…

[cf797c]Minimal string(貪心+模擬)

題意&#xff1a; 給出了字符串s的內容&#xff0c;字符串t&#xff0c;u初始默認為空&#xff0c;允許做兩種操作&#xff1a; 1、把s字符串第一個字符轉移到t字符串最后 2、把t字符串最后一個字符轉移到u字符串最后 最后要求s、t字符串都為空&#xff0c;問u字符串字典序最小…

發布composer包到 Packagist,并設置自動同步(從github到Packagist)

一、發布composer包 1、將我們寫好的項目包發布到github上 這一步不贅述&#xff0c;應該都會。 但是需要注意的是&#xff0c;我們一定要為我們的項目包打上tag之后再提交&#xff0c;否則 我們composer require時可能會報錯 Could not find a version of package。 # 設置…

教你在CorelDRAW中導入位圖

在CorelDRAW軟件中不能直接打開位圖圖像&#xff0c;在實際操作中&#xff0c;用戶需要使用導入位圖圖像的方法進行操作。導入位圖圖像時&#xff0c;可以導入整幅圖像&#xff0c;也可以在導入的過程中對圖像進行裁剪&#xff0c;或重新取樣圖像&#xff0c;導入整幅位圖圖像時…

.NET 6 中將 ASP.NET Core 注冊成 Windows Service

前言使用 Visual Studio 中的 Worker Service項目模板:我們很容易創建出 Windows Service&#xff1a;IHost host Host.CreateDefaultBuilder(args).UseWindowsService().ConfigureServices(services >{services.AddHostedService<Worker>();}).Build();await host.R…

19.12 添加自定義監控項目 配置郵件告警 測試告警

9月12日任務19.12 添加自定義監控項目19.13/19.14 配置郵件告警19.15 測試告警19.16 不發郵件的問題處理19.12 添加自定義監控項目需求&#xff1a;監控某臺web的80端口連接數&#xff0c;并出圖兩步&#xff1a;1&#xff09;zabbix監控中心創建監控項目&#xff1b;2&#xf…

wab框架

http協議 一、http簡介 1.HTTP是一個基于TCP/IP通信協議來傳遞數據&#xff08;HTML 文件, 圖片文件, 查詢結果等&#xff09;。 2.HTTP是一個屬于應用層的面向對象的協議&#xff0c;由于其簡捷、快速的方式&#xff0c;適用于分布式超媒體信息系統。它于1990年提出&#xff0…

c++ 二維矩陣 轉vector_Python線性代數學習筆記——矩陣的基本運算和基本性質,實現矩陣的基本運算...

當學習完矩陣的定義以后&#xff0c;我們來學習矩陣的基本運算&#xff0c;與基本性質矩陣的基本運算&#xff1a;矩陣的加法&#xff0c;每一個對應元素相加&#xff0c;對應結果的矩陣例子&#xff1a;矩陣A和矩陣B表示的是同學上學期和下學期的課程的成績&#xff0c;兩個矩…

android 4.4以上能夠實現的沉浸式狀態欄效果

僅僅有android4.4以及以上的版本號才支持狀態欄沉浸效果 先把程序執行在4.4下面的手機上,看下效果: 在4.4以上的效果: 當然圖片也是能夠作為背景的.效果: 代碼: if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {Window window getWindow();window.setFlags(Wind…

為abp vnext生成C#客戶端給非abp第三方net程序使用

abp vnext提供了動態C#API客戶端和靜態C#API客戶端來調用abp項目的接口&#xff0c;但是有局限性&#xff1b;要使用動態C#API客戶端的項目必須也是ABP vnext的項目。靜態C#API客戶端也依賴abp的包&#xff0c;如下圖為的靜態客戶端依賴于 Volo.Abp.DependencyInjection、Volo.…