ASP.NET Core 中的延遲注入:原理與實踐

在軟件開發中,依賴注入已成為構建可維護、可測試和可擴展應用程序的核心模式。ASP.NET Core 內置的依賴注入容器為我們管理服務生命周期提供了極大的便利。然而在某些特定場景下,我們可能不希望某個依賴項在宿主對象被創建時立即實例化,而是希望它在首次被使用時才進行實例化。這就是“延遲注入”(Lazy Injection)的概念。

一、延遲注入的原理

延遲注入的核心思想是推遲對象的創建和初始化,直到真正需要使用它的時候。這在以下場景中尤為有用:

  1. 性能優化:當某個依賴項的創建成本很高(例如,需要進行復雜的計算、數據庫查詢或網絡請求),但并非每次宿主對象被使用時都需要該依賴項時,延遲注入可以避免不必要的資源消耗,從而提升應用程序的啟動速度和整體性能。
  2. 資源節約:如果某個依賴項會占用大量內存或其他系統資源,并且在應用程序的生命周期中可能只被少數幾次使用,那么延遲注入可以幫助我們更有效地管理和節約資源。
  3. 解決循環依賴:在某些復雜的依賴關系圖中,可能會出現循環依賴的情況。雖然良好的設計應該避免循環依賴,但在某些不可避免的場景下,延遲注入可以作為一種解決方案,打破循環,允許應用程序正常啟動。

在 .NET 中,實現延遲注入最常用的方式是使用 System.Lazy<T> 類。Lazy<T> 是一個泛型類,它包裝了一個對象,并確保該對象只在首次訪問其 Value 屬性時才被創建。Lazy<T> 的構造函數接受一個 Func<T> 委托,這個委托包含了創建被延遲加載對象的邏輯。當 Value 屬性首次被訪問時,這個委托會被執行,并將其結果緩存起來,后續的訪問將直接返回緩存的值。

ASP.NET Core 的內置依賴注入容器本身并不直接支持 Lazy<T> 的自動解析。這意味著你不能直接在構造函數中注入 Lazy<TService>,并期望 DI 容器能夠自動為你提供一個 Lazy<TService> 實例。然而,我們可以通過一些簡單的配置和模式來實現延遲注入,尤其是在 .NET 8 環境下,其 DI 容器的性能和功能都有所增強,使得這些模式更加高效。

二、在 ASP.NET Core 中實現延遲注入

雖然 ASP.NET Core 的內置 DI 容器不直接支持 Lazy<T> 的自動解析,但我們可以利用其提供的 IServiceProvider 或通過注冊工廠方法來實現延遲注入。以下將通過一個具體的代碼示例來演示如何在 .NET 8 的 ASP.NET Core 應用程序中實現延遲注入。

假設我們有一個 ExpensiveService,它的創建成本很高,我們希望只在需要時才實例化它:

public interface IExpensiveService
{string GetData();
}public class ExpensiveService : IExpensiveService
{public ExpensiveService(){// 模擬耗時操作Console.WriteLine("ExpensiveService 實例被創建了!");System.Threading.Thread.Sleep(2000); }public string GetData(){return "這是來自 ExpensiveService 的數據。";}
}
2.1 方法一:通過 IServiceProvider 延遲解析

這是最直接的方法。你可以在需要延遲注入的類中注入 IServiceProvider,然后在需要時手動從 IServiceProvider 中解析服務。雖然這在一定程度上引入了服務定位器模式的痕跡,但在某些場景下是可接受的。

首先,在 Program.cs 中注冊 ExpensiveService

// Program.csusing Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;var builder = WebApplication.CreateBuilder(args);// 注冊 ExpensiveService
builder.Services.AddTransient<IExpensiveService, ExpensiveService>();// 添加控制器支持
builder.Services.AddControllers();var app = builder.Build();// 配置 HTTP 請求管道
if (app.Environment.IsDevelopment())
{app.UseDeveloperExceptionPage();
}app.UseRouting();app.UseEndpoints(endpoints =>
{endpoints.MapControllers();
});app.Run();

然后,在一個控制器中,你可以這樣使用 IServiceProvider 來延遲解析 IExpensiveService

using Microsoft.AspNetCore.Mvc;
using System;[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{private readonly IServiceProvider _serviceProvider;public HomeController(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}[HttpGet("lazy-resolve")]public IActionResult LazyResolve(){Console.WriteLine("進入 LazyResolve 方法");// 只有在需要時才解析 ExpensiveServicevar expensiveService = _serviceProvider.GetService<IExpensiveService>();if (expensiveService != null){var data = expensiveService.GetData();return Ok($"延遲解析成功:{data}");}return NotFound("服務未找到。");}[HttpGet("no-lazy-resolve")]public IActionResult NoLazyResolve(){Console.WriteLine("進入 NoLazyResolve 方法");// 不進行延遲解析,如果 ExpensiveService 在構造函數中被注入,則會立即創建return Ok("未進行延遲解析。");}
}

當你訪問 /home/lazy-resolve 時,ExpensiveService 的構造函數只會在 GetService<IExpensiveService>() 被調用時才執行。而訪問 /home/no-lazy-resolve 則不會觸發 ExpensiveService 的實例化(除非 ExpensiveService 被其他非延遲注入的方式所依賴)。

2.2 方法二:注冊 Lazy<T> 的工廠方法

這種方法更加優雅,它允許你直接在構造函數中注入 Lazy<TService>,而無需直接暴露 IServiceProvider。你需要手動注冊一個工廠方法,告訴 DI 容器如何創建 Lazy<TService> 的實例。

Program.cs 中,你可以這樣注冊 Lazy<IExpensiveService>

// Program.cs (部分代碼)// ...builder.Services.AddTransient<IExpensiveService, ExpensiveService>();// 注冊 Lazy<IExpensiveService>
builder.Services.AddTransient(sp => new Lazy<IExpensiveService>(() => sp.GetRequiredService<IExpensiveService>()));// ...

現在,我們可以在控制器中直接注入 Lazy<IExpensiveService>

using Microsoft.AspNetCore.Mvc;
using System;[ApiController]
[Route("[controller]")]
public class LazyInjectionController : ControllerBase
{private readonly Lazy<IExpensiveService> _lazyExpensiveService;public LazyInjectionController(Lazy<IExpensiveService> lazyExpensiveService){_lazyExpensiveService = lazyExpensiveService;Console.WriteLine("LazyInjectionController 構造函數執行,但 ExpensiveService 尚未創建。");}[HttpGet("lazy-injection")]public IActionResult LazyInjection(){Console.WriteLine("進入 LazyInjection 方法");// 首次訪問 .Value 屬性時,ExpensiveService 才會被創建var data = _lazyExpensiveService.Value.GetData();return Ok($"延遲注入成功:{data}");}[HttpGet("no-lazy-injection")]public IActionResult NoLazyInjection(){Console.WriteLine("進入 NoLazyInjection 方法");return Ok("未訪問延遲注入的服務。");}
}

當你訪問 /lazyinjection/lazy-injection 時,ExpensiveService 的構造函數只會在 _lazyExpensiveService.Value 被首次訪問時才執行。而訪問 /lazyinjection/no-lazy-injection 則不會觸發 ExpensiveService 的實例化。

2.3 方法三:使用第三方 DI 容器

一些第三方依賴注入容器,如 Autofac,對 Lazy<T> 有原生支持,使得延遲注入的實現更加簡潔。如果你已經在項目中使用或計劃使用第三方 DI 容器,這可能是一個更方便的選擇。

以 Autofac 為例,你通常只需要注冊你的服務,Autofac 會自動處理 Lazy<T> 的解析:

// Startup.cs (Autofac 配置示例)public class Startup
{public ILifetimeScope AutofacContainer { get; private set; }public void ConfigureServices(IServiceCollection services){services.AddControllers();}public void ConfigureContainer(ContainerBuilder builder){builder.RegisterType<ExpensiveService>().As<IExpensiveService>().InstancePerDependency();}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){AutofacContainer = app.ApplicationServices.GetAutofacRoot();if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseRouting();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}
}

然后,在控制器中直接注入 Lazy<IExpensiveService> 即可:

using Microsoft.AspNetCore.Mvc;
using System;[ApiController]
[Route("[controller]")]
public class AutofacLazyInjectionController : ControllerBase
{private readonly Lazy<IExpensiveService> _lazyExpensiveService;public AutofacLazyInjectionController(Lazy<IExpensiveService> lazyExpensiveService){_lazyExpensiveService = lazyExpensiveService;Console.WriteLine("AutofacLazyInjectionController 構造函數執行,但 ExpensiveService 尚未創建。");}[HttpGet("autofac-lazy-injection")]public IActionResult AutofacLazyInjection(){Console.WriteLine("進入 AutofacLazyInjection 方法");var data = _lazyExpensiveService.Value.GetData();return Ok($"Autofac 延遲注入成功:{data}");}
}

TIP:使用 Autofac 需要額外的配置步驟來集成到 ASP.NET Core 中,這里僅展示了核心的延遲注入部分。

三、總結

延遲注入是 ASP.NET Core 中一種重要的性能優化和資源管理策略,尤其適用于那些創建成本高昂或不總是需要的服務。通過 System.Lazy<T> 類,我們可以有效地推遲對象的實例化,直到它們真正被使用。雖然 ASP.NET Core 的內置 DI 容器不直接支持 Lazy<T> 的自動解析,但我們可以通過注冊工廠方法或注入 IServiceProvider 來實現這一目標。對于更復雜的場景,或者如果你已經在使用第三方 DI 容器,它們通常會提供更簡潔的 Lazy<T> 解析支持。在 .NET 8 及其后續版本中,隨著框架性能的不斷提升,合理地運用延遲注入將有助于構建更高效、響應更迅速的應用程序。

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

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

相關文章

PHP內存溢出問題的深度分析與系統解決方案

文章目錄一、問題本質&#xff1a;什么是PHP內存溢出&#xff1f;內存管理核心原理二、高頻內存溢出場景深度解析場景1&#xff1a;大數據集不當處理場景2&#xff1a;無限遞歸陷阱場景3&#xff1a;實體關系映射&#xff08;ORM&#xff09;的N1問題場景4&#xff1a;未及時釋…

常見 HTTP 方法的成功狀態碼200,204,202,201

HTTP 協議中&#xff0c;操作成功后的狀態碼選擇取決于操作類型和響應內容&#xff0c;并非所有非 GET/POST 請求都返回 204。以下是常見 HTTP 方法的成功狀態碼規范&#xff1a;1. GET200 OK&#xff1a;默認成功狀態碼&#xff0c;表示請求成功且返回了資源內容。206 Partial…

【論文閱讀】Think Only When You Need with Large Hybrid-Reasoning Models

Think Only When You Need with Large Hybrid-Reasoning Models2 Large Hybrid-Reasoning Models2.1 Problem Formulation關鍵定義與目標核心挑戰與解決方案2.2 第一階段&#xff1a;混合微調&#xff08;Hybrid Fine-Tuning, HFT&#xff09;核心設計數據構建數據集統計優化目…

洛谷 P13014:[GESP202506 五級] 最大公因數

【題目來源】 https://www.luogu.com.cn/problem/P13014 【題目描述】 對于兩個正整數 &#xff0c;他們的最大公因數記為 。對于 個正整數 &#xff0c;他們的最大公因數為&#xff1a; 給定 個正整數 以及 組詢問。對于第 組詢問&#xff0c;請求出 的最大公因數&…

構建應用內智能:衡石嵌入式BI如何打造“指標中臺”驅動的場景化分析

在當今數據驅動的業務環境中&#xff0c;將智能分析能力深度嵌入業務應用&#xff08;如CRM、ERP、SCM、自研SaaS&#xff09;已成為剛需。然而&#xff0c;實現高性能、一致性、可治理的嵌入式分析面臨巨大技術挑戰。衡石科技通過其核心的指標中臺&#xff08;Metric Platform…

帶貨視頻評論洞察 Baseline 學習筆記 (Datawhale Al夏令營)

一、 項目認識背景&#xff1a;電商直播/短視頻已積累大量「視頻 評論」數據&#xff0c;蘊含了消費者的真實反饋。目標&#xff1a;通過「商品識別 → 情感分析 → 評論聚類」三步&#xff0c;輔助品牌洞察、網紅投放評估。二、 Baseline 代碼流程1. 讀取和預處理video_data …

uniapp中使用uView-plus踩坑記錄

???1.使用插件市場安裝點擊到插件市場 零云uview-plus3.0重磅發布&#xff0c;全面的Vue3鴻蒙移動組件庫。 - DCloud 插件市場 點擊選擇項目直接導入就可以&#xff0c;下載完成后會在uni_modules中&#xff0c;這個.gitignore中不可忽略 ? 使用在main.js里引入 import…

openGauss數據庫管理實戰指南——基本常用操作總結

查看所有數據庫 查看所有表 \d 查看函數定義 查看所有用戶 select usename from pg_user; 1.數據庫創建管理 CREATE DATABASE test; 2.數據庫用戶創建管理 CREATE USER tom PASSWORD Root123456.; 3.表的創建及管理 3.1.創建表 CREATE TABLE test(ID INTEGER PRIMARY …

智慧公安信息化建設解決方案PPT(63頁)

智慧公安的定義與職能 智慧公安是利用現代信息技術提升公安工作效率與服務質量的新模式&#xff0c;涵蓋刑事偵查、治安管理、交通管理等多方面職能&#xff0c;致力于保障社會安全與秩序。 智慧公安信息化建設的重要性 信息化建設是智慧公安發展的核心&#xff0c;通過數據…

k8s存儲入門

目錄 一、 Volume 的概念 二、 Volume 的類型 三、 通過 emptyDir 共享數據 1. EmptyDir 特性 2. EmptyDir 共享數據 四&#xff1a;使用 HostPath 掛載宿主機文件 1. HostPath 特性 2. 掛載宿主機時區文件 五、 掛載 NFS 至容器 1. 前置準備&#xff08;所有 K8s 節…

基于 Flutter 的開源文本 TTS 朗讀器(支持 Windows/macOS/Android)

界面特性 基于 Flutter 的文本 TTS 朗讀器支持 Windows、macOS、AndroidTTS 源&#xff1a;OpenAI TTS、Microsoft TTS支持設置代理支持設置應用主題支持倍速支持書簽支持點擊指定地方朗讀支持 txt、epub、貼粘文本支持從上次地方開始朗讀 源代碼https://github.com/xchenhao/t…

深入理解大語言模型:從核心技術到極簡實現

零基礎的讀者建議先看《零基礎理解大語言模型&#xff1a;從生活例子到代碼實現》&#xff0c;本教程的完整代碼可以在GitHub上找到&#xff0c;如果你有任何問題或建議&#xff0c;歡迎交流討論。 引言 自ChatGPT橫空出世以來&#xff0c;大語言模型&#xff08;Large Langua…

7月13日日記

看來每天寫一篇日記對我來說還是一個不小的挑戰。主要是和惰性做抗爭吧。但是這個東西說實話也沒有什么難度&#xff0c;也并不占用時間&#xff0c;一篇日記大概十幾分鐘就可以寫完。可能更多的是健忘。忘了每天有一個這樣的小任務。忘了前幾天日記寫沒寫了&#xff0c;三下鄉…

《Stata面板數據分析:數據檢驗、回歸模型與診斷技術 - 以NLSW工資研究(公開數據)為例》

本教程旨在全面介紹使用 Stata 進行面板數據分析的方法和技巧。我們將以美國國家縱向調查(NLSW)的數據為例,系統地探討從基礎 OLS 回歸到高級固定效應模型的分析過程。 NLSW 數據集是公開的,可以免費獲取,這為讀者提供了實踐和復現的機會。 通過這個教程,您將掌握使用 …

【VSCode+LaTeX】科研寫作環境搭建

文章目錄0 引言為什么選擇LaTeXVSCode&#xff1f;為什么不選擇Overleaf&#xff1f;1 TeXLive安裝1.1 下載安裝包1.2 運行安裝程序1.3 通過鏡像安裝2 VSCode安裝與配置2.1 下載VSCode安裝包2.2 安裝VSCode2.3 安裝中文語言包2.4 配置LaTeX核心擴展2.5 加載TeX模版文件2.6 編譯…

Surfer軟件入門與等值線繪制實操教程

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;本教程將指導初學者如何使用Surfer軟件進行地質繪圖&#xff0c;重點在于等值線的繪制技巧和提升圖形質量。內容涵蓋Surfer界面介紹、數據導入、等值線繪制方法、樣式設置、地圖增強技術以及輸出保存方法&#…

攻防世界——Web題 very_easy_sql

目錄 payload1 payload2 payload3 看到了題目是sql就猜測是sql注入和萬能密碼了&#xff0c;但怎么試貌似都沒有反應&#xff0c;看源代碼發現了use.php 訪問use.php頁面 可以猜測這里是SSRF&#xff0c;可以訪問到我們本不能訪問的界面&#xff0c;比如&#xff1a;服務器…

基于 SpringBoot 的 REST API 與 RPC 調用的統一封裝

一、為何需要統一封裝&#xff1f; 在討論統一封裝之前&#xff0c;我們先看看 REST 和 RPC 各自的適用場景。 REST API 基于 HTTP 協議&#xff0c;采用 JSON 作為數據交換格式&#xff0c;可讀性好且跨語言&#xff0c;非常適合對外提供服務。 RPC&#xff08;如 Dubbo、gRPC…

【SpringBoot】 整合MyBatis+Postgresql

MyBatis 是一個輕量級的持久化框架&#xff0c;用于簡化數據庫訪問和操作。它通過將 SQL 語句與 Java 代碼分離&#xff0c;允許開發者使用 XML 或注解來配置 SQL 語句&#xff0c;并將結果映射為 Java 對象。MyBatis 提供了靈活的 SQL 控制&#xff0c;適合需要精細控制 SQL 的…

無縫銜接直播流體驗

文章目錄前言&#x1f9e0; 1. 為什么能“無縫銜接”&#xff1f;&#x1f9f0; 2. Flutter 實現方案? 總體策略&#x1f3af; 核心技術點? a. 使用全局播放器管理器&#xff08;單例模式&#xff09;? b. 廣場頁中的直播卡片使用播放器? c. 詳情頁復用控制器? d. 頁面切換…