最近在忙于 Fireasy 的重构,3.x 抛弃了 .Net Framework 时代的一些思想和模式,紧密拥抱 .Net Core,但它的思想仍然是开放性和灵活性。今天我主要来说说依赖注入与服务发现。
.Net Core 有自己的一套依赖注入,它的容器暴露给 IServiceCollection,通过在里面放入一些单例(Singleton)、瞬时(Transient)、作用域(Scoped)的一些服务描述(服务与实现的关系映射),这一部分我就不再细说了。
当然,一般常用的方式是,通过 AddSingleton、AddTransient 和 AddScoped 方法往容器里面加,但如果是依赖比较多的情况下(比如业务服务类),那你可能会经常忘了写这一部分代码了,而且也很难于维护。如常见的方式:
void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDeptmentService, DeptmentService>();
services.AddTransient<IRoleService, RoleService>();
services.AddTransient<IUserService, UserService>();
services.AddTransient<IDataRoleService, DataRoleService>();
//.......
services.AddTransient<IProcessService, ProcessService>();
services.AddTransient<IWorkService, WorkService>();
}
有没有更简便更容易维护的方式呢?答案是当然有!
在 Fireasy,我们定义了三个服务接口,分别是 ISingletonService、ITransientService 和 IScopedService,这三个类只是一个标识,没有具体的方法和属性。使用需要注入的类实现此接口,如下:
public class DeptmentService : IDeptmentService, ITransientService
{
// ......
}
public class DataRoleHelper : IDataRoleHelper, ISingletonService
{
// ......
}
好了,你只需在 ConfigureServices 里添加上这么一行代码,就能实现依赖注入:
void ConfigureServices(IServiceCollection services)
{
services.AddFireasy();
}
现在开始步入正题了,来看看 AddFireasy 是如何工作的。
IServiceDiscoverer 是用于服务发现的接口,它的默认实现是 DefaultServiceDiscoverer。如下:
public static SetupBuilder AddFireasy(this IServiceCollection services, Action<SetupOptions>? configure = null)
{
var options = new SetupOptions();
configure?.Invoke(options);
var builder = new SetupBuilder(services, options);
var discoverer = options.DiscoverOptions.DiscovererFactory == null ? new DefaultServiceDiscoverer(services, options.DiscoverOptions)
: options.DiscoverOptions.DiscovererFactory(services, options.DiscoverOptions);
if (discoverer != null)
{
services.AddSingleton<IServiceDiscoverer>(discoverer);
}
return builder;
}
入口方法是 DiscoverServices,它会遍列程序目录下的所有程序集文件(*.dll),这里有程序集过滤器,你可以自己定义过滤规则。如下:
/// <summary>
/// 发现工作目录中所有程序集中的依赖类型。
/// </summary>
/// <param name="services"></param>
private void DiscoverServices(IServiceCollection services)
{
foreach (var assembly in GetAssemblies())
{
if (_options?.AssemblyFilters?.Any(s => s.IsFilter(assembly)) == true)
{
continue;
}
if (_options?.AssemblyFilterPredicates?.Any(s => s(assembly)) == true)
{
continue;
}
_assemblies.Add(assembly);
ConfigureServices(services, assembly);
DiscoverServices(services, assembly);
}
}
方法 DiscoverServices 用于对单个程序集进行服务发现并进行注册,这里同样也有类型过滤器,如下:
/// <summary>
/// 发现程序集中的所有依赖类型。
/// </summary>
/// <param name="services"></param>
/// <param name="assembly"></param>
private void DiscoverServices(IServiceCollection services, Assembly assembly)
{
foreach (var type in assembly.GetExportedTypes())
{
if (_options?.TypeFilters?.Any(s => s.IsFilter(assembly, type)) == true)
{
continue;
}
if (_options?.TypeFilterPredicates?.Any(s => s(assembly, type)) == true)
{
continue;
}
ServiceLifetime? lifetime;
var interfaceTypes = type.GetDirectImplementInterfaces().ToArray();
//如果使用标注
if (type.IsDefined(typeof(ServiceRegisterAttribute)))
{
lifetime = type.GetCustomAttribute<ServiceRegisterAttribute>()!.Lifetime;
}
else
{
lifetime = GetLifetimeFromType(type);
}
if (lifetime == null)
{
continue;
}
if (interfaceTypes.Length > 0)
{
interfaceTypes.ForEach(s => AddService(services, s, type, (ServiceLifetime)lifetime));
}
else
{
AddService(services, type, type, (ServiceLifetime)lifetime);
}
}
}
private ServiceLifetime? GetLifetimeFromType(Type type)
{
if (typeof(ISingletonService).IsAssignableFrom(type))
{
return ServiceLifetime.Singleton;
}
else if (typeof(ITransientService).IsAssignableFrom(type))
{
return ServiceLifetime.Transient;
}
else if (typeof(IScopedService).IsAssignableFrom(type))
{
return ServiceLifetime.Scoped;
}
return null;
}
private ServiceDescriptor AddService(IServiceCollection services, Type serviceType, Type implType, ServiceLifetime lifetime)
{
var descriptor = ServiceDescriptor.Describe(serviceType, implType, lifetime);
_descriptors.Add(descriptor);
services.Add(descriptor);
return descriptor;
}
从上面的代码中可看出,通过在程序集内部查找实现了 ISingletonService、ITransientService 或 IScopedService 的类,并将它们添加到 services 中,这样就完成了开篇提到的工作。
这里还出现了一个 ServiceRegisterAttribute,它在不实现以上三个接口的情况下,通过标注 Lifetime 生命周期来进行注册,一样达到了目的。
接下来做几个简单的单元测试。
单例测试:
/// <summary>
/// 测试单例服务
/// </summary>
[TestMethod]
public void TestSingletonService()
{
var services = new ServiceCollection();
var builder = services.AddFireasy();
var serviceProvider = services.BuildServiceProvider();
var service1 = serviceProvider.GetService<ITestSingletonService>();
var service2 = serviceProvider.GetService<ITestSingletonService>();
Assert.IsNotNull(service1);
Assert.IsNotNull(service2);
//两对象的id应相等
Assert.AreEqual(service1.Id, service2.Id);
}
public interface ITestSingletonService
{
Guid Id { get; }
void Test();
}
public class TestSingletonServiceImpl : ITestSingletonService, ISingletonService
{
public TestSingletonServiceImpl()
{
Id = Guid.NewGuid();
}
public Guid Id { get; }
public void Test() => Console.WriteLine("Hello TestSingletonService!");
}
瞬时测试:
/// <summary>
/// 测试瞬时服务
/// </summary>
[TestMethod]
public void TestTransientService()
{
var services = new ServiceCollection();
var builder = services.AddFireasy();
var serviceProvider = services.BuildServiceProvider();
var service1 = serviceProvider.GetService<ITestTransientService>();
var service2 = serviceProvider.GetService<ITestTransientService>();
Assert.IsNotNull(service1);
Assert.IsNotNull(service2);
//两对象的id应不相等
Assert.AreNotEqual(service1.Id, service2.Id);
}
public interface ITestTransientService
{
Guid Id { get; }
void Test();
}
public class TestTransientServiceImpl : ITestTransientService, ITransientService
{
public TestTransientServiceImpl()
{
Id = Guid.NewGuid();
}
public Guid Id { get; }
public void Test() => Console.WriteLine("Hello TestTransientService!");
}
作用域测试:
/// <summary>
/// 测试作用域服务
/// </summary>
[TestMethod]
public void TestScopedService()
{
var services = new ServiceCollection();
var builder = services.AddFireasy();
var serviceProvider = services.BuildServiceProvider();
Guid id1, id2;
//作用域1
using (var scope1 = serviceProvider.CreateScope())
{
var service1 = scope1.ServiceProvider.GetService<ITestScopedService>();
var service2 = scope1.ServiceProvider.GetService<ITestScopedService>();
Assert.IsNotNull(service1);
Assert.IsNotNull(service2);
//两对象的id应相等
Assert.AreEqual(service1.Id, service2.Id);
id1 = service1.Id;
}
//作用域2
using (var scope2 = serviceProvider.CreateScope())
{
var service1 = scope2.ServiceProvider.GetService<ITestScopedService>();
var service2 = scope2.ServiceProvider.GetService<ITestScopedService>();
Assert.IsNotNull(service1);
Assert.IsNotNull(service2);
//两对象的id应相等
Assert.AreEqual(service1.Id, service2.Id);
id2 = service1.Id;
}
//两次scoped的id应不相等
Assert.AreNotEqual(id1, id2);
}
public interface ITestScopedService
{
Guid Id { get; }
void Test();
}
public class TestScopedServiceImpl : ITestScopedService, IScopedService
{
public TestScopedServiceImpl()
{
Id = Guid.NewGuid();
}
public Guid Id { get; }
public void Test() => Console.WriteLine("Hello TestScopedService!");
}
可见,不需要显式 Add 也能将大量的服务类注入到容器中,不仅节省了大量的时间和代码,更是提高了程序的可维护性。
最后,奉上 Fireasy 3 的开源地址:https://gitee.com/faib920/fireasy3 ,欢迎大家前来捧场。
本文相关代码请参考:
https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common/DependencyInjection
https://gitee.com/faib920/fireasy3/tests/Fireasy.Common.Tests/DependencyInjectionTests.cs
更多内容请移步官网 http://www.fireasy.cn 。
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru
我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这
在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除
require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame
我今天看到了一个ruby代码片段。[1,2,3,4,5,6,7].inject(:+)=>28[1,2,3,4,5,6,7].inject(:*)=>5040这里的注入(inject)和之前看到的完全不一样,比如[1,2,3,4,5,6,7].inject{|sum,x|sum+x}请解释一下它是如何工作的? 最佳答案 没有魔法,符号(方法)只是可能的参数之一。这是来自文档:#enum.inject(initial,sym)=>obj#enum.inject(sym)=>obj#enum.inject(initial){|mem