zoukankan      html  css  js  c++  java
  • 3. abp依赖注入的分析.md

    abp依赖注入的原理剖析

    请先移步参考 [Abp vNext 源码分析] - 3. 依赖注入与拦截器 本文此篇文章的补充和完善。

    abp的依赖注入最后是通过IConventionalRegister接口的AddType方法实现的。先看下该接口:

    public interface IConventionalRegistrar
    {
        void AddAssembly(IServiceCollection services, Assembly assembly);
    
        void AddTypes(IServiceCollection services, params Type[] types);
    
        void AddType(IServiceCollection services, Type type);
    }
    

    该接口定义了三个方法,支持传入程序集、类型数组、具体类型,其实现在一个抽象类ConventionalRegistrarBase中:

    public abstract class ConventionalRegistrarBase : IConventionalRegistrar
    {
        public virtual void AddAssembly(IServiceCollection services, Assembly assembly)
        {
            var types = AssemblyHelper
                .GetAllTypes(assembly)
                .Where(
                    type => type != null &&
                            type.IsClass &&
                            !type.IsAbstract &&
                            !type.IsGenericType
                ).ToArray();
    
            AddTypes(services, types);
        }
    
        public virtual void AddTypes(IServiceCollection services, params Type[] types)
        {
            foreach (var type in types)
            {
                AddType(services, type);
            }
        }
    
        public abstract void AddType(IServiceCollection services, Type type);
    }
    

    不管是程序集,还是类型数组最后都是调用AddType方法,AddType的实现在这个抽象类的派生类中,abp有一个默认的实现类DefaultConventionalRegistrar,该类实现了AddType方法,abp的依赖注入就是通过该类的AddType方法注入的。源码:

    public class DefaultConventionalRegistrar : ConventionalRegistrarBase
    {
        public override void AddType(IServiceCollection services, Type type)
        {
            if (IsConventionalRegistrationDisabled(type))
            {
                return;
            }
    
            var dependencyAttribute = GetDependencyAttributeOrNull(type);
            var lifeTime = GetLifeTimeOrNull(type, dependencyAttribute);
    
            if (lifeTime == null)
            {
                return;
            }
    
            var serviceTypes = ExposedServiceExplorer.GetExposedServices(type);
    
            TriggerServiceExposing(services, type, serviceTypes);
    
            foreach (var serviceType in serviceTypes)
            {
                var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);
    
                if (dependencyAttribute?.ReplaceServices == true)
                {
                    services.Replace(serviceDescriptor);
                }
                else if (dependencyAttribute?.TryRegister == true)
                {
                    services.TryAdd(serviceDescriptor);
                }
                else
                {
                    services.Add(serviceDescriptor);
                }
            }
        }
        
        // 其他方法实现
    }
    

    从这个类中可以看出abp依赖注入的实现思路:根据GetExposedServices方法返回的服务类型列表去构造服务描述符(服务描述符的第一个参数就是服务类型,第二个参数就是实现类型)。而实现类型就是我们要注入的类型。再将此服务描述符注入到DI容器中。
    GetDependencyAttributeOrNull方法和GetLifeTimeOrNull方法是获取使用了Dependency特性注入的类及其生命周期,如果没有则使用默认的生命周期,因此如果Dependency特性的注入优先级更高。GetExposedServices是在静态类ExposedServiceExplorer中,该静态类是用来获取注入类型的定义及实现的。源码实现:

    public static List<Type> GetExposedServices(Type type)
    {
        return type
            .GetCustomAttributes()
            .OfType<IExposedServiceTypesProvider>()
            .DefaultIfEmpty(DefaultExposeServicesAttribute)
            .SelectMany(p => p.GetExposedServiceTypes(type))
            .ToList();
    }
    

    IExposedServicveTypeProvider接口定义及实现:

    //定义:
    public interface IExposedServiceTypesProvider
    {
        Type[] GetExposedServiceTypes(Type targetType);
    }
    
    //实现:
    public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
    {
        public ExposeServicesAttribute(params Type[] serviceTypes)
        {
            ServiceTypes = serviceTypes ?? new Type[0];
        }
        
        public Type[] GetExposedServiceTypes(Type targetType)
        {
            var serviceList = ServiceTypes.ToList();
            
            if (IncludeDefaults == true)
            {
                foreach (var type in GetDefaultServices(targetType))
                {
                    serviceList.AddIfNotContains(type);
                }
            
                if (IncludeSelf != false)
                {
                    serviceList.AddIfNotContains(targetType);
                }
            }
            else if (IncludeSelf == true)
            {
                serviceList.AddIfNotContains(targetType);
            }
            
            return serviceList.ToArray();
        }
        
        private static List<Type> GetDefaultServices(Type type)
        {
            var serviceTypes = new List<Type>();
            
            foreach (var interfaceType in type.GetTypeInfo().GetInterfaces())
            {
                var interfaceName = interfaceType.Name;
            
                if (interfaceName.StartsWith("I"))
                {
                    interfaceName = interfaceName.Right(interfaceName.Length - 1);
                }
            
                if (type.Name.EndsWith(interfaceName))
                {
                    serviceTypes.Add(interfaceType);
                }
            }
            
            return serviceTypes;
        }
    }
    
    

    该接口的实现是在ExposeServices特性的实现类中,这是个特性类,是abp三种注入服务的第一种——ExposeServices特性注入。该类的构造函数会直接保存要注入类型的服务列表。其次就是GetDefaultService方法,该方法会返回默认的服务类型。通过反射获取类继承的接口,并截取接口的名称(除去I之后的接口名),只有实现类与接口名称相同的条件下才会注入到服务类型列表中,这点要注意!对于该方法返回的类型会被添加到服务类型列表中(ServiceTypes)。默认情况下,实现类本身会注入到服务类型列表中,从源码中可以分析到:

    if (IncludeSelf != false)
    {
        serviceList.AddIfNotContains(targetType);
    }
    

    targetType就是我们当前注入的类型。在此便注入了类型本身。如此的好处是,可以获取到类的实例,减少了直接实例化而带来依赖。

    如此便返回了注入类型的定义及实现列表(serviceTypes),而后遍历这个列表,服务描述符(ServiceDescriptor)的参数ServiceType就是这个列表的项。这个服务描述符便注入到了DI容器中。对于Dependency特性注入的方式,如果参数是ReplaceServices,那么将会替换;如果参数是Register,那么将会直接注入。否则的话,直接添加进DI容器中。

    三种注入方式的实现:

    • ExposeServices特性的注入分析
    // 接口
    public interface IMessageWriter
    {
        void Write();
    }
    
    // 实现 1
    [ExposeServices(typeof(IMessageWriter))]
    public class TestMessageTwo : IMessageWriter, ITransientDependency
    {
        public void Write()
        {
            Console.WriteLine("TestMessageTwo");
        }
    }
    
    // 实现 2
    [ExposeServices(typeof(IMessageWriter), typeof(TestMessageOne))]
    public class TestMessageOne : IMessageWriter, ITransientDependency
    {
        public void Write()
        {
            Console.WriteLine("TestMessageOne");
        }
    }
    
    // 注入
    _services = new ServiceCollection();
    _services.AddType<TestMessageOne>();
    _services.AddType<TestMessageTwo>();
    
    // 底层调用:
    var serviceTypes = ExposedServiceExplorer.GetExposedServices(type);
    
    

    abp底层通过ExposedServiceExplorer静态类的GetExposedServices方法确定需要注册类型的定义和实现。这个静态类最后实际上是调用了ExposeServicesAttribute类的构造函数和GetExposedServiceTypes方法确定了服务类型列表。

    public ExposeServicesAttribute(params Type[] serviceTypes)
    {
        ServiceTypes = serviceTypes ?? new Type[0];
    }
    
    public Type[] GetExposedServiceTypes(Type targetType)
    {
    }
    
    • Dependency特性注入
    //接口
    public interface IMyService : ITransientDependency
    {
    }
    
    //实现
    [Dependency(TryRegister = true)]
    public class TryRegisterImplOfMyService : IMyService
    {
    }
    
    //注入
    _services = new ServiceCollection();
    _services.AddTypes(typeof(TryRegisterImplOfMyService));
    
    //底层调用
    ExposeServicesAttribute.GetDefaultServices(typeof(TryRegisterImplOfMyService));
    
    

    Dependency特性注入在调用GetDefaultServices方法返回服务类型列表,而后在DefaultConventionalRegistrar类的AddType方法中构造服务描述符,注入到DI中。

    var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);
    
    if (dependencyAttribute?.ReplaceServices == true)
    {
        services.Replace(serviceDescriptor);
    }
    else if (dependencyAttribute?.TryRegister == true)
    {
        services.TryAdd(serviceDescriptor);
    }
    

    备注:
    对于Dependency注入和接口方式注入,实现类的类名必须以接口名结尾,否则将不能注入到DI中。

    • 接口注入
    //接口
    public interface IMyService : ITransientDependency
    {
    }
    
    //实现 1
    public class FirstImplOfMyService : IMyService
    {
    }
    
    //实现 2
    public class SecondImplOfMyService : IMyService
    {
    }
    
    // 注入
    _services = new ServiceCollection();
    _services.AddTypes(typeof(FirstImplOfMyService),typeof(SecondImplOfMyService));
    
    //底层调用
    ExposeServicesAttribute.GetDefaultServices(typeof(TryRegisterImplOfMyService));
    

    接口方式的注入,也是调用GetDefaultServices返回一个类型列表,然后遍历,保存到服务类型列表中,最后注入到DI容器中。

    • ReplaceServices替换
      如果接口方式注入与Dependency特性注入同时使用且接口相同那么就是另外一种情况,示例:
    // 接口
    public interface IMyService : ITransientDependency
    {
    }
    
    // 接口方式实现
    public class FirstImplOfMyService : IMyService
    {
    }
    
    // Dependency特性注入 -- 替换掉 接口方式注入的实现
    [Dependency(ReplaceServices = true)]
    public class MyServiceReplacesIMyService : IMyService
    {
    }
    
    // 注入
    _services = new ServiceCollection();
    _services.AddTypes(typeof(FirstImplOfMyService),typeof(MyServiceReplacesIMyService));
    
    

    使用ReplaceServices将会使Dependency特性注入替换接口方式的注入。因此只有Dependency特性的注入会被添加到DI容器中。


    代码示例:

    #region ExposeServices 属性注入
    public interface ICalculator { }
    
    public interface ITaxCalculator { }
    
    [ExposeServices(typeof(IService))]
    public class TaxCalculator : ICalculator, ITaxCalculator, ITransientDependency
    {
    }
    #endregion
    
    
    #region 接口约定 模式注入
    
    public interface IService : ITransientDependency { }
    
    public class MyService : IService
    {
    }
    
    #endregion
    
    #region Dependency特性注入
    
    public interface IMyDependencyTest { }
    
    [Dependency(lifetime: ServiceLifetime.Transient, TryRegister = true)]
    public class MyDependencyTest : IMyDependencyTest { }
    
    #endregion
    
    class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection();
            services.AddType(typeof(MyDependencyTest));
            services.AddType(typeof(MyService));
            services.AddType<TaxCalculator>();
    
            foreach(var service in services)
            {
                Console.WriteLine($"{service.ServiceType} --- {service.ImplementationType} --- {service.Lifetime}");
            }
    
            Console.Read();
        }
    }
    

    除去ExposeServices属性注入外,其余的两种模式必须接口与类名相对应。否则,就只能注入类本身,但是,ExposeServics属性注入不会截取接口名与类名比较。

    输出:

    img

  • 相关阅读:
    mysql 历史版本下载
    mysql 5.7 版本 You must reset your password using ALTER USER statement before executing this statement报错处理
    5.7 zip 版本的安装 以及遇到的坑
    mysql 5.6zip版本的卸载与5.7 zip 版本的安装
    mysql数据库的备份与还原
    本地Navicat连接docker里的mysql
    docker修改数据库密码
    docker 在push镜像到本地registry出现的500 Internal Server Error
    linux 没有界面内容显示不全解决办法
    json与map互相转换
  • 原文地址:https://www.cnblogs.com/zhiyong-ITNote/p/12005772.html
Copyright © 2011-2022 走看看