如何快速发现 ASP.NET Core 应用程序中的服务生命周期问题?【转】

在 ASP.NET Core 中,内置了非常强大的依赖注入容器功能。但是,如果不正确使用,也可能会引起一些问题。

问题

下面我们通过一段示例代码来说明这个问题。

public interface IServiceA
{
    string Get();
}

public interface IServiceB
{
    string Get();
}

public class ServiceAImpl : IServiceA
{
    private readonly IServiceB serviceB;

    public ServiceAImpl(IServiceB serviceB)
    {
        this.serviceB = serviceB;
    }

    public string Get()
    {
        return serviceB.Get();
    }
}

public class ServiceBImpl : IServiceB
{
    private readonly Guid _id = Guid.NewGuid();
    public string Get()
    {
        return _id.ToString();
    }
}

示例代码中有两个接口 IServiceA 和 IServiceB,以及两个实现类 ServiceAImpl 和 ServiceBImpl。其中 ServiceAImpl 依赖于 IServiceB。

我们在 Startup.cs 中将 IServiceA 注册为单例,将 IServiceB 注册为瞬时,我们希望Get方法每次返回值都不同:

builder.Services.AddSingleton<IServiceA, ServiceAImpl>();
builder.Services.AddTransient<IServiceB, ServiceBImpl>();

这时,如果我们多次调用 ServiceA,会发生什么呢?

for (int i = 0; i < 3; i++)
{
    var serviceA = app.Services.GetService<IServiceA>();

    Console.WriteLine(serviceA.Get());
}

Get方法始终输出相同结果,这是因为在单例服务中引用的瞬时服务,实际上也只会实例化一次。

这可不是我们想要的效果。

解决方案

为了避免这种问题,我们可以在注册服务的时候进行检查。具体方法是,在 Startup.cs 中加入如下代码:

builder.Services.ValidateServiceLifetime(typeof(ServiceAImpl).Assembly);

其中 ValidateServiceLifetime 是我们自己实现的一个方法,它的实现代码如下:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection ValidateServiceLifetime(this IServiceCollection services, params Assembly[] assemblies)
    {
        var singletonServiceDescriptors = services
            .Where(descriptor => descriptor.Lifetime == ServiceLifetime.Singleton);

        StringBuilder sb = new StringBuilder();
        foreach (var singletonServiceDescriptor in singletonServiceDescriptors)
        {
            var singletonService = singletonServiceDescriptor.ImplementationType;
            if (singletonService != null  && assemblies.Contains(singletonService.Assembly))
            {
                var parameterServices = singletonService
                    .GetConstructors()
                    .SelectMany(property => property.GetParameters())
                    .Where(propertyType => services.Any(descriptor => descriptor.ServiceType == propertyType.ParameterType
                                                        && (descriptor.Lifetime != ServiceLifetime.Singleton)));

                foreach (var parameterService in parameterServices)
                {
                    sb.AppendLine($"Singleton service {singletonService} use non-singleton Service {parameterService}");
                }
            }
        }
        if (sb.Length > 0)
        {
            throw new Exception(sb.ToString());
        }
        return services;
    }
}

这个方法会获取所有单例服务,然后检查它们的构造函数是否存在非单例的服务。如果发现问题,就会抛出异常。

总结

通过这种方式,我们可以在注册服务时就及时发现潜在的问题,从而避免因为服务生命周期配置不当而导致的 bug 和其他问题。

posted @ 2023-06-20 17:25  .Neterr  阅读(24)  评论(0编辑  收藏  举报