依赖注入

Autofac.Annotation

Autofac.Annotation 地址:https://github.com/yuzd/Autofac.Annotation

该类库可以实现特性注入、拦截器、切面以及配置文件读取。

具体使用方法,可以参考官方文档:https://github.com/yuzd/Autofac.Annotation/wiki

FlexibleCore 对源代码做了修改,读取配置文件部分,支持敏感数据(如数据库密码)的 RSA 加密字符串读取。

让一个配置类去注册一些对象到 DI 容器内

打了 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。

AutoConfiguration 标签支持下面几个参数配置

/// <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; }
                                

Bean 标签支持下面几个参数配置

/// <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; }
                                

Bean 标签的方法的参数支持注入 DI 已存在的,或者 Value 标签

//方法的参数支持注入 DI 已存在的,或者 Value 标签
[Bean]
public MyModel GetMyModel(OtherClass other,[Value("${connetionString}")] connetionString)
{
    return new MyModel
    {
        Name = "name"
    };
}
                                

AutoConfiguration 本身注册自己是单例

打了 AutoConfiguration 的 class 也会被单例的形式注册到容器中。

在业务代码里面的使用场景

使用 Key 指定可以实现不同的环境配置不同的对象到 DI 容器。

一些复杂的对象实例可以使用 Bean 的方式注册的 DI 容器。

Componet 标签把类型注册到 DI 容器

原理解释

框架会扫描打了 Componet 标签的 class。如果 Componet 标签里面指定了要注册的类型,则会只注册为这个类型到 DI 容器。如果没有指定则会把当前 class (参考下面的 4),以及父类,以及接口都会注册到DI容器(参考下面的 1、2 和 3)。

如何修改这个默认配置呢,比如关闭自动注册父类和接口。 可以参考:https://github.com/yuzd/Autofac.Annotation/issues/11

Componet 有哪些属性

属性名称 类型 含义
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 的属性注入方式)

Componet 的常用构造方法

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; }
                                

把一个类型注册到 DI 容器

// 把 Student 类型注册到容器                                    
[Component]
public class Student
{
}
                                

把当前类型和父类注册到 DI 容器

public class Person
{
}

// 把 Student 类型注册到容器     
// 并且把 Person 类型也注册到容器,根据 Person 类型拿到的是 Student 的实体
[Component]
public class Student: Person
{
}
                                

把当前类型和接口注册到 DI 容器

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 标签

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 自动装配

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;
    }
}
                                

在单例的对象里面 Autowired 多实例

使用 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; }
}
                                

延迟自动装配 Lazy

[Component]
public class TestLazyModel
{
    [Autowired]
    public Lazy<TestAutowiredModal1> TestAutowiredModal1 { get; set; }

    [Autowired]
    public TestAutowiredModal2 TestAutowiredModal2 { get; set; }
}
                                

使用瞬态的注入方式 Autowired 只会获取一次,并不会每次都是新的实例,要实现瞬态特性,应使用以下方法:

private IDemoInstancePerDependency DemoInstancePerDependency => GetServiceProvider.GetService<IDemoInstancePerDependency>();
                                

静态方法获取 IOC

在静态方法中是没办法用 Autowired 获取实例的,框架提供了另外一种方式

//只能获取瞬时和单例模式
IHttpContextAccessor httpContextAccessor = GetServiceProvider.GetService<IHttpContextAccessor>();
//只能获取作用域模式
IHttpContextAccessor httpContextAccessor = GetServiceProvider.GetScopeService<IHttpContextAccessor>();
                                

示例:

IHttpContextAccessor httpContextAccessor = GetServiceProvider.Instance.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
                                

PropertySource 指定数据源

属性 说明
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 装配数据源里面的值

属性 说明
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 加密
                                

如果要监听文件有改变能获取到最新的值请使用 IValue

[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;
    }
}
                                

在 aspnetcore 应用中 Value 默认用的数据源可以切换成应用的 Configuration

containerBuilder.RegisterModule(new AutofacAnnotationModule()
            .SetDefaultValueResource(c.Configuration) // c.Configuration 是当前应用的 Configuration,这样设置后,如果没有特别指定 PropertySource 的话 value 读取的数据源默认从这里获取了
    );
                                

动态数据源

nacos

前提条件

自己 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 方法

拦截器的方法参数 AspectContext 属性说明

名称 说明
ComponentContext DI 容器,可以从中取得你已注册的实例
Arguments 目标方法的参数
TargetMethod 目标方法的 MethodInfo
ReturnValue 目标方法的返回
Method 目标方法的代理方法 MethodInfo

前置拦截器(Before)

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 方法。

后置拦截器(After),不管目标方法成功还是抛异常都会执行

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 是成功还是抛异常,都会执行。

成功返回拦截器(AfterReturn),只有目标方法成功的时候才会执行

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 方法。

异常拦截器(AfterThrows)

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 方法。

环绕拦截器(Around)

注意: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 方法。

Around Befor After AfterReturn AfterThrows 一起使用

正常 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 个对象。

定义一个切面:创建一个class,打上 Pointcut 的标签

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 切面后,需要定义这个切面的拦截方法(也叫切入点)

配合 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()
    {
    }
}
                                

AOP 死锁问题

在 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 的逻辑,然后再走 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"

Autofac 标签,轻松实现 EventBus【Event aggregator and messenger】

使用场景:

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 Task HandleAsync(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 的设计

在类的初始化过程中进行自定义逻辑而设计的 BeanPostProcessor,有 2 个方法:PostProcessBeforeInitialization;和 PostProcessAfterInitialization。

1、PostProcessBeforeInitialization

该方法在 bean 实例化完毕(且已经注入完毕),在属性设置或自定义 init 方法执行之前执行。

2、PostProcessAfterInitialization

该方法在 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],并根据注册的参数实例化。

有些时候,想要满足某个条件才把一个类注册到容器里面。比如如何切换 Services,可以根据条件注册 Bean 和 Configuration。

特性 使用方式 备注
Conditional 打在 class 或者方法上面 条件加载,自定义实现的
ConditionOnBean 打在标有 Bean 特性的方法上面 条件加载
ConditionOnMissingBean 打在标有 Bean 特性的方法上面 条件加载
ConditionOnClass 打在 class 或者含有 Bean 特性的方法上面 条件加载
ConditionOnMissingClass 打在 class 或者含有 Bean 特性的方法上面 条件加载
ConditionOnProperty 打在 class 或者方法上面 条件加载
ConditionOnProperties 打在 class 或者方法上面 条件加载
DependsOn 可以配合 Bean 和 Component 使用 A 的实例化依赖另一个 B 的实例化,但是 A 并不需要持有一个 B 的对象

Conditional

该特性接受一个实现了 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 特性来实现满足条件才注册。

ConditionOnBean 和 ConditionOnMissingBean

这 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。

ConditionOnClass 和 ConditionOnMissingClass

这 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();
}
                                

ConditionOnProperty 和 ConditionOnProperties

这 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,道理是一样的。

DependsOn

该特性可以配合 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。

关于 netcore 中 Controller 打标签注入问题

需要把 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 的话是不需要的,具体原因可以参考拦截器那里有说明。