Scrutor隐藏技巧:用装饰器模式给.NET Core服务加日志竟如此简单 📅 发布时间:2026/7/11 15:02:17 👁️ 浏览次数: Scrutor隐藏技巧用装饰器模式给.NET Core服务加日志竟如此简单最近在重构一个老项目时我遇到了一个典型的痛点需要在几十个服务接口的方法调用前后统一添加执行时间日志。如果一个个去修改实现类不仅工作量巨大还容易引入错误更违背了开闭原则。就在我准备硬着头皮写一堆重复代码时团队里的一位架构师拍了拍我肩膀“试试Scrutor的装饰器模式一行注册全局生效。” 我将信将疑地试了试结果只用了一个装饰器类和一个Decorate调用就优雅地解决了所有问题原有业务代码一行未动。这种“无侵入式增强”的体验让我对.NET Core的依赖注入容器有了新的认识。这篇文章我就来拆解这个被很多人忽略的Scrutor隐藏技巧手把手带你用装饰器模式像搭积木一样为服务添加日志、监控、缓存等横切关注点。1. 为什么是装饰器模式从AOP的困境到DI容器的优雅解在面向切面编程AOP的世界里我们总想在不触碰核心业务逻辑的情况下为方法织入一些通用行为比如日志记录、性能监控、事务管理或缓存。传统的实现方式无论是动态代理如Castle DynamicProxy还是基于编译时织入的PostSharp都或多或少有些“重”它们要么需要引入额外的运行时代理框架增加复杂度和性能开销要么需要购买商业许可或进行复杂的编译配置。而装饰器模式这个来自《设计模式》经典教科书中的结构型模式恰恰提供了一种轻量级、编译时安全的AOP实现途径。其核心思想是定义一个与原始服务接口相同的装饰器类它内部持有一个原始服务的实例并在调用原始服务方法的前后执行额外的逻辑。这听起来很简单但难点在于如何让依赖注入容器自动管理这种装饰关系避免手动创建和组装对象的繁琐。这就是Scrutor大显身手的地方。Scrutor并没有发明新东西它只是巧妙地利用了Microsoft.Extensions.DependencyInjectionMSDI容器已有的能力并通过Decorate扩展方法将装饰器模式的注册变得声明式和自动化。它解决了手动实现装饰器时最头疼的问题如何让容器在解析IMyService时自动将LoggingDecorator(MyService)这个组合对象返回给你。通过Scrutor你只需告诉容器“嘿当有人要IMyService时请先用LoggingDecorator把它包起来。” 剩下的依赖解析和对象构建容器全帮你搞定。注意Scrutor的装饰器模式是纯粹的“编译时装饰”它不创建子类或代理类而是在依赖解析链中动态包装实例。这意味着它对性能的影响微乎其微且能获得完整的IDE智能提示和编译检查。2. 实战为服务方法自动注入执行时间日志理论说得再多不如一行代码。让我们从一个最实用的场景开始为服务方法记录执行耗时。假设我们有一个处理订单的核心服务。首先定义我们的业务接口和实现public interface IOrderProcessor { TaskOrderResult ProcessAsync(OrderRequest request); } public class OrderProcessor : IOrderProcessor { public async TaskOrderResult ProcessAsync(OrderRequest request) { // 模拟复杂的业务逻辑 await Task.Delay(100); return new OrderResult { Success true, OrderId Guid.NewGuid() }; } }现在我们需要在不修改OrderProcessor类的前提下为ProcessAsync方法添加耗时日志。第一步是创建装饰器public class TimingLoggingDecorator : IOrderProcessor { private readonly IOrderProcessor _inner; private readonly ILoggerTimingLoggingDecorator _logger; public TimingLoggingDecorator(IOrderProcessor inner, ILoggerTimingLoggingDecorator logger) { _inner inner; _logger logger; } public async TaskOrderResult ProcessAsync(OrderRequest request) { var stopwatch Stopwatch.StartNew(); _logger.LogInformation(开始处理订单请求ID: {RequestId}, request.Id); try { var result await _inner.ProcessAsync(request); stopwatch.Stop(); _logger.LogInformation(订单处理成功。请求ID: {RequestId}, 耗时: {ElapsedMs}ms, request.Id, stopwatch.ElapsedMilliseconds); return result; } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, 订单处理失败。请求ID: {RequestId}, 耗时: {ElapsedMs}ms, request.Id, stopwatch.ElapsedMilliseconds); throw; } } }这个装饰器做了几件事通过构造函数注入被装饰的原始服务实例_inner和日志器。在方法开始时启动计时器并记录开始日志。调用原始服务方法。无论成功与否都记录耗时和结果。关键步骤来了如何使用Scrutor进行注册。在Startup.cs或Program.cs的依赖注入配置中using Scrutor; public void ConfigureServices(IServiceCollection services) { // 1. 首先像往常一样注册原始服务 services.AddScopedIOrderProcessor, OrderProcessor(); // 2. 然后使用Scrutor的Decorate方法注册装饰器 services.DecorateIOrderProcessor, TimingLoggingDecorator(); // ... 其他服务注册 }是的就这么简单。两行代码。现在当你在控制器或其他服务中通过构造函数注入IOrderProcessor时你实际得到的是一个被TimingLoggingDecorator包装过的OrderProcessor。调用ProcessAsync方法时日志会自动记录。为了更清晰地理解这个过程我们看看容器解析服务时内部发生了什么步骤容器行为结果对象结构1. 收到解析IOrderProcessor的请求查找IOrderProcessor的注册发现最终实现是TimingLoggingDecorator2. 构建TimingLoggingDecorator发现它依赖IOrderProcessor即_inner容器开始递归解析_inner3. 解析内部的IOrderProcessor查找注册找到OrderProcessor创建OrderProcessor实例4. 完成装饰器构建将OrderProcessor实例注入TimingLoggingDecorator返回TimingLoggingDecorator实例整个调用链对使用者完全透明这才是依赖注入和装饰器模式结合的威力。3. 进阶技巧构建可组合、可配置的装饰器管道单个日志装饰器可能只是开始。在实际项目中我们可能需要对同一个服务叠加多个增强功能例如先进行参数验证再记录日志最后进行性能监控。手动嵌套装饰器类会很快导致代码混乱。Scrutor的优雅之处在于它天然支持装饰器链。假设我们还有两个装饰器需求ValidationDecorator: 对输入参数进行基础验证。CircuitBreakerDecorator: 添加简单的熔断器逻辑防止连续失败。我们可以分别创建这些装饰器并按特定的顺序进行注册public void ConfigureServices(IServiceCollection services) { services.AddScopedIOrderProcessor, OrderProcessor(); // 装饰器注册顺序决定了它们的执行顺序从外到内 // 1. 最先执行熔断器最外层 services.DecorateIOrderProcessor, CircuitBreakerDecorator(); // 2. 其次执行日志记录 services.DecorateIOrderProcessor, TimingLoggingDecorator(); // 3. 最后执行最先调用原始服务参数验证最内层 services.DecorateIOrderProcessor, ValidationDecorator(); }此时的解析对象结构就像一个洋葱CircuitBreakerDecorator-TimingLoggingDecorator-ValidationDecorator-OrderProcessor当调用发生时请求会依次穿过熔断器、日志、验证层最后到达核心业务逻辑。响应则按相反顺序返回。这种管道模式让每个装饰器职责单一易于测试和维护。但有时我们可能希望装饰器是有条件生效的。例如只在开发环境记录详细日志或者只为特定实现类的服务添加装饰器。Scrutor通过与TryDecorate和条件扫描的结合也能轻松实现。// 示例仅当原始实现是 SpecificProcessor 时才添加装饰器 services.AddScopedIOrderProcessor, OrderProcessor(); services.AddScopedIOrderProcessor, SpecificProcessor(); // 使用 TryDecorate 并配合条件判断 services.TryDecorateIOrderProcessor((inner, provider) { // 只有当内部实例是 SpecificProcessor 类型时才进行装饰 if (inner is SpecificProcessor specific) { var logger provider.GetRequiredServiceILoggerConditionalDecorator(); return new ConditionalDecorator(inner, logger); } // 否则返回原始实例 return inner; });TryDecorate方法提供了更大的灵活性你可以在工厂委托中访问到原始服务实例inner和服务提供者provider从而做出动态决策。4. 避坑指南装饰器模式下的生命周期与陷阱虽然Scrutor装饰器用起来很爽但如果不理解其背后的生命周期管理很容易掉进坑里。最常见的问题与作用域Scoped生命周期和装饰器构造函数依赖有关。陷阱一装饰器导致作用域实例被意外延长假设原始服务A是Scoped生命周期它依赖另一个Scoped服务B。如果装饰器DecoratorA也是Scoped并且在其构造函数或方法中注入了新的依赖比如一个DbContext这可能会导致B被装饰器间接持有从而影响其释放时机。关键在于理解装饰器实例和原始服务实例的生命周期是独立的但通过依赖关系关联。最佳实践建议尽量保持装饰器为Transient瞬态或Scoped作用域并与原始服务的生命周期保持一致以减少复杂度。避免在装饰器中持有对IDisposable资源的长期引用除非你很清楚它的生命周期。陷阱二装饰器构造函数中的依赖解析装饰器在构造时其内部包装的原始服务_inner可能尚未完全解析尤其是当存在装饰链时。因此不要在装饰器的构造函数中调用_inner的方法。所有对_inner的调用都应该在装饰器的方法内部进行。陷阱三对已解析实例的装饰Decorate方法只在服务注册阶段生效。如果你已经从一个ServiceProvider中解析出了某个服务的实例然后再对容器调用Decorate这个已存在的实例不会被重新装饰。装饰必须在构建ServiceProvider之前完成。为了帮助你规避这些陷阱下面列出了一些常见问题的症状和检查点内存泄漏检查装饰器是否无意中持有了大对象或数据库连接。生命周期异常例如在Singleton服务中使用了Scoped的DbContext。确保装饰器引入的依赖其生命周期是兼容的。装饰未生效确认services.Decorate的调用是否在BuildServiceProvider()之前并且注册的服务接口/类型完全匹配。提示在开发环境可以通过在Decorate处打断点或者输出容器的服务描述信息来验证装饰器是否按预期注册。一个简单的调试方法是在装饰器构造函数里打印日志观察其被创建的时机和次数。5. 超越日志装饰器模式的多样化应用场景掌握了基础的日志装饰器后你会发现这个模式的应用场景远不止于此。它本质上是一种行为增强的手段。以下是我在项目中实际应用过的几个例子希望能激发你的灵感。场景一自动缓存层为数据查询服务添加一层透明缓存是装饰器的经典用例。public class CachingUserServiceDecorator : IUserService { private readonly IUserService _inner; private readonly IMemoryCache _cache; private readonly TimeSpan _defaultExpiration TimeSpan.FromMinutes(5); public CachingUserServiceDecorator(IUserService inner, IMemoryCache cache) { _inner inner; _cache cache; } public async TaskUser GetUserByIdAsync(int id) { var cacheKey $user_{id}; return await _cache.GetOrCreateAsync(cacheKey, async entry { entry.AbsoluteExpirationRelativeToNow _defaultExpiration; return await _inner.GetUserByIdAsync(id); }); } } // 注册services.DecorateIUserService, CachingUserServiceDecorator();场景二重试与弹性处理对于调用外部API或数据库可能失败的操作添加自动重试逻辑。public class RetryPolicyDecorator : IExternalApiService { private readonly IExternalApiService _inner; private readonly ILoggerRetryPolicyDecorator _logger; public RetryPolicyDecorator(IExternalApiService inner, ILoggerRetryPolicyDecorator logger) { _inner inner; _logger logger; } public async TaskApiResponse CallApiAsync() { var retryCount 0; var maxRetries 3; while (true) { try { return await _inner.CallApiAsync(); } catch (HttpRequestException ex) when (retryCount maxRetries) { retryCount; _logger.LogWarning(ex, API调用失败正在进行第 {RetryCount} 次重试, retryCount); await Task.Delay(1000 * retryCount); // 指数退避 } } } }场景三审计与数据脱敏在返回敏感数据如用户信息前自动过滤掉密码、手机号等字段。public class DataMaskingDecorator : IUserDataService { private readonly IUserDataService _inner; public DataMaskingDecorator(IUserDataService inner) _inner inner; public async TaskUserProfile GetProfileAsync(int userId) { var profile await _inner.GetProfileAsync(userId); // 脱敏处理 if (!string.IsNullOrEmpty(profile.PhoneNumber)) { profile.PhoneNumber MaskPhoneNumber(profile.PhoneNumber); } profile.Email MaskEmail(profile.Email); return profile; } private string MaskPhoneNumber(string phone) phone.Length 7 ? ${phone[..3]}****{phone[^4..]} : phone; private string MaskEmail(string email) ******.com; // 简化示例 }这些场景的共同点是它们都是横切关注点与核心业务逻辑正交。使用装饰器模式你可以将这些通用功能模块化像插件一样按需添加到任何服务上极大地提升了代码的复用性和可维护性。下次当你发现自己在多个服务中复制粘贴相似的“辅助代码”时不妨停下来想想这能不能用一个Scrutor装饰器来优雅地解决
VS2019项目重命名全攻略:从解决方案到命名空间一键搞定 VS2019项目重命名:从解决方案到命名空间的深度重构实践 接手一个遗留项目,第一眼看到的往往是前任开发者留下的“印记”——一个可能不符合团队规范、甚至有些随意的项目名称和命名空间。在Visual Studio 2019中,这不仅仅是改个名字那么简单&… 2026/7/10 9:45:44
时间序列预测避坑指南:TCN膨胀卷积参数怎么调?SSA优化Informer的5个关键技巧 时间序列预测实战进阶:TCN膨胀卷积调参精髓与SSA优化Informer的五大核心策略 在光伏发电、电力负荷、交通流量等实际工业场景中,时间序列预测的准确性直接关系到资源调度效率与运营成本。面对复杂的长序列、多变量数据,传统的RNN、LSTM模型常… 2026/5/17 8:56:57
工业控制新组合:用CODESYS+OneOS实现EtherCAT总线控制的完整流程 工业控制新组合:用CODESYSOneOS实现EtherCAT总线控制的完整流程 最近和几个做非标自动化设备的朋友聊天,他们都在抱怨同一个问题:项目周期越来越紧,客户对设备的同步精度和响应速度要求却越来越高,传统的控制方案在应对… 2026/7/11 13:16:50
【大数据课程设计/毕业设计】基于 Python 的抖音女装评论负面舆情预警系统的设计与实现 基于 Python 的时尚女装抖音评论内容挖掘系统【附源码、数据库、万字文档】 博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am… 2026/7/11 15:01:31
Claude Code本地CLI工具安装配置全指南 1. 这不是“翻墙教程”,而是一份给开发者的本地AI编程搭档落地指南 最近两周,朋友圈和开发者群聊里几乎被“Claude Opus 4.7”刷屏了。不是因为又出了什么玄学 benchmark 分数,而是实打实的——它在代码理解深度、上下文连贯性、多文件协同推… 2026/7/11 14:59:31
AtlasOS性能优化:4个免费工具让你的Windows飞起来 AtlasOS性能优化:4个免费工具让你的Windows飞起来 【免费下载链接】Atlas 🚀 An open and lightweight modification to Windows, designed to optimize performance, privacy and usability. 项目地址: https://gitcode.com/GitHub_Trending/atlas1/A… 2026/7/11 14:57:30
麦克纳姆轮车的导航规划 REF:移动机器人自主导航系统研究1. 机器人模型麦克纳姆轮:一种比较独特的车轮结构,通常由一组斜向的棍子组成,每个轮子上都有若干个斜向安装的滚轮,每个滚轮轴与轮毂呈一定角度,通常是45,分为左… 2026/7/11 14:55:26
终极VIA键盘配置器桌面版:3步打造专属机械键盘布局 终极VIA键盘配置器桌面版:3步打造专属机械键盘布局 【免费下载链接】releases 项目地址: https://gitcode.com/gh_mirrors/re/releases VIA键盘配置器桌面版是一款革命性的开源工具,专为机械键盘爱好者设计,通过Electron技术封装&… 2026/7/11 14:55:26
bilibili-downloader完全指南:轻松下载B站大会员4K高清视频 bilibili-downloader完全指南:轻松下载B站大会员4K高清视频 【免费下载链接】bilibili-downloader B站视频下载,支持下载大会员清晰度4K,持续更新中 项目地址: https://gitcode.com/gh_mirrors/bil/bilibili-downloader 你是否曾因为网… 2026/7/11 14:53:26
5分钟搞定Kodi字幕难题:智能字幕插件让你追剧无忧 [特殊字符] 5分钟搞定Kodi字幕难题:智能字幕插件让你追剧无忧 🎬 【免费下载链接】zimuku_for_kodi Kodi 插件,用于从「字幕库」网站下载字幕 项目地址: https://gitcode.com/gh_mirrors/zi/zimuku_for_kodi 还记得那个深夜吗?你刚下载… 2026/7/11 0:00:11
工业信号干扰处理与FOD4216光耦应用实战 1. 工业环境中的信号干扰挑战在工业自动化领域,信号采集的准确性直接关系到整个控制系统的可靠性。典型的工业现场充斥着各种干扰源:大功率电机启停产生的电磁干扰、变频器工作产生的高频噪声、继电器触点火花放电,以及长距离传输引入的共模干… 2026/7/11 0:00:11
OpenHarmony 完整项目工程整合规范 + 模块化分层架构(API23+ 标准企业级结构) 摘要前面系列教程覆盖了 ArkUI 组件、路由、生命周期、本地存储、网络请求、Ability 底层全套基础能力,本篇统一梳理标准工程目录分层、模块化拆分、代码复用规范、全局工具统一管理、项目打包权限配置、常见工程报错统一解决方案,形成可直接用于课程设计… 2026/7/11 0:00:11
6个月转型AI工程师:实战路径与核心技能 1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公… 2026/7/11 14:53:30
TPAFE0808与PIC18F87K22的多通道信号采集方案 1. 项目背景与核心需求在工业自动化、医疗设备和科研仪器等领域,多通道信号采集与系统监测是基础且关键的技术需求。传统方案往往面临通道数量不足、信号调理复杂、系统集成度低等问题。TPAFE0808作为一款8通道模拟前端芯片,与PIC18F87K22微控制器的组合… 2026/7/11 12:30:52
STC3115与PIC18LF26K80构建高精度电池管理系统 1. STC3115与PIC18LF26K80在电池管理系统中的核心价值在现代电子设备中,电池管理系统(BMS)的重要性不亚于设备的核心处理器。STC3115作为一款高精度电池电量监测IC,与PIC18LF26K80微控制器的组合,构成了一个既能精确监控又能智能管理的完整解… 2026/7/8 14:25:08