SpringBoot的註解注入功能移植到.Net平台(開源)
- 2019 年 10 月 3 日
- 筆記
最近在公司用java和kotlin寫介面, 發現SpringBoot的註解來配置DI容器的功能非常的好用: 找了一下發現沒有一個net的框架實現了,所以我決定自己寫一個!
- 開源地址:https://github.com/yuzd/Autofac.Annotation
- 支援netcore2.0 + framework4.6+
- NUGET安裝: Install-Package Autofac.Annotation
這個是我基於autofac框架的一個擴展組件,實現了以下功能:
- Component標籤:註冊到DI容器直接打上一個即可
- Configuration註解和Bean標籤:實現了用實例方法註冊到DI容器
- PropertySource和Value標籤:實現了注入配置文件屬性的值到DI容器
- Autowired標籤:實現了自動裝配
玩過java的spring框架就應該看這個標籤名稱很熟悉,因為名稱是一模一樣的。 功能也是高度保持一致
var builder = new ContainerBuilder(); // 註冊autofac打標籤模式 builder.RegisterModule(new AutofacAnnotationModule(typeof(AnotationTest).Assembly)); //如果需要開啟支援循環注入 //builder.RegisterModule(new AutofacAnnotationModule(typeof(AnotationTest).Assembly).SetAllowCircularDependencies(true)); var container = builder.Build(); var serviceB = container.Resolve<B>();
AutofacAnnotationModule有兩種構造方法
- 可以傳一個Assebly列表 (這種方式會註冊傳入的Assebly裡面打了標籤的類)
- 可以傳一個AsseblyName列表 (這種方式是先會根據AsseblyName查找Assebly 然後在註冊)
Component標籤
說明:只能打在class上面(且不能是抽象class) 把某個類註冊到autofac容器 例如:
- 無構造方法的方式 等同於 builder.RegisterType();
//把class A 註冊到容器 [Component] public class A { public string Name { get; set; } } //如果 A有父類或者實現了介面 也會自動註冊(排除非public的因為autofac不能註冊私有類或介面) public interface IB { } public class ParentB:IB { public string Name1 { get; set; } } //把class B 註冊到容器 並且把 B作為ParentB註冊到容器 並且把B最為IB註冊到容器 [Component] public class B:ParentB { public string Name { get; set; } }
- 指定Scope [需要指定AutofacScope屬性 如果不指定為則默認為AutofacScope.InstancePerDependency]
[Component(AutofacScope = AutofacScope.SingleInstance)] public class A { public string Name { get; set; } }
- 指定類型註冊 等同於 builder.RegisterType<A6>().As()
public class B { } [Component(typeof(B))] public class A6:B { }
- 指定名字註冊 等同於 builder.RegisterType<A6>().Keyed<A4>(“a4”)
[Component("a4")] public class A4 { public string School { get; set; } = "測試2"; }
- 其他屬性說明
- OrderIndex 註冊順序 【順序值越大越早註冊到容器,但是一個類型多次註冊那麼裝配的時候會拿OrderIndex最小的值(因為autofac的規則會覆蓋)】
- InjectProperties 是否默認裝配屬性 【默認為true】
- InjectPropertyType 屬性自動裝配的類型
- Autowired 【默認值】代表打了Autowired標籤的才會自動裝配
- AutoActivate 【默認為false】 如果為true代表autofac build完成後會自動創建 具體請參考 autofac官方文檔
- Ownership 【默認為空】 具體請參考 autofac官方文檔
- Interceptor 【默認為空】指定攔截器的Type
- InterceptorType 攔截器類型 攔截器必須實現 Castle.DynamicProxy的 IInterceptor 介面, 有以下兩種
- Interface 【默認值】代表是介面型
- Class 代表是class類型 這種的話是需要將要攔截的方法標virtual
- InterceptorKey 如果同一個類型的攔截器有多個 可以指定Key
- InitMethod 當實例被創建後執行的方法名稱 類似Spring的init-method 可以是有參數(只能1個參數類型是IComponentContext)和無參數的方法
- DestroyMetnod 當實例被Release時執行的方法 類似Spring的destroy-method 必須是無參數的方法
[Component(InitMethod = "start",DestroyMetnod = "destroy")] public class A30 { [Value("aaaaa")] public string Test { get; set; } public A29 a29; void start(IComponentContext context) { this.Test = "bbbb"; a29 = context.Resolve<A29>(); } void destroy() { this.Test = null; a29.Test = null; } }
public class B { } [Component(typeof(B),"a5")] public class A5:B { public string School { get; set; } = "測試a5"; public override string GetSchool() { return this.School; } }
Autowired 自動裝配
可以打在Field Property 構造方法的Parameter上面 其中Field 和 Property 支援在父類
[Component] public class A16 { public A16([Autowired]A21 a21) { Name = name; A21 = a21; } [Autowired("A13")] public B b1; [Autowired] public B B { get; set; } //Required默認為true 如果裝載錯誤會拋異常出來。如果指定為false則不拋異常 [Autowired("adadada",Required = false)] public B b1; }
Value 和 PropertySource
- PropertySource類似Spring裡面的PropertySource 可以指定數據源 支援 xml json格式 支援內嵌資源
- json格式的文件
{ "a10": "aaaaaaaaa1", "list": [ 1, 2, 3 ], "dic": { "name": "name1" }, "testInitField": 1, "testInitProperty": 1, }
[Component] [PropertySource("/file/appsettings1.json")] public class A10 { public A10([Value("#{a10}")]string school,[Value("#{list}")]List<int> list,[Value("#{dic}")]Dictionary<string,string> dic) { this.School = school; this.list = list; this.dic = dic; } public string School { get; set; } public List<int> list { get; set; } public Dictionary<string,string> dic { get; set; } [Value("#{testInitField}")] public int test; [Value("#{testInitProperty}")] public int test2 { get; set; } //可以直接指定值 [Value("2")] public int test3 { get; set; } }
- xml格式的文件
<?xml version="1.0" encoding="utf-8" ?> <autofac> <a11>aaaaaaaaa1</a11> <list name="0">1</list> <list name="1">2</list> <list name="2">3</list> <dic name="name">name1</dic> </autofac>
[Component] [PropertySource("/file/appsettings1.xml")] public class A11 { public A11([Value("#{a11}")]string school,[Value("#{list}")]List<int> list,[Value("#{dic}")]Dictionary<string,string> dic) { this.School = school; this.list = list; this.dic = dic; } public string School { get; set; } public List<int> list { get; set; } public Dictionary<string,string> dic { get; set; } }
- 不指定PropertySource的話會默認從工程目錄的 appsettings.json獲取值
AutoConfiguration標籤 和 Bean標籤
[AutoConfiguration] public class TestConfiguration { //Bean標籤只能搭配AutoConfiguration標籤使用,在其他地方沒有效 //並且是單例註冊 [Bean] private ITestModel4 getTest5() { return new TestModel4 { Name = "getTest5" }; } }
在容器build完成後執行: 掃描指定的程式集,發現如果有打了AutoConfiguration標籤的class,就會去識別有Bean標籤的方法,並執行方法將方法返回實例註冊為方法返回類型到容器! 一個程式集可以有多個AutoConfiguration標籤的class會每個都載入。
AutoConfiguration標籤的其他屬性:
- OrderIndex 可以通過OrderIndex設置優先順序,越大的越先載入。
- Key 也可以通過Key屬性設置
搭配如下程式碼可以設置過濾你想要載入的,比如你想要載入Key = “test” 的所有 AutoConfiguration標籤class //builder.RegisterModule(new AutofacAnnotationModule(typeof(AnotationTest).Assembly).SetAutofacConfigurationKey(“test”));
Bean標籤的其他屬性:
- Key 也可以通過Key屬性設置 比如有多個方法返回的類型相同 可以設置Key來區分
AutofacAnnotation標籤模式和autofac寫程式碼性能測試對比
public class AutofacAutowiredResolveBenchmark { private IContainer _container; [GlobalSetup] public void Setup() { var builder = new ContainerBuilder(); builder.RegisterType<A13>().As<B>().WithAttributeFiltering(); builder.RegisterType<Log>().As<AsyncInterceptor>(); builder.RegisterType<Log2>().Keyed<AsyncInterceptor>("log2"); builder.RegisterType<A21>().WithAttributeFiltering().PropertiesAutowired(); builder.RegisterType<A23>().As<IA23>().WithAttributeFiltering().PropertiesAutowired().EnableInterfaceInterceptors() .InterceptedBy(typeof(AsyncInterceptor)); builder.RegisterType<A25>().WithAttributeFiltering().PropertiesAutowired().EnableClassInterceptors() .InterceptedBy(new KeyedService("log2", typeof(AsyncInterceptor))); _container = builder.Build(); } [Benchmark] public void Autofac() { var a1 = _container.Resolve<A25>(); var a2= a1.A23.GetSchool(); } }
BenchmarkDotNet=v0.11.3, OS=Windows 10.0.18362 Intel Core i7-7700K CPU 4.20GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=2.2.300 [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT [AttachedDebugger] DefaultJob : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT
| Method | Mean | Error | StdDev |
|---|---|---|---|
| Autofac | 30.30 us | 0.2253 us | 0.1997 us |
//打標籤模式 public class AutowiredResolveBenchmark { private IContainer _container; [GlobalSetup] public void Setup() { var builder = new ContainerBuilder(); builder.RegisterModule(new AutofacAnnotationModule(typeof(A13).Assembly)); _container = builder.Build(); } [Benchmark] public void AutofacAnnotation() { var a1 = _container.Resolve<A25>(); var a2= a1.A23.GetSchool(); } }
BenchmarkDotNet=v0.11.3, OS=Windows 10.0.18362 Intel Core i7-7700K CPU 4.20GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=2.2.300 [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT [AttachedDebugger] DefaultJob : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT
| Method | Mean | Error | StdDev |
|---|---|---|---|
| AutofacAnnotation | 35.36 us | 0.1504 us | 0.1407 us |
