浅谈.Net Core DependencyInjection源码探究

前言

对于IOC和DI,可能每个人都能说出自己的理解。IOC全称是Inversion of Control翻译成中文叫控制反转,简单的说就是把对象的控制权反转到IOC容器中,由IOC管理其生命周期。DI全称是DependencyInjection翻译成中文叫依赖注入,就是IOC容器把你依赖的模块通过注入的方式提供给你,而不是你自己主动去获取,其形式主要分为构造注入和属性注入,Core自带的DI只支持构造注入,至于为什么,最多的说法就是构造注入能使得依赖变得更清晰,我既然依赖你,那么我实例化的时候你就必须得出现。而构造函数恰恰就承担着这种责任。

简单介绍

    很多人接触它的时候应该都是从Asp.Net Core学习过程中开始的。其实它本身对Asp.Net Core并无依赖关系,Asp.Net Core依赖DI,但是这套框架本身并不只是可以提供给Asp.Net Core使用,它是一套独立的框架,开源在微软官方Github的extensions仓库中具体地址是https://github.com/dotnet/extensions/tree/v3.1.5/src/DependencyInjection。关于如何使用,这里就不再多说了,相信大家都非常清楚了。那咱们就说点不一样的。

服务注册

我们都知道提供注册的服务名称叫IServiceCollection,我们大部分情况下主要使用它的AddScoped、AddTransient、AddSingleton来完成注册。我们就先查看一下IServiceCollection接口的具体实现,找到源码位置

public interface IServiceCollection : IList<ServiceDescriptor>
{
}

(⊙o⊙)…额,你并没有看错,这次我真没少贴代码,其实IServiceCollection本质就是IList,而且并没有发现AddScoped、AddTransient、AddSingleton踪影,说明这几个方法是扩展方法,我们找到ServiceCollectionServiceExtensions扩展类的位置,我们平时用的方法都在这里,由于代码非常多这里就不全部粘贴出来了,我们只粘贴AddTransient相关的,AddScoped、AddSingleton的实现同理

/// <summary>
/// 通过泛型注册
/// </summary>
public static IServiceCollection AddTransient<TService, TImplementation>(this IServiceCollection services)where TService : classwhere TImplementation : class, TService
{if (services == null){throw new ArgumentNullException(nameof(services));}//得到泛型类型return services.AddTransient(typeof(TService), typeof(TImplementation));
}/// <summary>
/// 根据类型注册
/// </summary>
public static IServiceCollection AddTransient(this IServiceCollection services,Type serviceType,Type implementationType)
{if (services == null){throw new ArgumentNullException(nameof(services));}if (serviceType == null){throw new ArgumentNullException(nameof(serviceType));}if (implementationType == null){throw new ArgumentNullException(nameof(implementationType));}return Add(services, serviceType, implementationType, ServiceLifetime.Transient);
}/// <summary>
/// 根据类型实例来自工厂注册方法
/// </summary>
public static IServiceCollection AddTransient(this IServiceCollection services,Type serviceType,Func<IServiceProvider, object> implementationFactory)
{if (services == null){throw new ArgumentNullException(nameof(services));}if (serviceType == null){throw new ArgumentNullException(nameof(serviceType));}if (implementationFactory == null){throw new ArgumentNullException(nameof(implementationFactory));}return Add(services, serviceType, implementationFactory, ServiceLifetime.Transient);
}

通过以上代码我们可以得到两个结论,一是注册服务的方法本质都是在调用Add重载的两个方法,二是声明周期最终还是通过ServiceLifetime来控制的AddScoped、AddTransient、AddSingleton只是分文别类的进行封装而已,我们来看ServiceLifetime的源码实现

public enum ServiceLifetime
{/// <summary>/// 指定将创建服务的单个实例。/// </summary>Singleton,/// <summary>/// 指定每个作用域创建服务的新实例。/// </summary>Scoped,/// <summary>/// 指定每次请求服务时都将创建该服务的新实例。/// </summary>Transient
}

这个枚举是为了枚举我们注册服务实例的声明周期的,非常清晰不在过多讲述,接下来我们看核心的两个Add方法的实现

private static IServiceCollection Add(IServiceCollection collection,Type serviceType,Type implementationType,ServiceLifetime lifetime)
{var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);collection.Add(descriptor);return collection;
}private static IServiceCollection Add(IServiceCollection collection,Type serviceType,Func<IServiceProvider, object> implementationFactory,ServiceLifetime lifetime)
{var descriptor = new ServiceDescriptor(serviceType, implementationFactory, lifetime);collection.Add(descriptor);return collection;
}

通过这两个核心方法我们可以非常清晰的了解到注册的本质其实就是构建ServiceDescriptor实例然后添加到IServiceCollection即IList中,这里我们都是列举的根据实例去注册抽象的类型,还有一种是只注册具体类型或者具体实例的方法,这个是怎么实现的呢。

public static IServiceCollection AddTransient(this IServiceCollection services,Type serviceType)
{if (services == null){throw new ArgumentNullException(nameof(services));}if (serviceType == null){throw new ArgumentNullException(nameof(serviceType));}//把自己注册给自己return services.AddTransient(serviceType, serviceType);
}

通过这个方法我们就可以看到其实注册单类型的方法,也是通过调用的注入实例到抽象的方法,只不过是将自己注册给了自己。
好了,抽象和扩展方法我们就先说到这里,接下来我们来看IServiceCollection的实现类ServiceCollection的实现

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;}}public void Clear(){_descriptors.Clear();}public bool Contains(ServiceDescriptor item){return _descriptors.Contains(item);}public void CopyTo(ServiceDescriptor[] array, int arrayIndex){_descriptors.CopyTo(array, arrayIndex);}public bool Remove(ServiceDescriptor item){return _descriptors.Remove(item);}public IEnumerator<ServiceDescriptor> GetEnumerator(){return _descriptors.GetEnumerator();}void ICollection<ServiceDescriptor>.Add(ServiceDescriptor item){_descriptors.Add(item);}IEnumerator IEnumerable.GetEnumerator(){return GetEnumerator();}public int IndexOf(ServiceDescriptor item){return _descriptors.IndexOf(item);}public void Insert(int index, ServiceDescriptor item){_descriptors.Insert(index, item);}public void RemoveAt(int index){_descriptors.RemoveAt(index);}
}

这个类就非常清晰,也非常简单了。ServiceCollection承载了一个List的集合,由于实现了IList接口,所以该类实现了接口的方法,实现了对List集合的操作,其核心就是ServiceDescriptor服务描述类,我们看一下大致的源码。

public class ServiceDescriptor
{public ServiceDescriptor(Type serviceType,Type implementationType,ServiceLifetime lifetime): this(serviceType, lifetime){ImplementationType = implementationType;}public ServiceDescriptor(Type serviceType,object instance): this(serviceType, ServiceLifetime.Singleton){ImplementationInstance = instance;}public ServiceDescriptor(Type serviceType,Func<IServiceProvider, object> factory,ServiceLifetime lifetime): this(serviceType, lifetime){ImplementationFactory = factory;}private ServiceDescriptor(Type serviceType, ServiceLifetime lifetime){Lifetime = lifetime;ServiceType = serviceType;}public ServiceLifetime Lifetime { get; }public Type ServiceType { get; }public Type ImplementationType { get; }public object ImplementationInstance { get; }public Func<IServiceProvider, object> ImplementationFactory { get; }
}

这里我们只是粘贴了初始化的方法,通过这个初始化我们得到了,本质其实就是给描述具体注册的Lifetime、ServiceType、ImplementationType、ImplementationInstance、ImplementationFactory赋值。在平时的使用中,我们在注册服务的时候还会用到这种注册方式

services.Add(ServiceDescriptor.Scoped<IPersonService, PersonService>());
//services.Add(ServiceDescriptor.Scoped(typeof(IPersonService),typeof(PersonService)));
//或
services.Add(ServiceDescriptor.Transient<IPersonService, PersonService>());
//services.Add(ServiceDescriptor.Transient(typeof(IPersonService), typeof(PersonService)));
//或
services.Add(ServiceDescriptor.Singleton<IPersonService, PersonService>());
//services.Add(ServiceDescriptor.Singleton(typeof(IPersonService), typeof(PersonService)));

这种注册方式是通过ServiceDescriptor自身的操作去注册相关实例,我们拿出来其中一个Transient看一下具体实现

public static ServiceDescriptor Transient<TService, TImplementation>()where TService : classwhere TImplementation : class, TService
{//都是在调用Describereturn Describe<TService, TImplementation>(ServiceLifetime.Transient);
}public static ServiceDescriptor Transient(Type service, Type implementationType)
{//都是在调用Describereturn Describe(service, implementationType, ServiceLifetime.Transient);
}public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime)
{//还是返回ServiceDescriptor实例return new ServiceDescriptor(serviceType, implementationType, lifetime);
}public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime)
{//还是返回ServiceDescriptor实例return new ServiceDescriptor(serviceType, implementationFactory, lifetime);
}

通过这个我们就可以了解到ServiceDescriptor.Scoped、ServiceDescriptor.Singleton、ServiceDescriptor.Singleton其实是调用的Describe方法,Describe的本身还是去实例化ServiceDescriptor,殊途同归,只是多了种写法,最终还是去构建ServiceDescriptor。通过这么多源码的分析得出的结论就一点IServiceCollection注册的本质就是在构建ServiceDescriptor集合。

服务提供

上面我们了解到了服务注册相关,至于服务是怎么提供出来的,大家应该都是非常熟悉了其实是根据IServiceCollection构建出来的

IServiceProvider serviceProvider = services.BuildServiceProvider();

BuildServiceProvider并不是IServiceCollection的自带方法,所以也是来自扩展方法,找到ServiceCollectionContainerBuilderExtensions扩展类,最终都是在执行这个方法

public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
{return new ServiceProvider(services, options);
}

BuildServiceProvider的时候需要传递ServiceProviderOptions这个类主要是配置是否校验作用域和提供的实例来自于那种提供引擎使用

public class ServiceProviderOptions
{internal static readonly ServiceProviderOptions Default = new ServiceProviderOptions();/// <summary>/// 是够在编译的时候校验作用域范围检查/// </summary>public bool ValidateScopes { get; set; }/// <summary>/// 是够在编译的时候校验作用域范围检查/// </summary>public bool ValidateOnBuild { get; set; }/// <summary>/// 配置使用那种方式提供ServiceProvider的承载的具体实例/// </summary>internal ServiceProviderMode Mode { get; set; } = ServiceProviderMode.Default;
}internal enum ServiceProviderMode
{Default,Dynamic,Runtime,Expressions,ILEmit
}

作用域范围检查还是非常严格的,不开启的也会有一定的依赖规则,简单总结一下

  • 如果开启了范围检查,有依赖关系的模型如果生命周期不一致就会报错,如果不存Scope声明但是获取AddScoped也是会有异常的

  • 如果不开启范围检查,如果生命周期长的依赖生命周期短的,那么被依赖的模型将会被提升和依赖模型同等的生命周期。如果生命周期短的模型依赖生命周期长的模型,将保持和注册时候的生命周期一致。

接下来我们查看一下服务提供核心IServiceProvider的实现,这个接口只包含一个抽象,那就是根据"注册类型"获取具体实例,其他获取实例的方法都是根据这个方法扩展而来

public interface IServiceProvider
{object GetService (Type serviceType);
}

ServiceProvider是IServiceProvider的默认实现类,它是获取注册实例的默认出口类,我们只看提供服务相关的

public sealed class ServiceProvider : IServiceProvider, IDisposable, IServiceProviderEngineCallback, IAsyncDisposable
{private readonly IServiceProviderEngine _engine;private readonly CallSiteValidator _callSiteValidator;internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options){IServiceProviderEngineCallback callback = null;if (options.ValidateScopes){callback = this;_callSiteValidator = new CallSiteValidator();}//根据ServiceProviderMode的值判断才有那种方式去实例化对象switch (options.Mode){//默认方式case ServiceProviderMode.Default:if (RuntimeFeature.IsSupported("IsDynamicCodeCompiled")){_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);}else{_engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);}break;case ServiceProviderMode.Dynamic:_engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);break;case ServiceProviderMode.Runtime:_engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);break;//if IL_EMITcase ServiceProviderMode.ILEmit:_engine = new ILEmitServiceProviderEngine(serviceDescriptors, callback);break;case ServiceProviderMode.Expressions:_engine = new ExpressionsServiceProviderEngine(serviceDescriptors, callback);break;default:throw new NotSupportedException(nameof(options.Mode));}//判断是否开启编译时范围校验if (options.ValidateOnBuild){List<Exception> exceptions = null;foreach (var serviceDescriptor in serviceDescriptors){try{_engine.ValidateService(serviceDescriptor);}catch (Exception e){}}}}/// <summary>/// 通过IServiceProviderEngine获取具体实例的方法/// </summary>public object GetService(Type serviceType) => _engine.GetService(serviceType);
}

在这个类里,关于提供具体实例的操作还是非常清晰的,关于更深的IServiceProviderEngine这里就不过多介绍了,有兴趣的可以自行在GitHub上查阅。

关于Scope问题

在声明周期里Scope是比较特殊也是比较抽象的一个,我们使用的时候是通过当前serviceProvider创建子作用域

using (IServiceScope scope = serviceProvider.CreateScope())
{IServiceProvider scopeProvider = scope.ServiceProvider;
}

它大概的思路就是在当前容器中创建一个作用域,scope.ServiceProvider来获取这个子容器作用域里的实例。Singleton类型的实例直接去根容器获取,所以和当前子容器作用域无关。Scoped类型的实例,在当前作用域内唯一,无论获取多少次返回的都是同一个实例。Transient类型的只要去获取都是返回新的实例。当前IServiceScope释放的时候Scoped类型的实例也会被释放,注意!!!Transient类型的实例也是在当前IServiceScope Dispose的时候去释放,尽管你每次获取的时候都是新的实例,但是释放的时候都是统一释放的。在当前ServiceScope内你可以继续创建当前Scope的IServiceScope。其实通过这里也不难发现根容器的Scoped其实就是等同于Singleton,其生命周期都是和应用程序保持一致。
    Scope问题在如果写控制台之类的程序其作用可能不是很明显,除非有特殊的要求,在Asp.Net Core中使用还是比较深入的。Asp.Net Core在启动的时候会创建serviceProvider,这个serviceProvider的Scope是跟随程序的生命周期一致的,它是作为所有服务实例的根容器。在Asp.Net Core中有几种情况的实例和请求无关也就是说在程序运行期间是单例情况的,我们使用的时候需要注意的地方

  • 通过Startup.cs的构造函数注入的IHostEnvironment、IWebHostEnvironment、IConfiguration

  • 在Startup.cs类中的Configure方法注入的

  • 使用约定方式自定义的中间件,是在程序初始化的时候被执行的所以根据约定方式定义的中间件的构造函数注入的也是单例的。

  • 使用约定方式自定义的中间件,是在程序初始化的时候被执行的所以根据约定方式定义的中间件的构造函数注入的也是单例的。

其实就一点,在程序初始化过程中创建的类大部分都是和请求无关的,通常这一类方法或者具体的实例注入的依赖都是和程序生命周期保持一致的,即单例模式。Asp.Net Core在每次处理请求的时候会在根容器创建一个Scope范围的ServiceProvider,也就是我们所说的Asp.Net Core在每次请求过程中是唯一的情况。

  • 自定义实现了IMiddleware的中间件,且生命周期为Scoped的情况。

  • 中间件中Invoke或InvokeAsync注入的相关实例,且注册的时候为Scoped的情况。

  • Controller中或者为Controller提供服务的相关类,比如EF SQLConnection或其他连接服务相关,或者自定义的Service等,且注册的时候为Scoped的情况。 这里说明一点,默认情况下Controller并不是通过容器创建的,而是通过反射创建的。如果需要将Controller也托管到容器中,需要使用services.AddControllers().AddControllersAsServices()的方式,这个操作在使用Autofac容器的时候在Controller中使用属性注入是必不可少的。

  • 还有就是通过Inject注册到RazorPage视图页面中的情况。

关于UseServiceProviderFactory

UseServiceProviderFactory方法主要是为我们提供了替换默认容器的操作,通过这个方法可以将三方的IOC框架结合进来比如Autofac。我们可以查看UseServiceProviderFactory具体的实现,了解它的工作方式。这个方法来自HostBuilder类

public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));return this;
}

我们找到_serviceProviderFactory定义的地方,默认值就是为ServiceFactoryAdapter传递了DefaultServiceProviderFactory实例。

private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());

继续查找ServiceFactoryAdapter的大致核心实现

internal class ServiceFactoryAdapter<TContainerBuilder> : IServiceFactoryAdapter
{private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;public object CreateBuilder(IServiceCollection services){return _serviceProviderFactory.CreateBuilder(services);}public IServiceProvider CreateServiceProvider(object containerBuilder){return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder);}
}

通过查找HostBuilder中这段源码我们可以知道ServiceFactoryAdapter创建出来的容器是供整个Host使用的。也就是说我们在程序中使用的容器相关的都是由它提供的。
接下来我们看下默认的DefaultServiceProviderFactory的大致实现。找到源码位置

public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{public IServiceCollection CreateBuilder(IServiceCollection services){return services;}public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder){return containerBuilder.BuildServiceProvider(_options);}
}

没啥逻辑,其实就是把默认的IServiceCollection和IServiceProvider通过工厂的形式提供出来。这么做的目的只有一个,就是降低依赖的耦合度方便我们能够介入第三方的IOC框架。口说无凭,接下来我们就看一下Autofac是怎么适配进来的。我们在GitHub上找到Autofac.Extensions.DependencyInjection仓库的位置https://github.com/autofac/Autofac.Extensions.DependencyInjection,找到Autofac中IServiceProviderFactory实现类AutofacServiceProviderFactory,看看他是如何适配到默认的IOC框架的

public class AutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
{private readonly Action<ContainerBuilder> _configurationAction;public AutofacServiceProviderFactory(Action<ContainerBuilder> configurationAction = null){_configurationAction = configurationAction ?? (builder => { });}public ContainerBuilder CreateBuilder(IServiceCollection services){//由于是使用Autofac本身的容器去工作,所以返回的Autofac承载类ContainerBuildervar builder = new ContainerBuilder();//将现有的IServiceCollection中注册的实例托管到ContainerBuilder中builder.Populate(services);//这一步是我们自定义注入到Autofac方法的委托,及我们在Startup类中定义的//public void ConfigureContainer(ContainerBuilder builder)方法_configurationAction(builder);return builder;}public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder){if (containerBuilder == null) throw new ArgumentNullException(nameof(containerBuilder));//获取Container容器,因为接下来要使用获取实例的方法了var container = containerBuilder.Build();//这个类实现了IServiceProvider接口//实现了public object GetService(Type serviceType)方法从Autofac的Container中获取实例return new AutofacServiceProvider(container);}
}

IServiceProviderFactory的工作其实就是适配符合我们使用的适配器模式,其核心就是用你的容器去托管注册到IServiceCollection中的服务。然后用你的容器去构建IServiceProvider实例。

总结

    通过以上我们对自带的DependencyInjection工作方式有了一定的了解,而且其扩展性非常强,能够使我们通过自己的方式去构建服务注册和注入,我们以Autofac为例讲解了三方容器集成到自带IOC的方式。有很多核心的源码并没有讲解到,因为怕自己理解不够,就不误导大家了。我在上文中涉及到源码的地方基本上都加了源码的连接,可以直接点进去查看源码,之前源码探究相关的文章也都是一样,可能之前有许多同学没有注意到。主要原因是我粘贴出来的代码有删减,最重要的还是怕自己理解不到位,误导了大家,这样就能用过点击自己查看源码了。如有你有更好的理解,或者觉得我讲解的理解不到的地方,欢迎评论区沟通交流。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[mybatis]Configuration XML_settings

settings settings包含很多重要的设置项 setting:用来设置每一个设置项name:设置项名value:设置项取值 mapUnderscoreToCamelCase 开启驼峰命名法 <settings><setting name"mapUnderscoreToCamelCase" value"true"/></settings>Emplo…

Chain of responsibility(职责链)--对象行为型模式

Chain of responsibility&#xff08;职责链&#xff09;–对象行为型模式 一、意图 使多个对象有机会处理请求&#xff0c;从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处理它为止。 二…

[mybatis]Configuration XML_typeAliases and Alias

There are many built-in type aliases for common Java types. They are all case insensitive, note the special handling of primitives due to the overloaded names. typeAliases typeAlias:为某个java类型起别名 type:指定要起别名的类型全类名;默认别名就是类名小写;em…

Command(命令)--对象行为型模式

Command&#xff08;命令&#xff09;–对象行为型模式 一、意图 将一个请求封装为一个对象&#xff0c;从而使你可用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 二、动机 1.在软件构建过程中&#xff0c;“行为请求…

实现业务数据的同步迁移 · 思路一

&#xff08;好雨知时节&#xff0c;大雨 _ _ _&#xff09;时不时的呢&#xff0c;会有小伙伴问我这样的问题&#xff1a;1、群主&#xff0c;你的.tsv文件是如何生成的&#xff1f;2、在线项目数据和种子数据的不一样&#xff0c;可以下么&#xff1f;3、如果我本地的数据开发…

Interpreter(解释器)--类行为型模式

Interpreter&#xff08;解释器&#xff09;–类行为型模式 一、意图 给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;这个解释器使用该表示来解释语言中的句子。 二、动机 1.在软件构建过程中&#xff0c;如果某一特定领域的问题比…

基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)

一、前言回顾&#xff1a;基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (上篇)在上一篇中&#xff0c;主要讲解了授权在配置方面的源码&#xff0c;从添加授权配置开始&#xff0c;我们引入了需要的授权配置选项&#xff0c;而不同的授权要求构建不同的策略方式&#xff0…

Iterator(迭代器)--对象行为模式

Iterator&#xff08;迭代器&#xff09;–对象行为模式 一、意图 提供一种方法顺序访问一个聚合对象的各个元素&#xff0c;而又不需暴露该对象的内部结构。 二、动机 1.在软件构建过程中&#xff0c;集合对象内部结构常常变化各异。但对于这些集合对象&#xff0c;我们希望…

Blazor带我重玩前端(三)

VS自带的Blazor模板介绍需要升级VS2019以及.NET Core到最新版&#xff08;具体的最低支持&#xff0c;我已经忘了&#xff0c;总是越新支持的就越好&#xff09;&#xff0c;以更好的支持自己开发Blazor项目。使用VS创建Blazor WebAssembly项目搜索Blazor模板选择Blazor WebAss…

[mybatis]Configuration XML_environments

enviroments transactionManager There are two TransactionManager types (i.e. type"[JDBC|MANAGED]") that are included with MyBatis: • JDBC – This configuration simply makes use of the JDBC commit and rollback facilities directly. It relies on the…

Mediator(中介者)--对象行为型模式

Mediator&#xff08;中介者&#xff09;–对象行为型模式 一、意图 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互。 二、动机 1.在软件构建过程中&#xff0c;经常…

[mybatis]Configuration XML_mappers

mappers 将sql映射注册到全局配置中 mapper 注册一个sql映射 resource:引用类路径下的sql映射文件url:引用网络路径或者磁盘路径下的sql映射文件class:引用(注册)接口 1.有sql映射文件&#xff0c;映射文件名必须和接口同名&#xff0c;并且放在与接口同一目录下&#xff1b;…

Memento(备忘录)--对象行为型模式

Memento&#xff08;备忘录&#xff09;–对象行为型模式 一、意图 在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保持这个状态。这样以后就可将该对象恢复到原先保存的状态。 二、动机 1.在软件构建过程中&#xff0c;某些对象的状态…

IBM、甲骨文、CNCF 就谷歌对 Istio 治理的处理提出抗议

近日来 Istio 商标转让、IBM 抗议谷歌违背承诺未将 Istio 捐献给 CNCF 的事情闹的沸沸扬扬。Google 宣布将 Istio 商标转让给 Open Usage Commons 组织IBM 声明对 Google 违背承诺未将 Istio 贡献给 CNCF 表示失望下面是据 TheRegister 的报道&#xff1a;谷歌创建了一个开放使…

Observer(观察者)--对象行为型模式

Observer&#xff08;观察者&#xff09;–对象行为型模式 一、意图 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生变化时&#xff0c;所有的依赖于它的对象都得到通知并被自动更新。 二、动机 1.在软件构建过程中&#xff0c;我们需要为某些对象建立一…

【今天下午活动】从 HelloWorld 到 AntDesign,Blazor 将 .NET 带到现代前端圈

Blazor 是一个 Web UI 框架&#xff0c;可通过 WebAssembly 在任意浏览器中运行 .Net 。Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScrip…

[mybatis]sqlSessionFactory.openSession()

第一个是不会自动提交的 第二个带参数的是会自动提交的

State(状态)--对象行为型模式

State&#xff08;状态&#xff09;–对象行为型模式 一、意图 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 二、动机 1.在软件构建过程中&#xff0c;某些对象的状态如果改变&#xff0c;其行为也会随之而发生变化&#xff0c;比如文档处于只…