ABP - 懒加载 [ILazyServiceProvider、DefaultLazyServiceProvider、LazyServiceProvider]

懒加载

对于 ABP 框架的懒加载机制,核心是围绕 ILazyServiceProvider 接口及其实现类展开的。作为新手,你可以简单理解为:它是 ABP 提供的“延迟获取服务的工具”,能让你在需要时才创建服务实例,而不是一开始就初始化所有依赖。

核心接口和类

  1. ILazyServiceProvider 接口(核心)
    定义了懒加载服务的基本操作,最常用的方法是:

    • LazyGetRequiredService<T>():获取必须存在的服务(服务未注册会抛异常),首次调用时才初始化。
    • LazyGetService<T>():获取可选服务(服务未注册返回 null)。
  2. DefaultLazyServiceProvider
    ILazyServiceProvider 的默认实现,内部基于 .NET 内置的 IServiceProvider 实现懒加载,不依赖 Autofac,是 ABP 默认容器的一部分。

  3. 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 中完全不初始化,实现了“按需加载”。

五、懒加载的常见疑问

  1. 和直接注入 Lazy<T> 有什么区别?
    .NET 原生也支持注入 Lazy<T>(比如 public MyService(Lazy<IEmailService> emailService)),但 ABP 的 ILazyServiceProvider 是更贴合框架的封装,尤其是在 ApplicationService 中可以直接用 LazyServiceProvider,不用手动注入 Lazy<T>

  2. 什么时候必须用懒加载?

    • 服务初始化耗时/耗资源(如数据库连接、远程服务客户端)。
    • 服务可能不被使用(如可选功能)。
    • 避免循环依赖(A 依赖 B,B 依赖 A 时,直接注入会报错,懒加载可缓解)。
  3. 懒加载会影响性能吗?
    不会,反而能提升性能——因为它避免了不必要的初始化。只有当服务确实被使用时,才会付出初始化成本。

六、总结

ABP 的懒加载核心就是:

  1. 通过 ILazyServiceProvider 接口定义“延迟获取服务”的规则。
  2. 通过 DefaultLazyServiceProvider 类基于 .NET 原生 DI 实现这个规则。
  3. ApplicationService 中通过 LazyServiceProvider 属性直接使用,简化代码。

用法上,记住一句话:继承 ApplicationService 后,用 LazyServiceProvider.LazyGetRequiredService<T>() 获取服务,它会在你第一次用这个服务时才初始化

posted @ 2025-10-25 12:42  【唐】三三  阅读(3)  评论(0)    收藏  举报