前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【 .NET Core 3.0 】框架之九 || 依赖注入 与 IoC

【 .NET Core 3.0 】框架之九 || 依赖注入 与 IoC

作者头像
老张的哲学
发布2022-04-10 11:27:43
1.1K0
发布2022-04-10 11:27:43
举报
文章被收录于专栏:NetCore 从壹开始

本文有配套视频:https://www.bilibili.com/video/av58096866/?p=5

前言

1、重要:如果你实现了解耦,也就是 api 层只引用了 IService 和 IRepository 的话,那每次修改 service 层,都需要清理解决方案,重新编译项目,因为这个时候你的api层的dll,还是之前未修改的代码。 2、重要+ :请注意,依赖注入的目的不是为了解耦,依赖注入是为了控制反转,通俗来说,就是不用我们自己去 new 服务实例了,所以大家不需要一定去解耦(比如下文说到的我没有引用 Service层 和 Repository层),我下一个DDD系列,依赖注入就没有解耦,因为我用的是自带的注入,不是Autofac的反射dll ,我解耦的目的,是为了让大家更好的理解,服务是怎么注入到宿主容器里的。

说接上文,上回说到了《八 || API项目整体搭建 6.3 异步泛型+依赖注入初探》,后来的标题中,我把仓储两个字给去掉了,因为好像大家对这个模式很有不同的看法,嗯~可能还是我学艺不精,没有说到其中的好处,现在在学DDD领域驱动设计相关资料,有了好的灵感再给大家分享吧。

  虽然项目整体可以运行了,但是我还有几个小知识点要说下,主要是1、依赖注入和AOP相关知识;2、跨域代理等问题(因为Vue是基于Node开发的,与后台API接口不在同一个地址);3、实体类的DTO相关小问题;4、Redis缓存等;5、部署服务器中的各种坑;虽然都是很小的知识点,我还是都下给大家说下的,好啦,开始今天的讲解;

零、今天完成的绿色部分

一、依赖注入的理解和思考

更新(19-04-17):如果想好好的理解依赖注入,可以从以下几个方面入手:

1、项目之间引用是如何起作用的,比如为啥 api 层只是引用了 service 层,那为啥也能使用 repository 和 model 等多层的类? 2、项目在启动的时候,也就是运行时,是如何动态 获取和访问 每一个对象的实例的?也就是 new 的原理 3、项目中有 n 个类,对应 m 个实例等,那这些服务,都放在了哪里?肯定每一个项目都有专属自己的一块。如果项目不启动的话,内存里肯定是没有这些服务的。 4、使用接口(面向抽象)的好处? 5、在项目后期,如何业务需要要全部修改接口的实现类,比如想把 IA = new A();全部 改成 IA = new B(); 6、反射的重要性,为什么要用到反射 dll ?

如果这些每一条自己都能说清楚,那肯定就知道依赖注入是干啥的了。

说到依赖,我就想到了网上有一个例子,依赖注入和工厂模式中的相似和不同:

(1)原始社会里,没有社会分工。须要斧子的人(调用者)仅仅能自己去磨一把斧子(被调用者)。相应的情形为:软件程序里的调用者自己创建被调用者。 (2)进入工业社会,工厂出现。斧子不再由普通人完毕,而在工厂里被生产出来,此时须要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。相应软件程序的简单工厂的设计模式。 (3)进入“按需分配”社会,需要斧子的人不需要找到工厂,我们只需要去干活就行,斧子已经给我们自动准备好了,直接用就可以了。

首先,我们需要了解下什么是控制反转IOC,举个栗子,我在之前开发简单商城的时候,其中呢,订单模块,有订单表,那里边肯定有订单详情表,而且呢订单详情表中还有商品信息表,商品信息表还关联了价格规格表,或者其他的物流信息,商家信息,当然,我们可以放到一个大表里,可是你一定不会这么做,因为太庞大,所以必定分表,那必定会出现类中套类的局面,这就是依赖,比如上边的,订单表就依赖了详情表,我们在实例化订单实体类的时候,也需要手动实例详情表,当然,EF框架中,会自动生成。不过倘若有一个程序员把详情表实体类改错了,那订单表就崩溃了,哦不!我是遇到过这样的情景。

怎么解决这个问题呢,就出现了控制反转。网上看到一个挺好的讲解:

1、没有引入IOC之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,A直接使用new关键字创建B的实例,程序高度耦合,效率低下,无论是创建还是使用B对象,控制权都在自己手上。 2、软件系统在引入IOC容器之后,这种情形就完全改变了,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。 3、依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。依赖注入通常有两种: ·设值注入。 ·构造注入。 这个就是依赖注入的方式。

什么是控制反转(IoC)


Inversion of Control,英文缩写为IoC,不是什么技术,而是一种设计思想。

简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,如下图:

大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“黏合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“黏合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“黏合剂”的由来。

我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:

我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!

在上面文章中,我们已经了解到了,什么是依赖倒置、控制反转(IOC),什么是依赖注入(DI),网上这个有很多很多的讲解,我这里就不说明了,其实主要是见到这样的,就是存在依赖

代码语言:javascript
复制
public class A : D
{

    public A(B b)
    {
        // do something   
    }
    C c = new C();
}

就比如我们的项目中的BlogController,只要是通过new 实例化的,都是存在依赖

代码语言:javascript
复制
public async Task<List<Advertisement>> Get(int id)
{
    IAdvertisementServices advertisementServices = new AdvertisementServices();
    return await advertisementServices.Query(d => d.Id == id);
}

使用依赖注入呢,有以下优点:

  • 传统的代码,每个对象负责管理与自己需要依赖的对象,导致如果需要切换依赖对象的实现类时,需要修改多处地方。同时,过度耦合也使得对象难以进行单元测试。
  • 依赖注入把对象的创造交给外部去管理,很好的解决了代码紧耦合(tight couple)的问题,是一种让代码实现松耦合(loose couple)的机制。
  • 松耦合让代码更具灵活性,能更好地应对需求变动,以及方便单元测试。

举个栗子,就是关于日志记录的

日志记录:有时需要调试分析,需要记录日志信息,这时可以采用输出到控制台、文件、数据库、远程服务器等;假设最初采用输出到控制台,直接在程序中实例化ILogger logger = new ConsoleLogger(),但有时又需要输出到别的文件中,也许关闭日志输出,就需要更改程序,把ConsoleLogger改成FileLogger或者NoLogger, new FileLogger()或者new SqlLogger() ,此时不断的更改代码,就显得心里不好了,如果采用依赖注入,就显得特别舒畅。

二、常见的IoC框架有哪些

1、Autofac+原生

我常用的还是原生注入和 Autofac 注入。

Autofac:貌似目前net下用的最多吧 Ninject:目前好像没多少人用了 Unity:也是较为常见 微软 core 自带的 DI

其实.Net Core 有自己的轻量级的IoC框架,

ASP.NET Core本身已经集成了一个轻量级的IOC容器,开发者只需要定义好接口后,在Startup.cs的ConfigureServices方法里使用对应生

命周期的绑定方法即可,常见方法如下

services.AddTransient<IApplicationService,ApplicationService>//服务在每次请求时被创建,它最好被用于轻量级无状态服务(如我们的Repository和ApplicationService服务) services.AddScoped<IApplicationService,ApplicationService>//服务在每次请求时被创建,生命周期横贯整次请求 services.AddSingleton<IApplicationService,ApplicationService>//Singleton(单例) 服务在第一次请求时被创建(或者当我们在ConfigureServices中指定创建某一实例并运行方法),其后的每次请求将沿用已创建服务。如果开发者的应用需要单例服务情景,请设计成允许服务容器来对服务生命周期进行操作,而不是手动实现单例设计模式然后由开发者在自定义类中进行操作。

当然.Net Core自身的容器还是比较简单,如果想要更多的功能和扩展,还是需要使用上边上个框架。

2、三种注入的生命周期

权重:

AddSingleton→AddTransient→AddScoped

AddSingleton的生命周期:

项目启动-项目关闭 相当于静态类 只会有一个

AddScoped的生命周期:

请求开始-请求结束 在这次请求中获取的对象都是同一个

AddTransient的生命周期:

请求获取-(GC回收-主动释放) 每一次获取的对象都不是同一个

这里来个简单的小DEMO:

1、定义四个接口,并分别对其各自接口实现,目的是测试Singleton,Scope,Transient三种,以及最后的 Service 服务:

代码语言:javascript
复制
 public interface ISingTest
    {
        int Age { get; set; }
        string Name { get; set; }
    }

 public class SingTest: ISingTest
 {
     public int Age { get; set; }
     public string Name { get; set; }
 }

//--------------------------

 public interface ISconTest
 {
     int Age { get; set; }
     string Name { get; set; }
 }
  public class SconTest: ISconTest
  {
      public int Age { get; set; }
      public string Name { get; set; }
  }

//--------------------------
 public interface ITranTest
 {
     int Age { get; set; }
     string Name { get; set; }
 }
  public class TranTest: ITranTest
  {
      public int Age { get; set; }
      public string Name { get; set; }
  }

//-----------------------
public interface IAService
{
    void RedisTest();
}

 public class AService : IAService
 {
     private ISingTest sing; ITranTest tran; ISconTest scon;
     public AService(ISingTest sing, ITranTest tran, ISconTest scon)
     {
         this.sing = sing;
         this.tran = tran;
         this.scon = scon;
     }
     public void RedisTest()
     {

     }
 }

2、项目注入

3、控制器调用

代码语言:javascript
复制
 private ISingTest sing; ITranTest tran; ISconTest scon; IAService aService;
  public ValuesController(ISingTest sing, ITranTest tran, ISconTest scon, IAService aService)
  {
      this.sing = sing;
      this.tran = tran;
      this.scon = scon;
      this.aService = aService;
  }

  // GET api/values
  [HttpGet]
  public ActionResult<IEnumerable<string>> SetTest()
  {
      sing.Age = 18;
      sing.Name = "小红";

      tran.Age = 19;
      tran.Name = "小明";

      scon.Age = 20;
      scon.Name = "小蓝";

      aService.RedisTest();


      return new string[] { "value1", "value2" };
  }

  // GET api/values/5
  [HttpGet("{id}")]
  public ActionResult<string> Get(int id)
  {
      return "value";
  }
代码语言:javascript
复制

4、开始测试,三种注入方法出现的情况

请求SetTest // GET api/values

AddSingleton的对象没有变

AddScoped的对象没有变化

AddTransient的对象发生变化

------------------------------------------------------------

请求 // GET api/values/5

AddSingleton的对象没有变

AddScoped的对象发生变化

AddTransient的对象发生变化

注意:

由于AddScoped对象是在请求的时候创建的

所以不能在AddSingleton对象中使用

甚至也不能在AddTransient对象中使用

所以权重为

AddSingleton→AddTransient→AddScoped

不然则会抛如下异常

三、较好用的IoC框架使用——Autofac

首先呢,我们要明白,我们注入是要注入到哪里——Controller API层。然后呢,我们看到了在接口调用的时候,如果需要其中的方法,需要using两个命名空间

代码语言:javascript
复制
[HttpGet("{id}", Name = "Get")]
public async Task<List<Advertisement>> Get(int id)
{            
     //需要引用两个命名空间Blog.Core.IServices;Blog.Core.Services;
     IAdvertisementServices advertisementServices = new AdvertisementServices();
     return await advertisementServices.Query(d => d.Id == id);
}

接下来我们就需要做处理:

1、引入nuget包

在Nuget中引入两个:Autofac.Extras.DynamicProxy(Autofac的动态代理,它依赖Autofac,所以可以不用单独引入Autofac)、Autofac.Extensions.DependencyInjection(Autofac的扩展),注意是最新版本的。

代码语言:javascript
复制
 <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="5.0.0" />
 <PackageReference Include="Autofac.Extras.DynamicProxy" Version="4.5.0" />

2、配置容器,注入服务

在startup.cs 文件中,增加一个方法,用来配置Autofac 服务容器

首先我们创建一个接口和对应的实现类:

代码语言:javascript
复制
 public interface IAdvertisementServices
 {
     int Test();
 }

 public class AdvertisementServices : IAdvertisementServices
 {
     public int Test()
     {
         return 1;
     }
 }

然后将这个服务,注入到Autofac 容器里:

代码语言:javascript
复制
 public void ConfigureContainer(ContainerBuilder builder)
 {

     var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;

     //直接注册某一个类和接口
     //左边的是实现类,右边的As是接口
     builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();


     //注册要通过反射创建的组件
     var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");
     var assemblysServices = Assembly.LoadFrom(servicesDllFile);

     builder.RegisterAssemblyTypes(assemblysServices)
               .AsImplementedInterfaces()
               .InstancePerLifetimeScope()
               .EnableInterfaceInterceptors();

 }

这个时候我们就把 AdvertisementServices的new 实例化过程注入到了Autofac容器中,

这个时候要看明白,前边的是实现类,后边的是接口,顺序不要搞混了。

3、使用服务工厂,将Autofac容器添加到Host

为什么要这么做呢,从上边你也应该看到了,我们现在仅仅是配置了服务和容器,还没有添加到我们的项目宿主里,那我们的controller就拿不到相应的服务。

我们需要在 Program.cs 文件中,配置 UseServiceProviderFactory

代码语言:javascript
复制
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(new AutofacServiceProviderFactory()) //<--NOTE THIS
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

4、使用构造函数方式来注入

依赖注入有三种方式(构造函数注入、属性注入和方式注入),我们平时基本都是使用其中的构造函数方式实现注入,

大家还记得默认控制器 WeatherForecastController 么,当时我们说到了,已经有了一个依赖注入的用法,就是 ILogger,那现在我们继续注入,

代码语言:javascript
复制
 private readonly ILogger<WeatherForecastController> _logger;
 private readonly IAdvertisementServices advertisementServices;

 public WeatherForecastController(ILogger<WeatherForecastController> logger, IAdvertisementServices advertisementServices)
 {
     _logger = logger;
     this.advertisementServices = advertisementServices;
 }

 /// <summary>
 /// 获取接口数据
 /// </summary>
 /// <returns></returns>
 [HttpGet]
 public string[] Get()
 {
     var ads = advertisementServices.Test();
     return Summaries;
 }

5、效果调试,已经成功

然后运行调试,发现在断点刚进入的时候,接口已经被实例化了,达到了注入的目的。

注意:这里经常会遇到一个错误:None of the constructors found with ........,

查看你的service服务,是不是用了其他的仓储repository,但是又缺少了构造函数。

如果没有问题,大家就需要想想,除了 Autofac 还有没有其他的不用第三方框架的注入方法呢?聪明如你,netcore 还真自带了注入扩展。

6、NetCore 自带的注入实现效果

当然,我们用 Asp.net core 自带的注入方式也是可以的,也挺简单的,这里先说下使用方法:

代码语言:javascript
复制
 // 注入 service
 services.AddScoped<IAdvertisementServices, AdvertisementServices>();

这个时候,我们发现已经成功的注入了,而且特别简单,那为啥还要使用 Autofac 这种第三方扩展呢,我们想一想,上边我们仅仅是注入了一个 Service ,但是项目中有那么多的类,都要一个个手动添加么,多累啊,答案当然不是滴~

四、整个 dll 程序集的注入

1、服务程序集注入方式 —— 未解耦

通过反射将 Blog.Core.Services 和 Blog.Core.Repository 两个程序集的全部方法注入

修改如下代码,注意这个时候需要在项目依赖中,右键,添加引用 Blog.Core.Services 层和 Repository 层 到项目中,如下图,这个时候我们的程序依赖了具体的服务

核心代码如下,注意这里是 Load 模式(程序集名),还是在startup.cs 文件中,配置Autofac容器。

代码语言:javascript
复制
 public void ConfigureContainer(ContainerBuilder builder)
 {

     var assemblysServices = Assembly.Load("Blog.Core.Services");
   
     builder.RegisterAssemblyTypes(assemblysServices)
               .AsImplementedInterfaces()
               .InstancePerLifetimeScope()
               .EnableInterfaceInterceptors();
 }

其他不变,运行项目,一切正常,换其他接口也可以,具体的就不细说。

到这里,Autofac依赖注入已经完成,基本的操作就是这样,不过可能你还没有真正体会到注入的好处,挑战下吧,看看下边的内容,将层级进行解耦试试!


2、程序集注入 —— 实现层级解耦

这是一个学习的思路,大家要多想想,可能会感觉无聊或者没用,但是对理解项目启动和加载,还是很有必要的。

1、项目最终只依赖抽象

最终的效果是这样的:工程只依赖抽象,把两个实现层删掉,引用这两个接口层。

2、配置仓储和服务层的程序集输出

将 Blog.Repository 层和 Service 层项目生成地址改成相对路径,这样大家就不用手动拷贝这两个 dll 了,F6编译的时候就直接生成到了 api 层 bin 下了:

“...\Blog.Core\bin\Debug\”

3、使用 LoadFile 加载服务层的程序集
代码语言:javascript
复制
 var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//获取项目路径
 var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");//获取注入项目绝对路径
 var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加载文件的方法

这个时候,可能我们编译成功后,页面能正常启动,证明我们已经把 Service 和 Repository 两个服务层的所有服务给注册上了,但是访问某一个接口,还是会出现错误:

这个错误表示,我们的 SqlSugar 服务,没有被注册成功,那肯定就是我们的 Sqlsugar 程序集没有正常的引用,怎么办呢,直接在 api 层引用下就行。

4、解除Service层和Repository层之间的耦合

还记得Blog.Core.Services中的BaseServices.cs么,它还是通过new 实例化的方式在创建,仿照contrller,修改BaseServices并在全部子类的构造函数中注入:

代码语言:javascript
复制
 public class BaseServices<TEntity> : IBaseServices<TEntity> where TEntity : class, new()
    {
        //public IBaseRepository<TEntity> baseDal = new BaseRepository<TEntity>();
        public IBaseRepository<TEntity> baseDal;//通过在子类的构造函数中注入,这里是基类,不用构造函数
      //...
   }


    public class AdvertisementServices : BaseServices<Advertisement>, IAdvertisementServices
    {
        IAdvertisementRepository dal;
        public AdvertisementServices(IAdvertisementRepository dal)
        {
            this.dal = dal;
            base.baseDal = dal;
        }
    }

好啦,现在整个项目已经完成了相互直接解耦的功能,以后就算是Repository和Service如何变化,接口层都不用修改,因为已经完成了注入,第三方Autofac会做实例化的过程。

5、容器内查看注入的服务数据

如果你想看看是否注入到容器里成功了,可以直接看看容器 ApplicationContainer 的内容:

五、 无接口项目注入

1、接口形式的注入

上边我们讨论了很多,但是都是接口框架的,

比如:Service.dll 和与之对应的 IService.dll,Repository.dll和与之对应的 IRepository.dll,

这样,我们在多层之间使用服务的话,直接将我们需要使用的 new 对象,注入到容器里,然后我们就可以使用相应的接口了,

比如:我们想在 controller 里使用AdvertisementServices 类,那我们就可以直接使用它的接口 IAdvertisementServices,这样就很好的达到了解耦的目的,这样我们就可以在API层,就轻松的把 Service.dll 给解耦了;

如果我们需要在 Service类里,使用 AdvertisementRepository ,我们就直接使用对应的接口 IAdvertisementRepository,这样,我们就从 Service 层中,把仓储层给解耦了。

2、如果没有接口

案例是这样的:

如果我们的项目是这样的,没有接口,会怎么办:

代码语言:javascript
复制
// 服务层类
   public class StudentService
    {
        StudentRepository _studentRepository;
        public StudentService(StudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }


        public string Hello()
        {
            return _studentRepository.Hello();
        }

    }


    // 仓储层类
     public class StudentRepository
    {
        public StudentRepository()
        {

        }

        public string Hello()
        {
            return "hello world!!!";
        }

    }


    // controller 接口调用
    StudentService _studentService;

    public ValuesController(StudentService studentService)
    {
        _studentService = studentService;
    }

这样的话,我们就不能使用上边的接口注入模式了,因为我们上边是把注入的服务,对应注册给了接口了 .AsImplementedInterfaces() ,我们就无法实现解耦了,因为根本没有了接口层,所以我们只能引用实现类层,这样注入:

通过 builder.RegisterAssemblyTypes(assemblysRepository); 方法直接注入服务,没有其他的东西。

3、如果是没有接口的单独实体类

代码语言:javascript
复制
public class Love
    {
        // 一定要是虚方法
        public virtual string SayLoveU()
        {
            return "I ♥ U";
        }

    }

//---------------------------

////只能注入该类中的虚方法
builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love)))
    .EnableClassInterceptors()
    .InterceptedBy(typeof(BlogLogAOP));

六、同一接口多实现类注入

这里暂时没有实例代码,如果你正好需要,可以看看这个博友的栗子:https://www.cnblogs.com/fuyujian/p/4115474.html

我会在之后的时间写个栗子放到这里。

七、CODE

https://github.com/anjoy8/Blog.Core

https://gitee.com/laozhangIsPhi/Blog.Core

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 NetCore 从壹开始 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 零、今天完成的绿色部分
  • 一、依赖注入的理解和思考
  • 什么是控制反转(IoC)
  • 二、常见的IoC框架有哪些
    • 1、Autofac+原生
      • 2、三种注入的生命周期
      • 三、较好用的IoC框架使用——Autofac
        • 1、引入nuget包
          • 2、配置容器,注入服务
            • 3、使用服务工厂,将Autofac容器添加到Host
              • 4、使用构造函数方式来注入
                • 5、效果调试,已经成功
                  • 6、NetCore 自带的注入实现效果
                  • 四、整个 dll 程序集的注入
                    • 1、服务程序集注入方式 —— 未解耦
                      • 2、程序集注入 —— 实现层级解耦
                      • 五、 无接口项目注入
                        • 1、接口形式的注入
                          • 2、如果没有接口
                            • 3、如果是没有接口的单独实体类
                            • 六、同一接口多实现类注入
                            • 七、CODE
                            相关产品与服务
                            容器服务
                            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档