APB VNext系列(一):依赖注入

兄弟姐妹们,还有三个月就2021年了,给大家提前拜个早年~嘻嘻。

本系列我将梳理ABP VNext中的技术实现,在自我记录的同时,也帮大家更加深入了解下这个最近非常火的框架。

什么是ABP VNext这里就不做介绍了,大家可以去看资料了解下。这里讲解我们实际业务中如何使用该框架。

1.简介

   ABP中依赖注入是基于Microsoft.Extensions.DependencyInjection实现的,在组件开发过程中其实现方式和包括AutoFac在内的第三方类库大同小异。

  在实际的开发使用中,我们并没有使用第三方类库,完全基于Microsoft的依赖注入扩展库进行的组件开发。

 

2.如何实现依赖注入?

我们先来看下传统的实现方式:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddTransient<ITestApiService, TestApiService>();
        }

 

可以看到我们需要使用IServiceCollection这个属性,让我们来看下IServiceCollection的内部实现:

public class ServiceCollection : IServiceCollection
{
    private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();

    public int Count => _descriptors.Count;

    public bool IsReadOnly => false;

    public ServiceDescriptor this[int index]
    {
        get
        {
            return _descriptors[index];
        }
        set
        {
            _descriptors[index] = value;
        }
    }

    // ...
}

 

可以看到IServiceCollection没有定义其任何成员,而是从IList<ServiceDescriptor>派生,本质上是一个集合。

在实际生产过程中,我们肯定不能一个个的手动去注册,而是希望通过某种约定配置让其实现自动注册。

 

2.如何自定义实现一个依赖注入组件?

首先我们要新建三个接口,分别是L:ITransientDependency、ISingletonDependency、IScopedDependency,分别对应三种生命周期:

    public interface ITransientDependency
    {

    }    

    public interface ISingletonDependency
    {

    }

    public interface IScopedDependency
    {

    }

 

所有需要实现依赖注入的类都需要继承这其中的某个接口。

 

然后我们新建一个接口以及实现类:

    public interface ITestApiService
    {
        public string GetResult();
    }


    public class TestApiService : ITestApiService, ITransientDependency
    {
        public string GetResult()
        {
            return "This is test!";
        }
    }

 

我们还需要新建一个接口IApplication,用来获取程序集。(也可以通过其他方式,只不过这个IApplication后面其他功能会用到)

    public interface IApplication
    {

    }

然后下面是完整实现:

public void DependencyRegister(IServiceCollection services)
        {
            var assembly = typeof(IApplication).GetTypeInfo().Assembly;
            var types = assembly.GetTypes()
                .Where(type => type != null
                && type.IsClass
                && !type.IsAbstract
                && !type.IsGenericType).ToArray();

            foreach (var type in types)
            {
                ServiceLifetime? lifeTime = null;

                if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))
                {
                    lifeTime = ServiceLifetime.Transient;
                }

                if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))
                {
                    lifeTime = ServiceLifetime.Transient;
                }

                if (typeof(ITransientDependency).GetTypeInfo().IsAssignableFrom(type))
                {
                    lifeTime = ServiceLifetime.Transient;
                }

                if (!lifeTime.HasValue)
                    continue;

                var serviceTypes = type
                .GetCustomAttributes()
                .OfType<IExposedServiceTypesProvider>()
                .DefaultIfEmpty(new ExposeServicesAttribute
                {
                    IncludeDefaults = true,
                    IncludeSelf = true
                })
                .SelectMany(p => p.GetExposedServiceTypes(type))
                .ToList();

                foreach (var serviceType in serviceTypes)
                {
                    var serviceDescriptor = ServiceDescriptor.Describe(serviceType, type, lifeTime.Value);
                    services.Add(serviceDescriptor);
                }
            }
        }
    public interface IExposedServiceTypesProvider
    {
        Type[] GetExposedServiceTypes(Type targetType);
    }

    public class ExposeServicesAttribute : Attribute, IExposedServiceTypesProvider
    {
        public Type[] ServiceTypes { get; }

        public bool? IncludeDefaults { get; set; }

        public bool? IncludeSelf { get; set; }

        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;
        }
    }

首先我们通过IApplication用反射的方式获取到程序集中所有需要被检查的类,然后通过判断其继承的接口类型决定需要实现的生命周期。

这里的默认规则是接口定义层的名称去掉“I”字符后与实现层类相同,则表明是需要注册的。

看完是不是突然觉得挺简单的了!

 

posted @ 2020-09-20 23:04  名字都被注册了  阅读(827)  评论(0编辑  收藏  举报