懒加载
对于 ABP 框架的懒加载机制,核心是围绕 ILazyServiceProvider 接口及其实现类展开的。作为新手,你可以简单理解为:它是 ABP 提供的“延迟获取服务的工具”,能让你在需要时才创建服务实例,而不是一开始就初始化所有依赖。
核心接口和类
-
ILazyServiceProvider接口(核心)
定义了懒加载服务的基本操作,最常用的方法是:LazyGetRequiredService<T>():获取必须存在的服务(服务未注册会抛异常),首次调用时才初始化。LazyGetService<T>():获取可选服务(服务未注册返回null)。
-
DefaultLazyServiceProvider类
ILazyServiceProvider的默认实现,内部基于 .NET 内置的IServiceProvider实现懒加载,不依赖 Autofac,是 ABP 默认容器的一部分。 -
ApplicationService基类中的LazyServiceProvider属性
所有继承ApplicationService的类(如应用服务),可以直接通过LazyServiceProvider属性使用懒加载(已内置ILazyServiceProvider实例)。
一、先搞懂:什么是“懒加载”?
懒加载(Lazy Loading) 是一种“延迟初始化”技术:不到必须用的时候,绝不创建对象实例。
举个生活例子:
- 非懒加载:你出门时把所有可能用到的东西(雨伞、充电宝、书本)都塞进包里,不管当下用不用得上,背着很重。
- 懒加载:出门时只带钥匙和手机,下雨了再回家拿雨伞,需要充电了再找充电宝——按需取用,更轻便。
在代码中,这意味着:如果一个服务(比如数据库连接、邮件发送器)初始化耗时/耗资源,但不是每次都用到,就可以用懒加载,避免“没用却先创建”的浪费。
二、ABP 为什么需要专门的懒加载工具?
ABP 是依赖注入(DI)框架,所有服务都由容器管理。但 .NET 内置的 IServiceProvider(默认容器)有个特点:调用 GetService<T>() 时会立即创建服务实例,没有“延迟创建”的能力。
比如:
// 非懒加载:调用 GetService 时,EmailService 会立即被创建
var emailService = serviceProvider.GetService<IEmailService>();
如果 EmailService 初始化很耗时,但后续可能用不上,就会造成浪费。因此 ABP 封装了一套懒加载工具,解决这个问题。
三、ABP 懒加载的核心组件
ABP 提供了 3 个核心组件,从“定义规则”到“具体实现”再到“便捷使用”:
| 组件 | 作用 |
|---|---|
ILazyServiceProvider 接口 |
定义“懒加载服务”的规则(比如“如何延迟获取服务”),是“契约”。 |
DefaultLazyServiceProvider 类 |
实现 ILazyServiceProvider 接口,是实际干活的“工具人”。 |
ApplicationService 基类的 LazyServiceProvider 属性 |
给应用服务层提供的“快捷入口”,不用手动注入就能用懒加载。 |
1. ILazyServiceProvider 接口(规则定义)
它就像一份“说明书”,规定了懒加载必须支持哪些操作。核心方法有两个:
public interface ILazyServiceProvider
{// 1. 获取“必须存在”的服务(服务没注册会抛异常),首次用才创建实例T LazyGetRequiredService<T>();// 2. 获取“可选”服务(服务没注册返回 null),首次用才创建实例T? LazyGetService<T>();
}
LazyGetRequiredService<T>():适合“必须有这个服务”的场景(比如订单服务必须依赖支付服务)。LazyGetService<T>():适合“可有可无”的场景(比如日志服务,没注册就不打日志)。
2. DefaultLazyServiceProvider 类(实际实现)
这是 ILazyServiceProvider 的“打工人”,内部通过 .NET 内置的 IServiceProvider 实现延迟加载。
它的工作原理很简单:
- 当你调用
LazyGetRequiredService<T>()时,它先不创建服务实例,而是“记住”要创建的服务类型T。 - 直到你真正使用这个服务时(比如调用服务的方法),它才通过
IServiceProvider去创建实例。
(注:它不依赖 Autofac,完全基于 .NET 原生 DI,所以 ABP 默认就能用。)
3. ApplicationService 中的 LazyServiceProvider 属性(便捷使用)
ABP 的 ApplicationService 基类(应用服务的父类)已经帮你注入了 ILazyServiceProvider,并通过 LazyServiceProvider 属性暴露出来。
也就是说:只要你的类继承了 ApplicationService,就能直接用 LazyServiceProvider 调用懒加载方法,不用自己写构造函数注入。
四、实战:从 0 到 1 用起来
我们用一个完整场景演示:用户注册时,可选“发送欢迎邮件”,邮件服务初始化耗时,适合懒加载。
步骤 1:定义需要懒加载的服务(邮件服务)
// 邮件服务接口
public interface IEmailService
{void SendWelcomeEmail(string username);
}// 邮件服务实现(假设初始化很耗时)
public class EmailService : IEmailService, ITransientDependency
{public EmailService(){// 模拟耗时的初始化(比如连接邮件服务器)Console.WriteLine("【EmailService 初始化】:连接邮件服务器...(耗时 2 秒)");Thread.Sleep(2000); // 暂停 2 秒,模拟耗时}public void SendWelcomeEmail(string username){Console.WriteLine($"【发送邮件】:欢迎 {username} 注册成功!");}
}
IEmailService:邮件服务的接口。EmailService:实现类,标记为ITransientDependency(ABP 会自动注册为瞬态服务)。- 构造函数中的
Thread.Sleep(2000)模拟初始化耗时,方便我们观察懒加载效果。
步骤 2:在应用服务中使用懒加载
创建一个用户注册服务,其中一个方法需要发送邮件,另一个不需要:
// 继承 ApplicationService,直接使用 LazyServiceProvider 属性
public class UserRegistrationAppService : ApplicationService
{// 方法 1:快速注册(不发邮件)public void QuickRegister(string username){Console.WriteLine($"【用户注册】:{username} 快速注册成功(不发邮件)");// 注意:这里没有用到 EmailService,所以它不会被初始化}// 方法 2:完整注册(发邮件,需要懒加载 EmailService)public void FullRegister(string username){Console.WriteLine($"【用户注册】:{username} 完整注册成功(准备发邮件)");// 关键:用懒加载获取 EmailService(此时还没初始化)var emailService = LazyServiceProvider.LazyGetRequiredService<IEmailService>();Console.WriteLine("【准备调用邮件服务】:即将发送邮件...");// 只有当调用 emailService 的方法时,EmailService 才会真正初始化emailService.SendWelcomeEmail(username);}
}
步骤 3:测试效果(观察初始化时机)
假设我们在 ABP 项目中调用这两个方法,输出如下:
// 模拟 ABP 容器获取服务(实际项目中由 ABP 自动处理)
var userService = serviceProvider.GetRequiredService<UserRegistrationAppService>();// 调用 QuickRegister(不发邮件)
userService.QuickRegister("张三");
// 输出:
// 【用户注册】:张三 快速注册成功(不发邮件)
// (EmailService 从未被初始化,因为没用到)// 调用 FullRegister(发邮件)
userService.FullRegister("李四");
// 输出:
// 【用户注册】:李四 完整注册成功(准备发邮件)
// 【准备调用邮件服务】:即将发送邮件...
// 【EmailService 初始化】:连接邮件服务器...(耗时 2 秒) <-- 此时才初始化
// 【发送邮件】:欢迎 李四 注册成功!
关键结论:
EmailService只在FullRegister方法中真正使用时才初始化,QuickRegister中完全不初始化,实现了“按需加载”。
五、懒加载的常见疑问
-
和直接注入
Lazy<T>有什么区别?
.NET 原生也支持注入Lazy<T>(比如public MyService(Lazy<IEmailService> emailService)),但 ABP 的ILazyServiceProvider是更贴合框架的封装,尤其是在ApplicationService中可以直接用LazyServiceProvider,不用手动注入Lazy<T>。 -
什么时候必须用懒加载?
- 服务初始化耗时/耗资源(如数据库连接、远程服务客户端)。
- 服务可能不被使用(如可选功能)。
- 避免循环依赖(A 依赖 B,B 依赖 A 时,直接注入会报错,懒加载可缓解)。
-
懒加载会影响性能吗?
不会,反而能提升性能——因为它避免了不必要的初始化。只有当服务确实被使用时,才会付出初始化成本。
六、总结
ABP 的懒加载核心就是:
- 通过
ILazyServiceProvider接口定义“延迟获取服务”的规则。 - 通过
DefaultLazyServiceProvider类基于 .NET 原生 DI 实现这个规则。 - 在
ApplicationService中通过LazyServiceProvider属性直接使用,简化代码。
用法上,记住一句话:继承 ApplicationService 后,用 LazyServiceProvider.LazyGetRequiredService<T>() 获取服务,它会在你第一次用这个服务时才初始化。