Autofac.Annotation 地址:https://github.com/yuzd/Autofac.Annotation
该类库可以实现特性注入、拦截器、切面以及配置文件读取。
具体使用方法,可以参考官方文档:https://github.com/yuzd/Autofac.Annotation/wiki。
FlexibleCore 对源代码做了修改,读取配置文件部分,支持敏感数据(如数据库密码)的 RSA 加密字符串读取。
打了 AutoConfiguration 标签的 Class 就是配置类。
在 AutoConfiguration 标签的 Class 里面打了 Bean 标签的返回对象就是要注册到 autofac 容器的类。
[AutoConfiguration] public class MyConfig { [Bean] public MyModel GetMyModel() { return new MyModel { Name = "name" }; } } public class MyModel { public string Name { get; set; } }
框架会扫描打了【AutoConfiguration】的 class,然后会再扫描打了【Bean】标签的方法并且 Invoke 该方法,拿到方法返回的对象并默认是瞬时注册到 DI 容器中。
瞬时的意思是:每次从容器中都是拿到一个新的对象。
对比单例的话:只有第一次从容器中拿会创建一个对象,后面再从容器中拿依然是这个,可以按照如下方式配置是否单例还是瞬时。
[Bean(AutofacScope = AutofacScope.SingleInstance)] public MyModel InitMyModel() { return new MyModel(); }
如果你希望本框架打了[Bean]和[Component]全部默认都是单例模式注册的话 可以全局配置:
var builder = new ContainerBuilder(); // autofac 打标签模式 builder.RegisterModule(new AutofacAnnotationModule(typeof(MyModel).Assembly).SetDefaultAutofacScopeToSingleInstance());
顺序是: 优先使用【Component】和【Bean】特性的 AutofacScope 属性值,没有配置则用上面设置的全局 Scope。
/// <summary> /// 注册单个的 key /// </summary> public string Key { get; set; } /// <summary> /// 值越大越优先处理 /// </summary> public int OrderIndex { get; set; } /// <summary> /// 被创建后执行的方法 /// </summary> public string InitMethod { get; set; } /// <summary> /// 被 Release 时执行的方法 /// </summary> public string DestroyMethod { get; set; }
/// <summary> /// 注册单个的key /// </summary> public string Key { get; private set; } /// <summary> /// 作用域 bean的作用域默认是单例的 /// </summary> public AutofacScope AutofacScope { get; set; } = AutofacScope.Default; /// <summary> /// 被创建后执行的方法 /// </summary> public string InitMethod { get; set; } /// <summary> /// 被Release时执行的方法 /// </summary> public string DestroyMethod { get; set; } /// <summary> /// 依赖的 是用来表示一个bean A的实例化依赖另一个bean B的实例化, 但是A并不需要持有一个B的对象 /// </summary> public DependsOn DependsOn { get; set; }
//方法的参数支持注入 DI 已存在的,或者 Value 标签 [Bean] public MyModel GetMyModel(OtherClass other,[Value("${connetionString}")] connetionString) { return new MyModel { Name = "name" }; }
打了 AutoConfiguration 的 class 也会被单例的形式注册到容器中。
使用 Key 指定可以实现不同的环境配置不同的对象到 DI 容器。
一些复杂的对象实例可以使用 Bean 的方式注册的 DI 容器。
框架会扫描打了 Componet 标签的 class。如果 Componet 标签里面指定了要注册的类型,则会只注册为这个类型到 DI 容器。如果没有指定则会把当前 class (参考下面的 4),以及父类,以及接口都会注册到DI容器(参考下面的 1、2 和 3)。
如何修改这个默认配置呢,比如关闭自动注册父类和接口。 可以参考:https://github.com/yuzd/Autofac.Annotation/issues/11
属性名称 | 类型 | 含义 |
---|---|---|
Service | Type | 注册指定单个的类型 |
Key | String | 注册指定单个的 key(为了同个类型注册多次避免歧义) |
Services | Type[] | 注册指定多个的类型 |
Keys | String[] | 注册指定多个的 key(如果指定多个类型又想避免歧义可以搭配上面一起使用) |
AutofacScope | Enum | InstancePerDependency(默认,每次都是一个新实例,也可以全局配置);SingleInstance(单例);InstancePerLifetimeScope(每个scope获取新的实例);InstancePerRequest(根据每个请求一个实例) |
AutoActivate | bool | 默认 false,当 DI 容器初始化完成后会自动完成实例化 |
InitMethod | string | 当实例化后自动执行的方法名称 |
DestroyMethod | string | 当实例会DI容器回收会自动执行的方法名称 |
Ownership | Enum | LifetimeScope(DI 容器管理自动回收策略,默认);External(自己手动管理实例回收),具体2者可参考 autofac 官方文档 |
Interceptor | Type | 指定拦截器的 class 的 Type 类型 |
InterceptorType | Enum | Interface(使用接口模式);Class(使用 class 的虚方法模式) |
InterceptorKey | string | 如果同一个类型的拦截器有多个,避免歧义可以指定 Key |
InjectPropertyType | Enum | 属性自动装配的类型,Autowired(默认,代表打了 Autowired 标签的才会装配);All(代表全部自动装配,采用 autofac 的属性注入方式) |
1、默认的构造方法
//默认的构造方法会把当前class,以及父类,以及接口都会注册到DI容器 //这里只会注册A8到DI容器 [Component] public class A8 { } //通过A8类型可以装配成功 [Autowired] public A8 A8 { get; set; } ························································· public class B { } //这里会把A8 和 B 都注册到DI容器 [Component] public class A8:B { } //通过A8类型可以装配成功 [Autowired] public A8 A8 { get; set; } //通过B类型也可以装配成功 拿到的是A8类型 [Autowired] public B B { get; set; } ·························································· public interface IA { } public interface IB:IA { } public class B:IB { } //这里会把A8 B IB IA 全都注册到DI容器 //如果你只想要注册 A8 不要注册父类,可以参考 https://github.com/yuzd/Autofac.Annotation/issues/11 去配置 [Component] public class A8:B { } //通过A8类型可以装配成功 [Autowired] public A8 A8 { get; set; } //通过B类型也可以装配成功 拿到的是A8类型 [Autowired] public B B { get; set; } //通过IB类型也可以装配成功 拿到的是A8类型 [Autowired] public IB IB { get; set; } //通过IA类型也可以装配成功 拿到的是A8类型 [Autowired] public IA IA { get; set; }
2、注册为指定类型
public class B { } //这个构造方法就是将 A6 注册为 B 类型 [Component(typeof(B))] public class A6:B { } //可以通过下面的方式自动装配 因为上面注册的是 B 类型,通过 B 类型可以装配成功,拿到的是 A6 类型 [Autowired] public B B { get; set; } //通过下面的方式自动装配会失败 会失败 会失败 [Autowired] public A6 A6 { get; set; } //也可以打在方法上面 [Autowired] public void SetA6(A6 a6){ A6 = a6; } [Autowired] public void SetA6(A6 a6,, [Value("${a9}")] string school){ A6 = a6; Console.WriteLine(school) }
3、同一个注册类型有多个,采用 Key 方式解决歧义
public interface ITestAutowiredModal { } // ITestAutowiredModal这个类型被注册多个了 避免歧义用"abc"来解决 [Component("abc")] public class TestAutowiredModal1: ITestAutowiredModal { } // ITestAutowiredModal这个类型被注册多个了 避免歧义用"def"来解决 [Component("def")] public class TestAutowiredModal2:ITestAutowiredModal { } //可以用下面的方式来自动装配 拿到的是 TestAutowiredModal1 类型对象 [Autowired("abc")] public ITestAutowiredModal TestAutowiredModal1 { get; set; } //如果不指定的话会先尝试根据byType模式匹配,因为是指定了Key 所以根据byType拿不到,拿不到就会再次根据属性名称 “abc” 去匹配,就和上面的方式一样了 [Autowired] public ITestAutowiredModal abc { get; set; } //可以用下面的方式来自动装配 拿到的是TestAutowiredModal2类型对象 [Autowired("def")] public ITestAutowiredModal TestAutowiredModal2 { get; set; } //也可以打在方法上面 [Autowired] public void SetTestAutowiredModal2 ([Autowired("def")]TestAutowiredModal2 model2){ Console.WriteLine(model2); } // 如果不指定key的话默认就是classname来作为默认的key [Component] // 等同于 [Component("TestAutowiredModal1")] public class TestAutowiredModal1: ITestAutowiredModal { } //可以用下面的方式来自动装配 拿到的是 TestAutowiredModal1 类型对象 [Autowired("TestAutowiredModal1")] public ITestAutowiredModal TestAutowiredModal1{ get; set; }
// 把 Student 类型注册到容器 [Component] public class Student { }
public class Person { } // 把 Student 类型注册到容器 // 并且把 Person 类型也注册到容器,根据 Person 类型拿到的是 Student 的实体 [Component] public class Student: Person { }
public interface ISay { void SayHello(); } // 把 Student 类型注册到容器 // 并且把 ISay 也注册到容器,根据 ISay 类型拿到的是 Student 的实体 [Component] public class Student: ISay { public void SayHello() { Console.WriteLine("hello"); } }
public interface ISay { void SayHello(); } // 只能通过 ISay 拿到 Student 的实体,不能通过 Student 类型拿到 [Component(typeof(ISay))] public class Student: ISay { public void SayHello() { Console.WriteLine("hello"); } }
还可以设置 Component 的 AutofacScope 属性,若有指定默认全局的 Scope 类型,那么在 Compoment 标签没有指定 Scope 那就会用全局的。
如果不做任何指定就用全局默认的 Scope:是每次获取的一个新的实例。
public interface ISay { void SayHello(); } [Component(typeof(ISay), "Student1")] public class Student1: ISay { public void SayHello() { Console.WriteLine("hello"); } } [Component(typeof(ISay), "Student2")] public class Student2: ISay { public void SayHello() { Console.WriteLine("hello"); } }
ISay 类型有 2 个实现类 Student1 和 Student2,分别指定了 Key 的值。
通过 ISay + “Student1” 可以获取到 Student1 的实体。
通过 ISay + “Student2” 可以获取到 Student2 的实体。
[Component(AutofacScope = AutofacScope.SingleInstance, AutoActivate = true)] public class Student { public string Name { get; set; } public void Student() { this.Name = "name" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); Console.WriteLine($"{nameof(Student)}.constructor"); } }
设定 AutoActivate = true 代表是启动自动实例化。
AutofacScope = AutofacScope.SingleInstance 代表单例模式。
Student 类型会对象会自动实例化,并且以单例的方式存储在 DI 容器内。
[Component(InitMethod = nameof(Student.Start), DestroyMethod = nameof(Student.Stop))] public class Student { public void Start() { Console.WriteLine($"{nameof(Student.Start)}.invoked!"); } public void Stop() { Console.WriteLine($"{nameof(Student.Stop)}.invoked!"); } }
设置 InitMethod 和 DestroyMethod。
当实例从 DI 容器初始化时就会调用 InitMethod。
当 DI 容器 Dispose 的时候会触发调用 DestroyMethod。
另外:
[Component(InitMethod = nameof(Student.Start), DestroyMethod = nameof(Student.Stop))] public class Student { public void Start([Value($"{a}") string a, [Autowired] Student1 student1]) { Console.WriteLine($"{nameof(Student.Start)}.invoked! {a} {student1.Name}"); } public void Stop() { Console.WriteLine($"{nameof(Student.Stop)}.invoked!"); } }
InitMethod 支持注入。
DestroyMethod 只能是无参数方法。
Import 是为了扩展注册 Component 到容器中去。
打在实现了 ImportSelector 接口的 class 上面:
public class TestImport1 { public string Name { get; set; } = nameof(TestImport1); } [Import] //注意得实现ImportSelector接口 public class TestImportSelector:ImportSelector { //实现这个接口 返回你要注册到容器的类型 BeanDefination的定义请查看下方文档说明 public BeanDefination[] SelectImports() { return new[] { // 这个 TestImport1 的 class 上面没有打[Component]标签,是借助 Import 来实现注册的 new BeanDefination(typeof(TestImport1)) }; } }
打在普通 class(非实现了 ImportSelector 接口的 class)上面,但是在指定了实现了 ImportSelector 的 Class 的类型:
[Import(typeof(TestImportSelector))] // 如果有多个要指定的话 可以 [Import(typeof(TestImportSelector),typeof(xxxxx),typeof(yyyy))] public class TestImport2 { } public class TestImportSelector:ImportSelector { public BeanDefination[] SelectImports() { return new[] { new BeanDefination(typeof(TestImport1)) }; } } public class TestImport1 { public string Name { get; set; } = nameof(TestImport1); }
BeanDefination
构造方法 | 参数 | 说明 |
---|---|---|
ctor | 无参构造 | 自定义 Type 和 Component 属性 |
ctor | Type _type | 注册 _type 到 DI 容器 |
ctor | Type _type, Type asType | 注册 _type 类型以 asType 到 DI 容器 |
ctor | string key | 注册 _type,如果 _type 会有多个注册的话用 key 作为避免歧义 |
Autowired 属性:
注意事项:想要 Autowire 起作用必须得是从 DI 容器里面获取对象,自己手工 new 的无法应用 Autowired 特性!
注意关于循环注入:默认 Autowired 方式注入到 Field、Property 是支持循环注入的,但是构造方法注入是无法支持循环注入的!
属性名称 | 类型 | 含义 |
---|---|---|
Name | String | 搭配 Component 指定 Key 的时候使用,消除一个类型被多个注册带来的歧义(支持 SPEL 表达式动态配置) |
Required | bool | 默认装载失败会报错 设置为false装载失败不会报错 |
[Component] public class Student { private Student1 _student1; [Autowired] private Student2 _student2; [Autowired] private Student3 _student3 { get; set; } public void Student([Autowired] Student1 student1) { _student1 = student1; } }
使用 ObjectFactory 来实现,和 Lazy 的区别是 ObjectFactory 修饰的每次获取都是从容器里面获取一遍。而 Lazy 只有首次才会去容器获取。
[Component] public class A38 { [Autowired("A3612")] public A36 A36 { get; set; } [Autowired("A3611")] public A36 A3611 { get; set; } [Autowired] public A37 A37 { get; set; } public DateTime Now = DateTime.Now; } [Component(AutofacScope = AutofacScope.SingleInstance)] public class A39 { public DateTime Now = DateTime.Now; [Autowired] public ObjectFactory <A38> A38 { get; set; } }
[Component] public class TestLazyModel { [Autowired] public Lazy<TestAutowiredModal1> TestAutowiredModal1 { get; set; } [Autowired] public TestAutowiredModal2 TestAutowiredModal2 { get; set; } }
private IDemoInstancePerDependency DemoInstancePerDependency => GetServiceProvider.GetService<IDemoInstancePerDependency>();
在静态方法中是没办法用 Autowired 获取实例的,框架提供了另外一种方式
//只能获取瞬时和单例模式 IHttpContextAccessor httpContextAccessor = GetServiceProvider.GetService<IHttpContextAccessor>(); //只能获取作用域模式 IHttpContextAccessor httpContextAccessor = GetServiceProvider.GetScopeService<IHttpContextAccessor>();
示例:
IHttpContextAccessor httpContextAccessor = GetServiceProvider.Instance.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
属性 | 说明 |
---|---|
Path | 文件路径 |
OrderIndex | 文件源排序,越大越先作为查找对象 |
Embedded | 是否为内嵌资源 |
ReloadOnChange | 是否监听文件有修改自动重新加载,默认 true(注意:非内嵌资源才行) |
Dynamic | 动态数据源类型,必须继承接口:IDynamicSourceProvider <为了扩展自定义数据源用的> |
Key | 当上面的 Dynamic 动态数据源类型有多个注册的时候根据 key 来获取唯一<为了扩展自定义数据源用的> |
[PropertySource("appsettings.json")] // 这行也可以直接去掉 因为和默认的一样 [Component] public class Student { [Value("${a9}")] public string Name { get; set; } } [PropertySource("/file/appsettings.json")] [AutoConfiguration] public class StudentConfiguration { [Value("${a9}")] public string Name { get; set; } }
PropertySource 的 path 可以指定为 json 和 xml 文件。
PropertySource 如果不指定 path,默认 path 为根目录的 appsettions.json。
如果 Path 的值为 / 开头,则代表为相对于工程目录。
如果设定 Embedded = true 则代表 path 的值为内嵌资源的文件名称。
Path 的值也可以为文件的完整路径。
属性 | 说明 |
---|---|
Value | 对应的 key |
IsRsa | 是否 RSA 加密,默认 false,如果 true,那么配置的 value 的值是经过 RSA 加密后的值 |
UseSpel | 是否启用 SPEL,默认 true,如果 false,那么根据配置的 value 的值直接从 PropertySource 里面读取 |
IgnoreUnresolvablePlaceholders | 如果从数据源取不到要不要报错,默认取不到报错 |
EnvironmentVariableMode | 设置是否从环境变量拿 默认是从 PropertySource 里面读取不到 就从环境里面去拿 |
${xxx} 代表从配置源里面 获取属性名称为 xxx 的值。
#{xxx} 代表启动 SPEL 表达式,xxx 里面可以嵌套 ${yyy}。
SPEL 表达式具体用法可以查看:https://github.com/yuzd/Spring.EL。
[PropertySource("appsettings.json")] // 这行也可以直接去掉 因为和默认的一样 [Component] public class Student { [Value("${a9}")] public string Name { get; set; } [Value("#{@(Autofac.Configuration.Test.Component_Autowired.Student8,Autofac.Configuration.Test).Student5.Name}")] public string Name2 { get; set; } [Value("#{${dic}}")] public Dictionary<string, string> Dic; [Value("${rsaString}", IsRsa = true)] public string RsaString { get; set; } }
举例:
appsettings.json 文件内容如下:
{ "a9": "aaaaaaaaa", "list": "1, 2, 3, 4 ", "dic": "#{'name': 'name1','school': 'school1'}", "parent": { "name": "yuzd" }, "list2": [ "1", "2" ], "ValueInjectModel1": { "Name": "yuzd", "Age": 20 }, "AppSettings": { "ConnectionString": "gkT4rqOINgxhYxwYIjqxHyMtcQA1BsF5FDEiuACITSHwbY4PBE7P8Wbsoly2h5Bcxyw0aCeKUlNbw36CAO2ZCvDQAwZxrqMExLZGhPh4uCNC2A/uqLYyzeww9+a5dyrGe+MQ6wNaGZ+E8HYTN2pdDCinERJIyiqQH6x4Em37sh343FiK8wFf/JXSa5o+RZLub/Uz3ZJMJ2D1nWPLiM3LP6KJpKviByV6g3SCel4uSGJUJXrFJ0iIjY2PZ05xoKx437slemmAXW8dWNXX2xU6nQovxT2ZF86exVGAq8Qy07/iyMWJiAKjBlAoXzuFWKIocT2wYVxPgn50SrfK77Lfjg==" }, }
[Value("hello")] private string normal; // 注入普通字符串 normal = "hello" [Value("${a9}")] private string a9; // 注入 a9 = "aaaaaaaaa" Value("${list}")] private List<int> list; // 注入 list 是 int 的集合 [1,2,3,4] [Value("#{${dic}}")] private Dictionary<string, string> dic; // 注入 dic 是字典 {'name': 'name1','school': 'school1'} [Value("list2", UseSpel = false)] //注意这里 UseSpel 设置为 false,注入 list2 是 string 集合 ['1','2'] public List<string> list2 { get; set; } [Value("ValueInjectModel1", UseSpel = false)] // 注意这里 UseSpel 设置为 false,注入 ValueInjectModel1 对象 {'Name': 'yuzd','Age': 20} public ValueInjectModel1 ValueInjectModel1 { get; set; } [Value("${AppSettings:ConnectionString}", IsRsa = true)] public string ConnectionString { get; set; } // ConnectionString 的值经过 RAS 加密
[Component] public class ValueModel6 { [Value("${parent:name}")] public IValue<string> ParentName { get; set; } [Value("${a9}")] private IValue<string> Test; public string GetTest() { return Test.Value; } }
containerBuilder.RegisterModule(new AutofacAnnotationModule() .SetDefaultValueResource(c.Configuration) // c.Configuration 是当前应用的 Configuration,这样设置后,如果没有特别指定 PropertySource 的话 value 读取的数据源默认从这里获取了 );
自己 new 一个对象不能实现拦截器功能,必须得从 DI 容器拿到的对象才能具备拦截器功能。
在需要实现拦截的目标 class 里打上拦截器标签 [Component(EnableAspect = true)],那么就会被框架认为这个 class 需要被代理包装,还可以根据 InterceptorType 属性值设定你是哪种方式的拦截器。
InterceptorType 属性 | 说明 |
---|---|
Class | 使用 class 的虚方法模式,默认方式 |
Interface | 使用接口模式 |
通过打标签就能够拦截目标方法,每个拦截器方法都有一个。
使得我们自定义的方法能够:
1、在指定的目标方法执行之前先执行(比如参数校验);
2、或者在指定的目标方法执行之后执行(比如说检验返回值,或其他收尾工作);
3、或者环绕目标的方法,比如日志 or 事务:TransactionScope 或者记录方法执行的时间或者日志。
拦截器标签 | 拦截器类型 | 使用说明 |
---|---|---|
AspectArround(抽象标签类) | 环绕拦截 | 重写 OnInvocation 方法 |
AspectBefore(抽象标签类) | 前置拦截器 | 重写 Before 方法 |
AspectAfter(抽象标签类) | 后置拦截器(不管目标方法成功失败都会执行) | 重写 After 方法 |
AspectAfterReturn(抽象标签类) | 后置拦截器(只有目标方法成功才会执行) | 重写 AfterReturn 方法 |
AspectAfterThrows(抽象标签类) | 错误拦截器(只有目标方法失败才会执行) | 重写 AfterThrows 方法 |
名称 | 说明 |
---|---|
ComponentContext | DI 容器,可以从中取得你已注册的实例 |
Arguments | 目标方法的参数 |
TargetMethod | 目标方法的 MethodInfo |
ReturnValue | 目标方法的返回 |
Method | 目标方法的代理方法 MethodInfo |
1、首先要自己写一个类继承前置拦截器 AspectBefore(抽象标签类);
2、实现该抽象类的 Before 方法。
public class TestHelloBefore: AspectBefore { public override Task Before(AspectContext aspectContext) { Console.WriteLine("TestHelloBefore"); return Task.CompletedTask; } } // Component 特性里面有一个属性叫做 InterceptorType,默认为 Class + virtual 来实现代理包装 [Component] public class TestHello { // 打上拦截器 [TestHelloBefore] public virtual void Say() { Console.WriteLine("Say"); } }
前置拦截器方法的执行顺序为:先执行 TestHelloBefor 的 Before 方法,再执行 Say 方法。
1、首先要自己写一个类继承后置拦截器 AspectAfter(抽象标签类);
2、实现该抽象类的 After 方法。
public class TestHelloAfter: AspectAfter { //这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值 // 如果目标方法抛异常的话 那就是异常本身 public override Task After(AspectContext aspectContext,object returnValue) { Console.WriteLine("TestHelloAfter"); return Task.CompletedTask; } } // Component 特性里面有一个属性叫做 InterceptorType,默认为 Class + virtual 来实现代理包装 [Component] public class TestHello { // 打上拦截器 [TestHelloAfter] public virtual void Say() { Console.WriteLine("Say"); } }
执行顺序为:先执行 SayAfter 方法,再执行 TestHelloAfter 的 After 方法。
这里要特别注意的是:After 拦截器,不管目标方法 SayAfter 是成功还是抛异常,都会执行。
1、首先要自己写一个类继承拦截器 AspectReturn(抽象标签类);
2、实现该抽象类的 After 方法。
public class TestHelloAfterReturn: AspectAfterReturn { //result 是目标方法的返回 (如果目标方法是void 则为null) public override Task AfterReturn(AspectContext aspectContext, object result) { Console.WriteLine("TestHelloAfterReturn"); return Task.CompletedTask; } } // Component 特性里面有一个属性叫做 InterceptorType,默认为 Class + virtual 来实现代理包装 [Component] public class TestHello { // 打上拦截器 [TestHelloAfterReturn] public virtual void Say() { Console.WriteLine("Say"); } }
执行顺序为:先执行 Say方法,再执行 TestHelloAfterReturn 的 AfterReturn 方法。
如果 Say 方法抛出异常,那么就不会执行 TestHelloAfterReturn 的 AfterReturn 方法。
1、首先要自己写一个类继承拦截器AspectReturn(抽象标签类);
2、实现该抽象类的 After 方法。
public class TestHelloAfterThrows: AspectAfterThrows { public override Task AfterThrows(AspectContext aspectContext, Exception exception) { Console.WriteLine(exception.Message); return Task.CompletedTask; } } // Component 特性里面有一个属性叫做 InterceptorType,默认为 Class + virtual 来实现代理包装 [Component] public class TestHello { // 打上拦截器 [TestHelloAfterThrows] public virtual void Say() { Console.WriteLine("Say"); throw new ArgumentException("exception"); } }
执行顺序为:先执行 Say 方法,再执行 TestHelloAfterThrows 的 AfterThrows 方法。
如果 Say 方法不抛出异常,那么就不会执行 TestHelloAfterThrows 的 AfterThrows 方法。
注意:OnInvocation 方法除了 AspectContext 参数以外,还有一个 AspectDelegate _next 参数, 需要在 Around 拦截器方法显示调用 _next(aspectContext) 方法,否则目标方法不会被调用。
1、首先要自己写一个类继承拦截器AspectArround(抽象标签类);
2、实现该抽象类的 OnInvocation 方法。
public class TestHelloAround: AspectArround { public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next) { Console.WriteLine("around start"); await _next(aspectContext); Console.WriteLine("around end"); } } // Component 特性里面有一个属性叫做 InterceptorType,默认为 Class + virtual 来实现代理包装 [Component] public class TestHello { // 打上拦截器 [TestHelloAround] public virtual void Say() { Console.WriteLine("Say"); } }
方法的执行顺序为:
1、先执行 TestHelloAround 的 OnInvocation 方法;
2、然后 TestHelloAround 的 OnInvocation 方法里面执行的 await _next(aspectContext); 就会执行被拦截方法 TestHello 的 Say 方法。
正常 case:
// Component 特性里面有一个属性叫做 InterceptorType,默认为 Class + virtual 来实现代理包装 [Component] public class TestHello { // 打上拦截器 [TestHelloAround,TestHelloBefore,TestHelloAfter,TestHelloAfterReturn,TestHelloAfterThrows] public virtual void Say() { Console.WriteLine("Say"); } }
代码的执行顺序为:
1、先执行 TestHelloAround,打印“around start”然后执行到里面的 _next(aspectContext) 会触发下面;
2、执行 TestHelloBefore,打印“TestHelloBefore”;
3、执行目标方法,打印“Say”;
4、打印“around end”,TestHelloAround 运行结束;
5、执行 TestHelloAfter,打印“TestHelloAfter”;
6、因为目标方法成功执行,TestHelloAfterReturn 打印“TestHelloAfterReturn”。
由于目标方法成功返回没有异常,所以不会走进 TestHelloAfterThrows。
异常 case:
// Component 特性里面有一个属性叫做 InterceptorType,默认为 Class + virtual 来实现代理包装 [Component] public class TestHello { // 打上拦截器 [TestHelloAround,TestHelloBefore,TestHelloAfter,TestHelloAfterReturn,TestHelloAfterThrows] public virtual void Say() { Console.WriteLine("Say"); throw new ArgumentException("exception"); } }
代码的执行顺序为:
1、先执行 TestHelloAround,打印“around start”然后执行到里面的 _next(aspectContext) 会触发下面;
2、执行 TestHelloBefore,打印“TestHelloBefore”;
3、执行目标方法,打印“Say”;
4、打印“around end”,TestHelloAround 运行结束;
5、执行 TestHelloAfter,打印“TestHelloAfter”;
6、因为目标方法异常,执行 TestHelloAfterThrows 打印异常信息。
public class TestHelloBefore1: AspectBefore { public override Task Before(AspectContext aspectContext) { Console.WriteLine("TestHelloBefore1"); return Task.CompletedTask; } } public class TestHelloAfter1: AspectAfter { // 这个 returnValue 如果目标方法正常返回的话 那就是目标方法的返回值 // 如果目标方法抛异常的话 那就是异常本身 public override Task After(AspectContext aspectContext,object returnValue) { Console.WriteLine("TestHelloAfter1"); return Task.CompletedTask; } } public class TestHelloAfterReturn1: AspectAfterReturn { // result 是目标方法的返回(如果目标方法是 void 则为 null) public override Task AfterReturn(AspectContext aspectContext, object result) { Console.WriteLine("TestHelloAfterReturn1"); return Task.CompletedTask; } } public class TestHelloAround1: AspectArround { public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next) { Console.WriteLine("TestHelloAround1 start"); await _next(aspectContext); Console.WriteLine("TestHelloAround1 end"); } } public class TestHelloAfterThrows1: AspectAfterThrows { public override Task AfterThrows(AspectContext aspectContext, Exception exception) { Console.WriteLine("TestHelloAfterThrows1"); return Task.CompletedTask; } } public class TestHelloBefore2: AspectBefore { public override Task Before(AspectContext aspectContext) { Console.WriteLine("TestHelloBefore2"); return Task.CompletedTask; } } public class TestHelloAfter2: AspectAfter { // 这个 returnValue 如果目标方法正常返回的话,那就是目标方法的返回值 // 如果目标方法抛异常的话,那就是异常本身 public override Task After(AspectContext aspectContext,object returnValue) { Console.WriteLine("TestHelloAfter2"); return Task.CompletedTask; } } public class TestHelloAfterReturn2: AspectAfterReturn { // result 是目标方法的返回(如果目标方法是 void 则为 null) public override Task AfterReturn(AspectContext aspectContext, object result) { Console.WriteLine("TestHelloAfterReturn2"); return Task.CompletedTask; } } public class TestHelloAround2: AspectArround { public override async Task OnInvocation(AspectContext aspectContext, AspectDelegate _next) { Console.WriteLine("TestHelloAround2 start"); await _next(aspectContext); Console.WriteLine("TestHelloAround2 end"); } } public class TestHelloAfterThrows2: AspectAfterThrows { public override Task AfterThrows(AspectContext aspectContext, Exception exception) { Console.WriteLine("TestHelloAfterThrows2"); return Task.CompletedTask; } } [Component] public class TestHello { [ TestHelloAround1(GroupName = "Aspect1",OrderIndex = 10), TestHelloBefore1(GroupName = "Aspect1",OrderIndex = 10), TestHelloAfter1(GroupName = "Aspect1",OrderIndex = 10), TestHelloAfterReturn1(GroupName = "Aspect1",OrderIndex = 10), TestHelloAfterThrows1(GroupName = "Aspect1",OrderIndex = 10) ] [ TestHelloAround2(GroupName = "Aspect2",OrderIndex = 1), TestHelloBefore2(GroupName = "Aspect2",OrderIndex = 1), TestHelloAfter2(GroupName = "Aspect2",OrderIndex = 1), TestHelloAfterReturn2(GroupName = "Aspect2",OrderIndex = 1), TestHelloAfterThrows2(GroupName = "Aspect2",OrderIndex = 1) ] public virtual void SayGroup() { Console.WriteLine("SayGroup"); } }
如上面的代码在目标方法上打了 2 组,那么对应的执行顺序是:
1、先执行 TestHelloAround2,打印“TestHelloAround2 start”,然后执行到里面的 _next(aspectContext) 会触发下面;
2、执行 TestHelloBefore2,打印“TestHelloBefore2”,然后进入到...
3、执行 TestHelloAround1,打印“TestHelloAround1 start”。然后执行到里面的 _next(aspectContext) 会触发下面;
4、执行 TestHelloBefore1,打印“TestHelloBefore1”;
5、执行目标方法 SayGroup,打印 "SayGroup";
6、TestHelloAround1 运行结束,打印“TestHelloAround1 end”;
7、执行 TestHelloAfter1,打印“TestHelloAfter1”;
8、执行 TestHelloAfterReturn1,打印“TestHelloAfterReturn1”;
9、TestHelloAround2 运行结束,打印“TestHelloAround2 end”;
10、执行 TestHelloAfter2,打印“TestHelloAfter2”;
11、执行 TestHelloAfterReturn2,打印“TestHelloAfterReturn2”。
public class TestHelloBefore:AspectBefore { public override Task Before(AspectContext aspectContext) { Console.WriteLine("TestHelloBefore"); return Task.CompletedTask; } } public interface IHello { // 打上拦截器也会生效,注意看下面的文字说明 [TestHelloBefore] void SayHello(); } public abstract class TestParentHello { //打上拦截器也会生效 [TestHelloBefore] public virtual void Say() { Console.WriteLine("Say"); } } [Component] public class TestHello: TestParentHello,IHello { public void SayHello() { } }
本文介绍的拦截器标签的方式实现切面编程,是基于组件 Castle 创建代理类来实现的,默认是采用继承原来的 class,然后把 virtual 的方法进行重写。
如果你原来的 class 有继承一些接口的话,代理类也会继承,并且 AOP 也会生效,只不过需要显示接口的方式调用才行。
public interface InterIgnoreAop { [ExceptionAop1Attribute] [ExceptionAop2Attribute] void sayIgnore(); } [Component] public class IgnoreAop1 : InterIgnoreAop { // 如果不用 [IgnoreAop] 特性的话,会走 2 个 AOP 逻辑:ExceptionAop1Attribute 和 ExceptionAop2Attribute [IgnoreAop] // 针对方法关闭 AOP,那么 2 个 AOP 逻辑都不会走了 // [IgnoreAop(Target = new[] { typeof(ExceptionAop1Attribute) })] // 针对方法关闭某个 AOP,就只会走 ExceptionAop2Attribute public virtual void sayIgnore() { } }
Aspect 是一对一的方式,我想要某个 class 开启拦截器功能,需要针对每个 class 去配置。
比如,有 2 个 controller,每个 controller 都有 2 个 action 方法:
[Component] public class ProductController { public virtual string GetProduct(string productId) { return "GetProduct:" + productId; } public virtual string UpdateProduct(string productId) { return "UpdateProduct:" + productId; } } [Component] public class UserController { public virtual string GetUser(string userId) { return "GetUser:" + userId; } public virtual string DeleteUser(string userId) { return "DeleteUser:" + userId; } }
如果需要这 2 个 controller 的 action 方法,在方法执行前打 log,在方法执行后打 log,按照上一节 Aspect 的话,需要每个 controller 都配置。
如果有 100 个 controller,就需要配置 100 次。
下面看如何用 Pointcut 的方式,方便的配置一种切面,去适用于 N 个对象。
Pointcut 标签类有如下属性:
属性 | 说明 |
---|---|
Name | Pointcut 切面的名称(默认为空,和拦截方法进行匹配,参考下面说明) |
RetType | 匹配目标类的方法的返回类型(默认是 %) |
NameSpace | 匹配目标类的 namespace(默认是 %) |
ClassName | 匹配目标类的类名称(和下面的 AttributeType 参数二选一必填) |
AttributeType | 匹配特定的标签(和上面的 ClassName 参数二选一必填) |
AttributeFlag | 当指定了 AttributeType 时可以用此值来扩展(具体看下面) |
MethodName | 匹配目标类的方法名称(默认是 %) |
AttributeFlag(指定按照 AttributeType来 匹配时生效)
1、NONE(默认值,代表打了 AttributeType 才匹配);
2、AssignableFrom,代表打了 AttributeType 或者AttributeType 的父类(排除 Abstract 类型)的都会被匹配;
3、AssignableTo,代表打了 AttributeType 或者 AttributeType 的子类的都会被匹配。
匹配符号 | 说明 |
---|---|
* | 匹配 >= 1 个 |
% | 匹配 >= 1 个 |
? | 只匹配 1 个 |
- | 只匹配 1 个 |
// *Controller 代表只要是 Controller 结尾的类都能匹配 // Get* 代表匹配成功的类中,所有 Get 打头的方法都能匹配 [Pointcut(Class = "*Controller", Method = "Get*")] public class LoggerPointCut { }
// *Controller 代表只要是 Controller 结尾的类都能匹配 // Get* 代表匹配成功的类中,所有 Get 打头的方法都能匹配 [Pointcut(ClassName = "*Controller",MethodName = "Get*")] public class LoggerPointCut { }
// 打了 TestPointAttributes1 特性的 Component 都会被识别 [Pointcut(AttributeType = typeof(TestPointAttributes1))] // 因为设置了 AssignableFrom,代表打了 TestPointAttributes1 和它的父类(排除 Abstract 类型)都会被识别 //[Pointcut(AttributeType = typeof(TestPointAttributes1),AttributeFlag = AssignableFlag.AssignableFrom)] public class LoggerPointCut { }
配合 Pointcut 切面标签,可以在打了这个标签的 class 下定义拦截方法,在方法上得打上特定的标签,有如下几种:
切入点标签 | 说明 |
---|---|
Before | 在匹配成功的类的方法执行前执行 |
After | 在匹配成功的类的方法执行后执行(不管目标方法成功还是失败) |
AfterReturn | 在匹配成功的类的方法执行后执行(只是目标方法成功) |
AfterThrows | 在匹配成功的类的方法执行后执行(只是目标方法抛异常时) |
Around | 环绕目标方法,承接了匹配成功的类的方法的执行权 |
以上 3 种标签有一个可选的参数:Name(默认为空,可以和 Pointcut 的 Name 进行 mapping)。
因为一个 class 上可以打多个 Pointcut 切面,一个 Pointcut 切面可以根据 name 去匹配对应拦截方法。
切入点标签所在方法的参数说明:
1、Around 切入点必须要指定 AspectContext 类型和 AspectDelegate 类型的 2 个参数,且返回类型要是 Task,否则会报错;
2、除了 Around 切入点以外其他的切入点的返回值只能是 Task 或者 Void,否则会报错;
3、除了 Around 切入点以外其他的切入点可以指定 AspectContext 类型参数注入进来;
4、After 切入点可以指定 Returing 参数,可以把目标方法的返回注入进来,如果目标方法抛异常则是异常本身;
5、AfterReturn 切入点可以指定 Returing 参数,可以把目标方法的返回注入进来;
6、AfterThrows 切入点可以指定 Throwing参数,可以把目标方法抛出的异常注入进来;
7、只要你参数类型是你注册到 DI 容器,运行时会自动从 DI 容器把类型注入进来;
8、如果是指定 Attribute 扫描的,支持方法注入这个 Attribute 进来;
9、支持方法注入当前是哪个 PointCut;
10、可以使用 Autowired、Value标 签来修饰参数。
/// <summary> /// 第一组切面 /// </&> [Pointcut(NameSpace = "Autofac.Annotation.Test.test6", Class = "Pointcut*", OrderIndex = 1)] public class PointcutTest1 { [Around] public async Task Around(AspectContext context,AspectDelegate next) { Console.WriteLine("PointcutTest1.Around-start"); await next(context); Console.WriteLine("PointcutTest1.Around-end"); } [Before] public void Before() { Console.WriteLine("PointcutTest1.Before"); } [After] public void After() { Console.WriteLine("PointcutTest1.After"); } [AfterReturn(Returing = "value1")] public void AfterReturn(object value1) { Console.WriteLine("PointcutTest1.AfterReturn"); } [AfterThrows(Throwing = "ex1")] public void Throwing(Exception ex1) { Console.WriteLine("PointcutTest1.Throwing"); } }
/// <summary> /// 第二组切面 /// </&> [Pointcut(NameSpace = "Autofac.Annotation.Test.test6", Class = "Pointcut*", OrderIndex = 0)] public class PointcutTest2 { [Around] public async Task Around(AspectContext context,AspectDelegate next) { Console.WriteLine("PointcutTest2.Around-start"); await next(context); Console.WriteLine("PointcutTest2.Around-end"); } [Before] public void Before() { Console.WriteLine("PointcutTest2.Before"); } [After] public void After() { Console.WriteLine("PointcutTest2.After"); } [AfterReturn(Returing = "value")] public void AfterReturn(object value) { Console.WriteLine("PointcutTest2.AfterReturn"); } [AfterThrows(Throwing = "ex")] public void Throwing(Exception ex) { Console.WriteLine("PointcutTest2.Throwing"); } }
[Component] public class Pointcut1Controller { //正常case public virtual void TestSuccess() { Console.WriteLine("Pointcut1Controller.TestSuccess"); } //异常case public virtual void TestThrow() { Console.WriteLine("Pointcut1Controller.TestThrow"); throw new ArgumentException("ddd"); } } [Component] public class Pointcut2Controller { //正常case public virtual void TestSuccess() { Console.WriteLine("Pointcut1Controller.TestSuccess"); } //异常case public virtual void TestThrow() { Console.WriteLine("Pointcut1Controller.TestThrow"); throw new ArgumentException("ddd"); } }
按照上面的配置:
1、Pointcut1Controller.TestSuccess 和 TestThrow 2 个方法会被匹配;
2、Pointcut2Controller.TestThrow 和 TestThrow 2 个方法会被匹配。
执行顺序是和上一节说的 [Aspect] 是一致的。
[Pointcut(NameSpace = "Autofac.Annotation.Test.issue31", AttributeType = typeof(ExceptionAttIgnore1Attribute))] public class TestIgnoreAopClass1 { [Before] public void befor() { } } [Pointcut(NameSpace = "Autofac.Annotation.Test.issue31", AttributeType = typeof(ExceptionAttIgnore2Attribute))] public class TestIgnoreAopClass2 { [Before] public void befor() { } } public interface InterIgnoreAop2 { [ExceptionAttIgnore1Attribute] [ExceptionAttIgnore2Attribute] void sayIgnore(); } [Component] public class IgnoreAop2 : InterIgnoreAop2 { // 如果不打上 [IgnoreAop] 特性的话,那么切面 TestIgnoreAopClass1 和切面 TestIgnoreAopClass2 都会走 [IgnoreAop] //打上的话,就都不会走了 // [IgnoreAop(Target = new[] { typeof(TestIgnoreAopClass1)})] // 这样就只会走切面 TestIgnoreAopClass2 public virtual void sayIgnore() { } }
在 winform/wpf/asp.net 等有异步上下文的场景中,同步代码调用异步代码且 Wait 异步结果会导致死锁问题(ASP.NET Core 没有此问题)。
那么该问题在 AOP 中会是怎样呢?
举例:在 wpf 中同步的 click 事件中调用另外一个方法,这个方法是会被 AOP 的:
// wpf 的 click 事件 private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { // 参考下发代码,TestController 的 controllerTest 方法是同步方法,会被 AOP var abd = container.Resolve().controllerTest(); } [Component] public class TestController { [AfterAopTestAttribute] public virtual string controllerTest() { Console.WriteLine(111); return "abc"; } } public class AfterAopTestAttribute : AspectAfter { public override async Task After(AspectContext aspectContext, object result) { // 注意这里,当同步方法被 AOP,当调用同步方法走到这里会被卡死 await aspectContext.ComponentContext.Resolve ().Send(); } } [Component] public class Serial { public virtual async Task Send() { await Task.Delay(100); Console.WriteLine("aaa"); } }
当执行到 After 拦截器时候,如果不加.ConfigureAwait(false) 会造成死锁。
所以在本框架的 AOP 执行流程里面,会自动将 AOP 执行逻辑包裹.ConfigureAwait(false) 遇到阻塞死锁的时候会直接不等待,所以那上面代码的效果就是 await Task.Delay(100); 不会死锁,会异步出去,不等待它了
另外本框架的 AOP 执行逻辑会自动检测死锁,比如改成如下:
public class AfterAopTestAttribute : AspectAfter { public override async Task After(AspectContext aspectContext, object result) { // 会直接触发死锁检测,报异常出来 aspectContext.ComponentContext.Resolve<Serial>().Send().ConfigureAwait(false).GetAwaiter().GetResult(); } }
本框架的 AOP 执行自带死锁检测,调试的时候可以看到错误内容,可以定位到是哪个方法造成了死锁,检测到死锁会异常终止程序,至少不会卡死!
先走 Pointcut 的逻辑,然后再走 Aspect 的逻辑。
注意事项:打了 Pointcut 的 class,框架会注册为单例,不要里面定义共享属性,会有并发问题。
[Pointcut(NameSpace = "Command.*", Class = "CommandData*", OrderIndex = /*2*/ 3)] public class DelayInterceptor { private int a = 0; [After(Returing = "value")] public void After(AspectContext context, object value) { } }
关于字符串匹配的举例:
匹配结果 | 匹配模板 | 要匹配的字符串 |
---|---|---|
匹配结果:true | "%" | "" |
匹配结果:true | "%" | " " |
匹配结果:true | "%" | "asdfa asdf asdf" |
匹配结果:true | "%" | "%" |
匹配结果:false | "_" | "" |
匹配结果:true | "_" | " " |
匹配结果:true | "_" | "4" |
匹配结果:true | "_" | "C" |
匹配结果:false | "_" | "CX" |
匹配结果:false | "[ABCD]" | "" |
匹配结果:true | "[ABCD]" | "A" |
匹配结果:false | "[ABCD]" | "b" // 因为区分大小写 |
匹配结果:false | "[ABCD]" | "X" |
匹配结果:false | "[ABCD]" | "AB" |
匹配结果:true | "[B-D]" | "C" |
匹配结果:true | "[B-D]" | "D" |
匹配结果:false | "[B-D]" | "A" |
匹配结果:false | "[^B-D]" | "C" |
匹配结果:false | "[^B-D]" | "D" |
匹配结果:true | "[^B-D]" | "A" // 不是B或者C或者D打头 |
匹配结果:true | "[^BCD]" | "A" // 不是B或者C或者D打头 |
匹配结果:false | "[^(abc)]" | "abc" //不是abc打头 |
匹配结果:true | "[^(abc)]" | "abd" |
匹配结果:false | "[^(abc)(def)]" | "def" /不是abc或者def打头 |
匹配结果:false | "[^(abc)(def)]" | "abc" |
匹配结果:true | "[^(abc)(def)]" | "edf" |
匹配结果:true | "[(abc)(def)]" | "abc" // 是abc或者def打头 |
匹配结果:false | "[(abc)]" | "def" |
匹配结果:true | "%TEST[ABCD]XXX" | "lolTESTBXXX" |
匹配结果:false | "%TEST[ABCD]XXX" | "lolTESTZXXX" |
匹配结果:false | "%TEST[^ABCD]XXX" | "lolTESTBXXX" |
匹配结果:true | "%TEST[^ABCD]XXX" | "lolTESTZXXX" |
匹配结果:true | "%TEST[B-D]XXX" | "lolTESTBXXX" |
匹配结果:true | "%TEST[^B-D]XXX" | "lolTESTZXXX" |
匹配结果:true | "%Stuff.txt" | "Stuff.txt" |
匹配结果:true | "%Stuff.txt" | "MagicStuff.txt" |
匹配结果:false | "%Stuff.txt" | "MagicStuff.txt.img" |
匹配结果:false | "%Stuff.txt" | "Stuff.txt.img" |
匹配结果:false | "%Stuff.txt" | "MagicStuff001.txt.img" |
匹配结果:true | "Stuff.txt%" | "Stuff.txt" |
匹配结果:false | "Stuff.txt%" | "MagicStuff.txt" |
匹配结果:false | "Stuff.txt%" | "MagicStuff.txt.img" |
匹配结果:true | "Stuff.txt%" | "Stuff.txt.img" |
匹配结果:false | "Stuff.txt%" | "MagicStuff001.txt.img" |
匹配结果:true | "%Stuff.txt%" | "Stuff.txt" |
匹配结果:true | "%Stuff.txt%" | "MagicStuff.txt" |
匹配结果:true | "%Stuff.txt%" | "MagicStuff.txt.img" |
匹配结果:true | "%Stuff.txt%" | "Stuff.txt.img" |
匹配结果:false | "%Stuff.txt%" | "MagicStuff001.txt.img" |
匹配结果:true | "%Stuff%.txt" | "Stuff.txt" |
匹配结果:true | "%Stuff%.txt" | "MagicStuff.txt" |
匹配结果:false | "%Stuff%.txt" | "MagicStuff.txt.img" |
匹配结果:false | "%Stuff%.txt" | "Stuff.txt.img" |
匹配结果:false | "%Stuff%.txt" | "MagicStuff001.txt.img" |
匹配结果:true | "%Stuff%.txt" | "MagicStuff001.txt" |
匹配结果:true | "Stuff%.txt%" | "Stuff.txt" |
匹配结果:false | "Stuff%.txt%" | "MagicStuff.txt" |
匹配结果:false | "Stuff%.txt%" | "MagicStuff.txt.img" |
匹配结果:true | "Stuff%.txt%" | "Stuff.txt.img" |
匹配结果:false | "Stuff%.txt%" | "MagicStuff001.txt.img" |
匹配结果:false | "Stuff%.txt%" | "MagicStuff001.txt" |
匹配结果:true | "%Stuff%.txt%" | "Stuff.txt" |
匹配结果:true | "%Stuff%.txt%" | "MagicStuff.txt" |
匹配结果:true | "%Stuff%.txt%" | "MagicStuff.txt.img" |
匹配结果:true | "%Stuff%.txt%" | "Stuff.txt.img" |
匹配结果:true | "%Stuff%.txt%" | "MagicStuff001.txt.img" |
匹配结果:true | "%Stuff%.txt%" | "MagicStuff001.txt" |
匹配结果:true | "?Stuff?.txt?" | "1Stuff3.txt4" |
匹配结果:false | "?Stuff?.txt?" | "1Stuff.txt4" |
匹配结果:false | "?Stuff?.txt?" | "1Stuff3.txt" |
匹配结果:false | "?Stuff?.txt?" | "Stuff3.txt4" |
使用场景:
1、打个标签自动注册成为事件监听者;
2、支持事件处理者(无参数返回);
3、支持事件处理者(有参数返回);
4、支持同步和异步事件。
比如订单支付成功触发事件给以下事件接收者:发送给客人短信;发送运营钉钉通知;调用供应商 api 下单等。
本插件自带实现了一个 EventBus,可以在需要 EventBus 的场景下使用。
[Component] public class WorkPublisher { // 事件发送者可以注入进来 [Autowired] public IEventPublisher EventPublisher { get; set; } }
或者
[Component] public class WorkPublisher { // 异步的事件发送者可以注入进来 [Autowired] public IAsyncEventPublisher AsyncEventPublisher { get; set; } }
同步事件发布器【IEventPublisher】 | 说明 |
---|---|
Publish 方法 | 同步的事件发送者 |
Publish<T> | 同步的事件发送者并拿到返回值(T 为返回值类型) |
异步事件发布器【IAsyncEventPublisher】 | 说明 |
---|---|
PublishAsync 方法 | 异步的事件发送者 |
PublishAsync<T> | 异步的事件发送者并拿到返回值(T 为返回值类型) |
订阅类型 | 说明 | 使用方法 |
---|---|---|
IHandleEvent<T1> | 同步的事件订阅者,T1 是消息类型 | Handle 方法无返回值 |
IHandleEventAsync<T1> | 异步的事件订阅者,T1 是消息类型 | HandleAsync 方法无返回值 |
IHandleEventAsync<T1> | 异步的事件订阅者,T1 是消息类型 | HandleAsync 方法无返回值 |
IReturnEvent<T1, T2> | 同步的事件处理并得到返回值订阅者,T1 是消息类型,T2 是返回类型 | Handle 方法有返回值 |
IReturnEventAsync<T1, T2> | 异步的事件并得到返回值订阅者,T1 是消息类型,T2 是返回类型 | HandleAsync 方法有返回值 |
// 同步事件处理器1 [Component] public class WorkListener1:IHandleEvent<WorkModel1> { ////也可以注入DI容器的其他类型 [Autowired] public School School { get; set; } //事件接收 public void Handle(WorkModel1 @event) { Console.WriteLine(@event.Name + School.Name); } } //同步事件处理器2 [Component] public class WorkListener2:IHandleEvent<WorkModel1> { //也可以注入DI容器的其他类型 [Autowired] public School School { get; set; } //事件接收 public void Handle(WorkModel1 @event) { Console.WriteLine(@event.Name + School.Name); } } //异步事件处理器 [Component] public class AsyncWorkListener2:IHandleEventAsync<WorkModel1> { [Autowired] public School School { get; set; } public async Task HandleAsync(WorkModel1 @event) { Console.WriteLine(@event.Name + School.Name); await Task.Delay(1000); } } //同步事件处理器有返回值 [Component] public class WorkReturnListener2:IReturnEvent<WorkModel1, WorkReturnListener2Model> { [Autowired] public School School { get; set; } public WorkReturnListener2Model Handle(WorkModel1 @event) { return new WorkReturnListener2Model { Name = @event.Name + School.Name }; } } //异步事件处理器有返回值 [Component] public class AsyncWorkReturnListener2:IReturnEventAsync<WorkModel1, WorkReturnListener2Model> { [Autowired] public School School { get; set; } public async TaskHandleAsync(WorkModel1 @event) { return await Task.FromResult(new WorkReturnListener2Model { Name = @event.Name + School.Name }); } } [Component] public class WorkPublisher { //同步事件发送器 [Autowired] public IEventPublisher EventPublisher { get; set; } //异步事件发送器 [Autowired] public IAsyncEventPublisher AsyncEventPublisher { get; set; } }
事件发送和订阅测试代码:
var builder = new ContainerBuilder(); // autofac打标签模式 builder.RegisterModule(new AutofacAnnotationModule(typeof(WorkPublisher).Assembly)); var ioc = builder.Build(); var a1 = ioc.Resolve<WorkPublisher>(); Assert.NotNull(a1); //下面的Publish方法调用会触发 上面的 WorkListener1 和 WorkListener2 的 Hangdle方法 a1.EventPublisher.Publish(new WorkModel1()); //下面的方法会触发上面的 WorkListener1 和 WorkListener2 的 Hangdle方法 并且 触发上面的 WorkReturnListener2 的 Handle 方法并拿到返回值 List<WorkReturnListener2Model> sendResult = a1.EventPublisher.Publish<WorkReturnListener2Model>(new WorkModel1()); //下面的方法会触发上面的 AsyncWorkListener2 的HandleAsync方法 await a1.AsyncEventPublisher.PublishAsync(new WorkModel1()); //下面的方法会触发上面的 AsyncWorkListener2 的HandleAsync方法 并且触发上面的 AsyncWorkReturnListener2 的 HandleAsync方法并拿到返回值 List<WorkReturnListener2Model> sendAsyncResult = await a1.AsyncEventPublisher.PublishAsync<WorkReturnListener2Model>(new WorkModel1());
在类的初始化过程中进行自定义逻辑而设计的 BeanPostProcessor,有 2 个方法:PostProcessBeforeInitialization;和 PostProcessAfterInitialization。
该方法在 bean 实例化完毕(且已经注入完毕),在属性设置或自定义 init 方法执行之前执行。
该方法在 bean 实例化完毕(且已经注入完毕),在属性设置或自定义 init 方法执行之后执行。
先定义一个自定义特性:
// 自定义特性 [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public sealed class Soa : Attribute { // 构造函数 public Soa(Type type) { Type = type; } // 注册的类型 internal Type Type { get; set; } }
特性的名字叫 Soa,有一个构造方法,传参为一个 Class Type,下面需要实现一个 BeanPostProcessor:
[Component] public class SoaProcessor : BeanPostProcessor { // 在实例化后且属性设值之前执行 public object PostProcessBeforeInitialization(object bean) { Type type = bean.GetType(); // 找到 bean 下所有的字段 var fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); foreach (var field in fieldInfos) { // 看字段上面有没有打 Soa 自定义特性 var soaAnnotation = field.GetCustomAttribute(typeof(Soa)) as Soa; if (soaAnnotation == null) continue; // 有的话根据特性的参数 Type 来实例化对象并设值 var instance = Activator.CreateInstance(soaAnnotation.Type) as ISoa; if (instance == null) continue; field.SetValue(bean, instance); } return bean; } //不管返回 public object PostProcessAfterInitialization(object bean) { return bean; } }
实现 BeanPostProcessor,写一个类继承并实现它的接口即可。 然后打上 [Compoment] 注册到容器中即可。
[Component] public class Test11Models1 { [Soa(typeof(SoaTest1))] private ISoa Soa1; [Soa(typeof(SoaTest2))] private ISoa Soa2; public string getSoa1() { return Soa1.say(); } public string getSoa2() { return Soa2.say(); } } public interface ISoa { string say(); } public class SoaTest1 : ISoa { public string say() { return nameof(SoaTest1); } } public class SoaTest2 : ISoa { public string say() { return nameof(SoaTest2); } }
[Fact] public void Test1() { var builder = new ContainerBuilder(); builder.RegisterSpring(r => r.RegisterAssembly(typeof(TestBeanPostProcessor).Assembly)); var container = builder.Build(); var isRegisterd = container.TryResolve(out Test11Models1 model1); Assert.True(isRegisterd); Assert.Equal("SoaTest1",model1.getSoa1()); Assert.Equal("SoaTest2",model1.getSoa2()); }
Test11Models1 这个类打了 [Compoment] 注册到容器,当从容器获取它的时候会走到上面的 SoaProcessor。然后识别到里面有打了自定义特性 [Soa],并根据注册的参数实例化。
特性 | 使用方式 | 备注 |
---|---|---|
Conditional | 打在 class 或者方法上面 | 条件加载,自定义实现的 |
ConditionOnBean | 打在标有 Bean 特性的方法上面 | 条件加载 |
ConditionOnMissingBean | 打在标有 Bean 特性的方法上面 | 条件加载 |
ConditionOnClass | 打在 class 或者含有 Bean 特性的方法上面 | 条件加载 |
ConditionOnMissingClass | 打在 class 或者含有 Bean 特性的方法上面 | 条件加载 |
ConditionOnProperty | 打在 class 或者方法上面 | 条件加载 |
ConditionOnProperties | 打在 class 或者方法上面 | 条件加载 |
DependsOn | 可以配合 Bean 和 Component 使用 | A 的实例化依赖另一个 B 的实例化,但是 A 并不需要持有一个 B 的对象 |
该特性接受一个实现了 ICondition 接口的 Type 类型的参数。
首先定义一个 class 实现 ICondition 接口的 ShouldSkip 方法,下面的类的意思看注释应该可以明白:
public class Test10Condition : ICondition { /// <summary> /// 只有当 windows 系统下才被注册 /// </summary> /// <param name="context"></param> /// <param name="metadata"></param> /// <returns>返回true代表不满足条件,那就不会被注册到容器</returns> public bool ShouldSkip(IComponentRegistryBuilder context, object metadata) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { //是linux系统 就不注册 return true; } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { //是mac系统 也不注册 return true; } //是windows系统 那就注册 return false; } }
使用上面的条件用 Conditional 特性打在方法上面,这个条件表明了只有在 windows 平台才会将 Test10Model1 注册到容器中。
[AutoConfiguration] public class Test10Config { [Bean] [Conditional(typeof(Test10Condition))] public Test10Model1 getTest10Model1() { Console.WriteLine("registered Test10Model1"); return new Test10Model1(); } }
上面的例子是结合 Bean 特性一起使用,Conditional 特性还可以打在 class 上面,结合 Compoment 或者 AutoConfiguration 特性来实现满足条件才注册。
这 2 个特性是只能配合 Bean 特性一起使用,且只能打在方法上面,不能打在 class 上面。
1、ConditionOnBean 的意思是,如果指定的类已经被注册的话,就注册。
[AutoConfiguration] public class Test10Config { [Bean] [ConditionOnBean(typeof(Test10Model3))] public Test10Model5 getTest10Model5() { Console.WriteLine("registered Test10Model5"); return new Test10Model5(); } }
上面的代码的意思是,如果 Test10Model3 被注册的话,才会注册 Test10Model5。
2、ConditionOnMissingBean 的意思是,如果指定的类没被注册的话,就注册。
[AutoConfiguration] public class Test10Config { [Bean] [ConditionOnMissingBean(typeof(Test10Model1))] public Test10Model3 getTest10Model3() { Console.WriteLine("registered Test10Model3"); return new Test10Model3(); } }
上面的代码的意思是,如果 Test10Model1 没被注册的话,才会注册 Test10Model3。
这 2 个特性是配合 Compoment 或者 AutoConfiguration,PointCut 等特性一起使用,可以打在 class 和含有 Bean 特性的方法上面,该特性的参数需要填入类的完整名称。
1、ConditionOnClass 的意思是,如果当前运行环境存在指定的类,这个类不一定是 Componet 的话,就注册。
111111111111111
[Bean] [ConditionOnClass("Autofac.Annotation.Test.Test10Model2,Autofac.Configuration.Test")] public Test10Model6 getTest10Model6() { // 找的到 class,注册 Test10Model6 Console.WriteLine("registered Test10Model6"); return new Test10Model6(); }
2、ConditionOnMissingClass 的意思是,如果当前运行环境不存在指定的类的话,就注册。
[Bean] [ConditionOnMissingClass("Autofac.Annotation.Test.test10.Test10Model2,xxxx")] public Test10Model7 getTest10Model7() { // 找不到 class,注册 Test10Model7 Console.WriteLine("registered Test10Model7"); return new Test10Model7(); }
这 2 个特性可以配合 Bean、Compoment、AutoConfiguration、PointCut 等特性一起使用,可以打在 class 和 method 上面。
意思是,如果数据源(读取当前项目的 appsettings.json)满足下列条件,就注册:
1、指定的 key 对应的值为 xxx 时;
2、不存在指定的 key。
appsettings.json
{ "onproperty": "on" }
1、文件中存在指定的 key 为 xxx 时:
[Bean] [ConditionalOnProperty("onproperty", "on")] public Test10Model8 getTest10Model8() { // 因为配置文件 onproperty 的值为 on,所以会注册 Console.WriteLine("registered Test10Model8"); return new Test10Model8(); }
2、不存在指定的 key:
[Bean] [ConditionalOnProperty("onproperty1", matchIfMissing = true)] public Test10Model10 getTest10Model10() { // 由于配置文件里面没有 onproperty1,所以会注册 Console.WriteLine("registered Test10Model10"); return new Test10Model10(); }
3、存在指定的 key:
[Bean] [ConditionalOnProperty("onproperty1")] public Test10Model10 getTest10Model10() { // 由于配置文件有存在 onproperty1,不管对应的值是什么,都会注册 Console.WriteLine("registered Test10Model10"); return new Test10Model10(); }
当想要指定多个值同时满足的话就用 ConditionOnProperties,道理是一样的。
该特性可以配合 Bean 和 Component 特性一起使用,是用来表示一个 A 的实例化依赖另一个 B 的实例化,但是 A 并不需要持有一个 B 的对象
[Bean] [DependsOn(typeof(Test12Bean4))] public Test12Bean3 get13() { Debug.WriteLine("new Test12Bean3"); return new Test12Bean3 { Hello = "world" }; } [Bean] public Test12Bean4 get14() { Debug.WriteLine("new Test12Bean4"); result.Add("get14"); return new Test12Bean4 { Hello = "world" }; }
上面的意思是,在需要加载 Test12Bean3 实例(还没加载)的时候,由于设置了 DependsOn 类 Test12Bean4,先去加载 Test12Bean4。
[Component] [DependsOn(typeof(Test12Bean8))] //也支持配置类的全限定名称 [DependsOn("Autofac.Annotation.Test.test12.Test12Bean8,Autofac.Configuration.Test")] public class Test12Bean7 { public Test12Bean7() { //Console.WriteLine("然后我在加载") } public string Hello { get; set; } } [Component] public class Test12Bean8 { public Test12Bean8() { //Console.WriteLine("我先加载") } public string Hello { get; set; } }
上面的意思是,在需要加载 Test12Bean7 的实例的时候,先去加载 Test12Bean8。
需要把 controller 的实例的生成也转交给 autofac 才行。
具体可以可以看下文档:https://autofaccn.readthedocs.io/en/latest/integration/aspnetcore.html"
在 netcore3.0 之前关键代码是:
services.AddMvc().AddControllersAsServices(); builder.Populate(services);
netcore3.1 之后的话,参考下面:
public void ConfigureContainer(ContainerBuilder builder) { // 注册 Autofac 特性注入模式 builder.RegisterModule(new AutofacAnnotationModule(typeof(Startup).Assembly)); } public void ConfigureServices(IServiceCollection services) { // 这行代码代表 Controller 的实例的创建交给 autofac 容器处理 services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); // 这行代码代表 PageModel 的实例的创建交给 autofac 容器处理(pagemodel 是 razor 的概念) services.Replace(ServiceDescriptor.Transient<IPageModelActivatorProvider, ServiceBasedPageModelActivatorProvider>()); }
除了上面那段代码以外,只能实现 Autofac 去创建 Controller 对象,但是必须得打【Compoment】标签注册到 autofac 容器才行,注意:每个 controller 都得打:
[Component] public class HomeController : Controller { }
按照上面的配置后,Controller 的实例的创建就是 Autofac 创建的,那么拦截器或者 PointCut 切面就可以作用在 Controller 这一层了。
但是要注意,如果用到拦截器或者 PointCut 切面功能,需要保证你的方法需要是 public virtual。
因为默认是根据类型是 class 去创建动态代理的,如果是 interface 的话是不需要的,具体原因可以参考拦截器那里有说明。