依赖注入的核心概念

依赖注入(DI)是一种设计模式,通过将对象的依赖关系从内部创建转移到外部传递,实现解耦。在 MVC 框架中,DI 容器负责管理对象的生命周期和依赖关系,开发者只需声明依赖,容器自动完成注入。

生命周期配置

不同框架的生命周期命名可能不同,但核心分为三类:

  • 瞬态(Transient):每次请求都创建新实例,适合无状态的轻量级服务。
  • 作用域(Scoped):同一请求内共享实例,常见于 HTTP 上下文相关的服务(如数据库连接)。
  • 单例(Singleton):全局共享一个实例,适用于耗时资源(如配置中心)。

错误示例:将数据库上下文误注册为单例,导致多用户数据混乱。

// 错误:DbContext 应使用 Scoped 而非 Singleton
services.AddSingleton<AppDbContext>();

注入方式对比

  • 构造函数注入:强类型,显式声明依赖,推荐作为首选。
  • 属性注入:灵活性高,但可能隐藏依赖关系,需谨慎使用。
  • 方法注入:适用于临时依赖,常见于工厂模式。

推荐代码示例:

public class OrderService
{private readonly IPaymentGateway _gateway;// 构造函数注入public OrderService(IPaymentGateway gateway){_gateway = gateway;}
}

常见问题与解决方案

循环依赖:A 依赖 B,B 又依赖 A。可通过提取公共逻辑到第三类或改用方法注入解决。
过度注入:构造函数参数过多(如超过 5 个),需拆分职责或引入聚合服务。

测试中的应用

通过模拟依赖项(Mock)实现单元测试隔离。例如使用 Moq 框架:

var mockGateway = new Mock<IPaymentGateway>();
mockGateway.Setup(g => g.Process(It.IsAny<decimal>())).Returns(true);
var service = new OrderService(mockGateway.Object);

框架差异示例

  • ASP.NET Core:内置 DI 容器,通过 IServiceCollection 配置。
  • Spring Boot:使用 @Autowired 注解实现注入。
  • Laravel:通过服务容器绑定依赖,支持自动解析。

最佳实践

  • 优先选择构造函数注入,明确依赖关系。
  • 根据业务需求严格匹配生命周期,避免跨请求状态污染。
  • 定期检查容器配置,移除未使用的服务以减少开销。

通过合理使用 DI,可显著提升代码的可维护性和可测试性,减少模块间的耦合度。### 依赖注入在 .NET Framework 与 .NET Core 中的配置差异

基础准备:定义服务接口与实现

定义服务接口与实现类,确保接口与实现分离,便于解耦和测试。以下是一个示例:

// 服务接口(定义契约)
public interface IProductService
{List<Product> GetHotProducts(int count);Product GetById(int id);
}// 服务实现(具体逻辑)
public class ProductService : IProductService
{private readonly AppDbContext _dbContext;// 构造函数注入依赖(DbContext 也是服务)public ProductService(AppDbContext dbContext){_dbContext = dbContext;}public List<Product> GetHotProducts(int count){return _dbContext.Products.Where(p => p.IsHot && p.IsActive).Take(count).ToList();}public Product GetById(int id){return _dbContext.Products.Find(id);}
}
.NET Core/.NET 5+ 配置

.NET Core 内置 DI 容器,配置入口在 Program.cs 文件中,通过 IServiceCollection 注册服务:

var builder = WebApplication.CreateBuilder(args);// 添加 MVC 控制器与视图支持
builder.Services.AddControllersWithViews();// 注册数据库上下文(Scoped 生命周期)
builder.Services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));// 注册自定义服务(推荐接口+实现)
builder.Services.AddScoped<IProductService, ProductService>();// 直接注册实现类(无接口时用)
builder.Services.AddTransient<LogService>();// 注册配置类(从 appsettings.json 绑定)
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));var app = builder.Build();
// ... 中间件配置 ...
app.Run();
.NET Framework 配置(使用 Autofac)

.NET Framework 原生 DI 功能较弱,需借助第三方容器(如 Autofac):

  1. 安装 Autofac 包:

    Install-Package Autofac.Mvc5
    
  2. Global.asax 中配置:

    public class MvcApplication : System.Web.HttpApplication
    {protected void Application_Start(){// 初始化 Autofac 容器var builder = new ContainerBuilder();// 注册控制器(Autofac 需显式注册控制器)builder.RegisterControllers(typeof(MvcApplication).Assembly);// 注册数据库上下文(InstancePerRequest 对应 .NET Core 的 Scoped)builder.RegisterType<AppDbContext>().InstancePerRequest();// 注册自定义服务builder.RegisterType<ProductService>().As<IProductService>().InstancePerRequest();// 设置 MVC 的依赖解析器var container = builder.Build();DependencyResolver.SetResolver(new AutofacDependencyResolver(container));// 其他 MVC 初始化AreaRegistration.RegisterAllAreas();FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);}
    }
    
生命周期对比
  • .NET Core:提供 Transient(每次请求新建)、Scoped(每次请求单例)、Singleton(全局单例)三种生命周期。
  • Autofac:提供 InstancePerRequest(类似 Scoped)、InstancePerDependency(类似 Transient)、SingleInstance(类似 Singleton)。
关键区别
  1. 配置入口:.NET Core 在 Program.cs,.NET Framework 在 Global.asax
  2. 容器依赖:.NET Core 内置 DI 容器,.NET Framework 需引入第三方库。
  3. 控制器注册:.NET Core 自动注册控制器,Autofac 需显式注册。

注入服务到控制器

在控制器中使用构造函数注入是最推荐的方式。通过私有只读字段存储服务实例,确保依赖项通过构造函数传入,避免手动实例化服务。

public class ProductsController : Controller
{private readonly IProductService _productService;private readonly IOptions<AppSettings> _appSettings;public ProductsController(IProductService productService,IOptions<AppSettings> appSettings){_productService = productService;_appSettings = appSettings;}public ActionResult HotProducts(){int hotCount = _appSettings.Value.HotProductCount;var hotProducts = _productService.GetHotProducts(hotCount);return View(hotProducts);}
}

注入服务到视图

视图中的服务注入适用于简单场景,避免使视图逻辑过于复杂。使用@inject指令声明服务,直接在视图中使用。

@model List<Product>
@inject IProductService ProductService
@inject IOptions<AppSettings> AppSettings<h3>热门商品(共 @AppSettings.Value.HotProductCount 个)</h3>
<ul>@foreach (var product in Model){<li>@product.Name - ¥@product.Price</li>}
</ul><p>本月热销:@ProductService.GetHotProducts(1).FirstOrDefault()?.Name</p>

注入服务到过滤器

过滤器默认不支持构造函数注入,需通过TypeFilterServiceFilter实现依赖注入。

public class LogFilter : IActionFilter
{private readonly LogService _logService;public LogFilter(LogService logService){_logService = logService;}public void OnActionExecuting(ActionExecutingContext filterContext){var controller = filterContext.Controller.ToString();var action = filterContext.ActionDescriptor.ActionName;_logService.WriteLog($"请求:{controller}/{action}");}public void OnActionExecuted(ActionExecutedContext filterContext) { }
}

在控制器或方法上使用TypeFilter

[TypeFilter(typeof(LogFilter))]
public class ProductsController : Controller
{// 控制器逻辑
}

全局注册过滤器:

builder.Services.AddControllersWithViews(options =>
{options.Filters.Add<TypeFilter<LogFilter>>();
});

Singleton、Scoped、Transient 核心区别

Singleton
实例在第一次请求时创建,整个应用生命周期内保持唯一。适用于无状态服务,如全局配置、工具类。

builder.Services.AddSingleton<IMailService, MailService>();

Scoped
每个请求范围内创建一个实例,同一请求内多次注入共享同一实例。适用于有状态服务,如数据库上下文(DbContext)、用户会话。

builder.Services.AddScoped<IProductService, ProductService>();

Transient
每次注入或获取服务时都创建新实例。适用于轻量级、无状态服务,如日志记录器、验证器。

builder.Services.AddTransient<ILoginValidator, LoginValidator>();

常见错误案例与解决方案

错误1:Singleton 依赖 Scoped 服务
问题:Singleton 长期持有 Scoped 服务(如 DbContext),导致内存泄漏和数据不一致。
错误代码示例:

public class SingletonService : ISingletonService
{private readonly AppDbContext _dbContext;public SingletonService(AppDbContext dbContext) => _dbContext = dbContext;public void DoWork() => var data = _dbContext.Products.ToList();
}
// ❌ 错误注册
builder.Services.AddSingleton<ISingletonService, SingletonService>();
builder.Services.AddScoped<AppDbContext>();

解决方案
通过 IServiceScopeFactory 创建临时作用域:

public class SingletonService : ISingletonService
{private readonly IServiceScopeFactory _scopeFactory;public SingletonService(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;public void DoWork(){using (var scope = _scopeFactory.CreateScope()){var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();var data = dbContext.Products.ToList(); // 正确释放}}
}

错误2:Transient 用于有状态服务
问题:每次注入生成新实例,导致状态丢失(如购物车数据)。
错误代码示例:

public class CartService : ICartService
{public List<CartItem> Items { get; set; } = new();public void AddItem(CartItem item) => Items.Add(item);
}
// ❌ 错误注册
builder.Services.AddTransient<ICartService, CartService>();// 控制器中状态丢失
_cartService1.AddItem(new CartItem { Id = 1 });
var count = _cartService2.Items.Count; // 结果为 0

解决方案
改用 Scoped 生命周期:

builder.Services.AddScoped<ICartService, CartService>();

生命周期选择原则

  • 无状态且全局共享:Singleton
  • 请求内有状态或需隔离:Scoped
  • 短暂、无状态且轻量:Transient

避免跨生命周期依赖(如 Singleton 直接依赖 Scoped),优先通过工厂模式或作用域隔离解决。

自定义缓存过滤器的实现步骤

定义缓存服务接口与实现
public interface ICacheService
{T Get<T>(string key);void Set<T>(string key, T value, TimeSpan expiration);void Remove(string key);
}public class MemoryCacheService : ICacheService
{private readonly IMemoryCache _memoryCache;public MemoryCacheService(IMemoryCache memoryCache){_memoryCache = memoryCache;}public T Get<T>(string key) => _memoryCache.TryGetValue(key, out T value) ? value : default;public void Set<T>(string key, T value, TimeSpan expiration) => _memoryCache.Set(key, value, expiration);public void Remove(string key) => _memoryCache.Remove(key);
}
创建自定义缓存过滤器
public class CustomCacheFilter : IActionFilter
{private readonly ICacheService _cacheService;private readonly string _cacheKey;private readonly int _expirationMinutes;public CustomCacheFilter(ICacheService cacheService, string cacheKey, int expirationMinutes){_cacheService = cacheService;_cacheKey = cacheKey;_expirationMinutes = expirationMinutes;}public void OnActionExecuting(ActionExecutingContext filterContext){var cacheData = _cacheService.Get<object>(_cacheKey);if (cacheData != null){filterContext.Result = new ViewResult { ViewData = (ViewDataDictionary)cacheData };}}public void OnActionExecuted(ActionExecutedContext filterContext){if (filterContext.Result is ViewResult viewResult && _cacheService.Get<object>(_cacheKey) == null){_cacheService.Set(_cacheKey, viewResult.ViewData, TimeSpan.FromMinutes(_expirationMinutes));}}
}
服务注册与配置
builder.Services.AddMemoryCache();
builder.Services.AddScoped<ICacheService, MemoryCacheService>();
在控制器中使用过滤器
[TypeFilter(typeof(CustomCacheFilter), Arguments = new object[] { "HotProductsCache", 10 })]
public ActionResult HotProducts()
{var hotProducts = _productService.GetHotProducts(8);return View(hotProducts);
}

关键注意事项

  • 过滤器构造函数注入的服务需通过DI容器注册
  • TypeFilter用于传递运行时参数(如cacheKey
  • OnActionExecuting中若命中缓存会直接短路请求
  • OnActionExecuted仅在首次未命中缓存时执行存储

坑 1:手动 new 服务实例(绕过 DI 容器,依赖无法注入)

在 ASP.NET Core 中,依赖注入(DI)是核心机制,手动通过 new 创建服务实例会导致依赖链断裂。例如 ProductService 需要 DbContext,但手动实例化时无法自动注入依赖,导致编译错误或运行时异常。

正确做法:始终通过构造函数注入服务,禁止手动 new

public class ProductsController : Controller
{private readonly IProductService _productService;public ProductsController(IProductService productService){_productService = productService;}public ActionResult Index(){var products = _productService.GetHotProducts(8);return View(products);}
}

坑 2:控制器构造函数参数过多(“构造函数爆炸”)

当控制器依赖过多服务时,构造函数会变得冗长且难以维护。例如 OrderController 依赖 5 个服务,导致代码臃肿。

解决方案:使用聚合服务封装相关依赖

// 定义聚合服务
public class OrderAggregateService
{public IOrderService OrderService { get; }public IProductService ProductService { get; }public ICartService CartService { get; }public OrderAggregateService(IOrderService orderService,IProductService productService,ICartService cartService){OrderService = orderService;ProductService = productService;CartService = cartService;}
}// 注册聚合服务
services.AddScoped<OrderAggregateService>();// 简化后的控制器
public class OrderController : Controller
{private readonly OrderAggregateService _aggregateService;private readonly IUserService _userService;public OrderController(OrderAggregateService aggregateService,IUserService userService){_aggregateService = aggregateService;_userService = userService;}public ActionResult Create(){var products = _aggregateService.ProductService.GetAll();// 其他逻辑}
}

坑 3:循环依赖问题

当服务 A 依赖服务 B,同时服务 B 又依赖服务 A 时,会导致 DI 容器无法解析。

解决方案

  • 重构设计,通过引入第三个服务(如中介者模式)解耦循环依赖。
  • 必要时使用 IServiceProvider.GetRequiredService 延迟解析(需谨慎)。

坑 4:未正确管理服务生命周期

误用 Singleton 生命周期注册需要请求作用域的服务(如 DbContext),会导致内存泄漏或数据污染。

生命周期选择指南

  • Transient:每次请求创建新实例(轻量级无状态服务)。
  • Scoped:同一请求内共享实例(如 DbContext)。
  • Singleton:全局单例(配置类服务)。

坑 5:过度依赖 DI 容器

在非 DI 管理的类(如静态类或实体类)中强行使用 DI,会导致设计混乱。

解决方案

  • 遵循“构造函数注入”原则,避免在非 DI 上下文中解析服务。
  • 对于需要服务的实体类,可采用“领域事件”模式解耦。

坑 6:忽略 IDisposable 服务的释放

未正确处理实现了 IDisposable 的服务(如文件流、数据库连接),可能导致资源泄漏。

正确做法

  • DI 容器会自动释放 Scoped/Transient 服务的 IDisposable 实例。
  • 手动创建的 IDisposable 对象需使用 using 语句包裹。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/96609.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96609.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/96609.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【实证分析】上市公司经营风险数据集-含代码(2000-2022年)

数据简介&#xff1a;上市公司经营风险涉及多维度、多层次的复杂因素&#xff0c;本文章参考王竹泉-经营风险与营运资金融资决策对上市公司经验风险进行测算&#xff0c;经营风险是该公司息税折旧摊销前利润率的标准差&#xff0c;经营风险是该公司息税折旧摊销前利润率的标准差…

领码方案|Windows 下 PLT → PDF 转换服务超级完整版:异步、权限、进度

摘要 面向 Windows 平台&#xff0c;使用 ASP.NET Core Web API 结合 Ghostscript.NET 库&#xff0c;实现 PLT&#xff08;HPGL&#xff09;→PDF 的纯库调用转换&#xff0c;无需外部进程。支持同步与异步模式&#xff0c;采用 JWTRBAC 进行权限治理&#xff0c;任务状态存储…

浏览器兼容性问题全解:CSS 前缀、Grid/Flex 布局兼容方案与跨浏览器调试技巧

1. 浏览器兼容性与前缀问题 不同浏览器&#xff08;尤其是老版本 IE、Edge、Safari&#xff09;对新特性&#xff08;比如 CSS 变量、Grid、Flex 等&#xff09;的支持程度不一&#xff0c;需要使用厂商前缀&#xff08;-webkit-、-moz- 等&#xff09;或降级方案。新手往往忽…

【Android View】事件分发机制

参考文献 https://juejin.cn/post/6844904041487532045https://juejin.cn/post/6844903894103883789#heading-12https://www.jianshu.com/p/dea72779a6b7 文章目录

【大数据相关】ClickHouse命令行与SQL语法详解

ClickHouse命令行与SQL语法详解一、ClickHouse命令行与SQL语法详解第一部分&#xff1a;ClickHouse SQL 命令行客户端 (clickhouse-client)1. 基础连接2. 核心命令行参数3. 数据导入与导出实战第二部分&#xff1a;ClickHouse SQL 语法详解1. DDL (数据定义语言)2. DML (数据操…

学习日记-CSS-day53-9.11

1.CSS介绍知识点核心内容重点CSS定义层叠样式表&#xff0c;用于内容修饰和样式展现英文全称cascading style sheetsCSS作用实现HTML内容与样式分离&#xff0c;提高开发效率对比传统HTML元素单独设置样式的低效方式学习建议掌握常用功能即可&#xff0c;重点在打通前后端数据通…

Maven中optional的作用

目的&#xff1a; 控制依赖传递 &#xff1a;将依赖标记为可选&#xff0c;这样当其他模块依赖common-component时&#xff0c;不会自动继承Elasticsearch依赖。这遵循了"依赖最小化"原则&#xff0c;避免不必要的库被引入到不需要它们的模块中。模块化设计 &#xf…

蓝桥杯算法之基础知识(7)---排序题的快排和归并排序

一、快排》快排方法&#xff0c;就三步1.随便选一个值作为基准值x2.拿选中的这个x值划分队列为左右两个区间&#xff08;左边的都小于x&#xff0c;右边的都大于x&#xff09;3.然后递归左区间和右区间就行》代码举例&#xff1a;#qs排序#1 6 7 8 6 5 4 #先找比较点&#xff0c…

缓存未命中

缓存未命中&#xff08;Cache Miss&#xff09; 发生在 CPU 访问某块内存时&#xff0c;该地址不在当前缓存&#xff08;L1/L2/L3&#xff09;中&#xff0c;导致程序被迫从更慢的内存&#xff08;RAM&#xff09;读取数据&#xff0c;严重拖慢程序执行速度。 &#x1f4cd; 一…

AR眼镜:化工安全生产的技术革命

在石化企业的压缩机组巡检中&#xff0c;佩戴AR眼镜的巡检员眼前实时显示着设备温度场分布和振动频谱曲线&#xff0c;单台设备巡检时间从45分钟缩短至18分钟。这不仅是效率的提升&#xff0c;更是化工安全生产的一场智能革命。一、行业痛点&#xff1a;传统化工巡检的困境与挑…

消息中间件RabbitMQ(从入门到精通)

RabbitMQ概念_MQ 消息队列 MQ全称Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于系统之间的异步通信。 同步通信相当于两个人当面对话,你一言我一语。必须及时回复 异步通信相当于通过第三方转述对话,可能有消息的延迟,但不需要二人时刻保持联系。…

前端学习之后端java小白(五)之多表查询/事务

一、多表查询概念二、概述 1. 内连接隐式内连接 SELECT 字段列表 FROM 表1&#xff0c;表2... WHERE 条件显示内连接SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 条件2. 外连接 左外连接SELECT 列名 FROM 左表 LEFT [OUTER] JOIN 右表 ON 连接条件;右外连接SELECT 列名…

Java全栈学习笔记34

# JDBCjava database connection Java 数据库连接技术## JDBC 驱动程序如果需要通过jdbc技术连接关系型数据库&#xff0c;就需要为jdbc提供一个该数据库的驱动。驱动程序由对应的数据库厂商提供。mysql提供了针对于各种语言的驱动程序。去官网下载和java相关的驱动即可## JDB…

如何为MySQL中的JSON字段设置索引

背景 MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此&#xff0c;它成了一种逃离严格列定义的方式&#xff0c;可以存储各种形状和大小的JSON文档&#xff0c;例如审计日志、配置信息、第三方数据包、用户自定义字段等。 虽然MySQL提供了读写JSON数据的函数&am…

【学习日记】

1.上午看了会面经&#xff0c;八股&#xff0c;很多看不懂1.5排查本地mysql服务启动问题2.刷了两道题翻转二叉树的Dfs和bfs递归方法&#xff0c;看了几分钟看懂了&#xff0c;一开始刷题&#xff0c;没有这种感觉&#xff0c;可能思维上升了3.下午做了会ppt4.看了ssm的一个gith…

本地大模型部署指南-Ollama与HuggingFace对比

在本地部署大模型时&#xff0c;用 Ollama 和 Hugging Face (HF) 确实有很大区别&#xff0c;涉及系统、硬件、训练、推理方式&#xff0c;以及能否查看模型源代码。下面我分几个维度说明&#xff1a; 系统和安装 Ollama 定位是「开箱即用」的本地大模型运行环境。 自带运行时&…

河北周边有哪些比较靠谱的智算中心?

河北省通过算力普惠、绿色能源、数据开放、金融支持四大支柱政策&#xff0c;推动智算中心高质量发展。河北及周边地区的智算中心已形成高可靠性、先进技术和战略协同的布局。那么&#xff0c;河北周边有哪些比较靠谱的智算中心&#xff1f;一、河北周边智算中心盘点‍1、尚航怀…

电动汽车充电标准之 — 国标 GB/T 18487《电动汽车传导充电系统》 简介

GB/T 18487 的全称是 《电动汽车传导充电系统》 &#xff0c;它是中国电动汽车充电领域最基础、最核心的国家标准之一。该标准规定了电动汽车传导充电系统的通用要求、通信协议、安全要求等&#xff0c;是整个中国充电基础设施建设的基石。 与您之前了解的IEC 61851类似&#x…

温湿度传感器如何守护工业制造?

在工业制造、农业养殖、仓储物流乃至文物保护等领域&#xff0c;环境温湿度的精确监测是保障品质与安全的关键。温湿度传感器作为无声的守护者&#xff0c;如何通过稳定可靠的数据采集&#xff0c;为现代工业生产的精细化与智能化管理提供坚实基础&#xff1f;本文将深入探讨其…

破壁·融合·共赢:杭州大成慧谷基金与涉海科技混改项目公司正式启航!

2025 年 7 月 15 日,一家融合国企基金实力与民企创新活力的混合所有制项目公司正式诞生——由杭州大成慧谷股权投资基金管理有限公司与山东涉海海洋生物科技有限公司共同出资设立的武创慧聚创芯科学技术(上海)有限公司,当日完成法律合规手续。此前,上海武创大智高新技术集团副总…