如何快速发现 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 和其他问题。