目录
- Fireasy3 揭秘 — 依赖注入与服务发现
- Fireasy3 揭秘 — 自动服务部署
- Fireasy3 揭秘 — 使用 SourceGeneraor 改进服务发现
- Fireasy3 揭秘 — 使用 SourceGeneraor 实现动态代理(AOP)
- Fireasy3 揭秘 — 使用 Emit 构建程序集
- Fireasy3 揭秘 — 代码编译器及适配器
- Fireasy3 揭秘 — 使用缓存提高反射性能
- Fireasy3 揭秘 — 动态类型及扩展支持
- Fireasy3 揭秘 — 线程数据共享的实现
- Fireasy3 揭秘 — 配置管理及解析处理
- Fireasy3 揭秘 — 数据库适配器
- Fireasy3 揭秘 — 解决数据库之间的语法差异
- Fireasy3 揭秘 — 获取数据库的架构信息
- Fireasy3 揭秘 — 数据批量插入的实现
- Fireasy3 揭秘 — 使用包装器对数据读取进行兼容
- Fireasy3 揭秘 — 数据行映射器
- Fireasy3 揭秘 — 数据转换器的实现
- Fireasy3 揭秘 — 通用序列生成器和雪花生成器的实现
- Fireasy3 揭秘 — 命令拦截器的实现
- Fireasy3 揭秘 — 数据库主从同步的实现
- Fireasy3 揭秘 — 大数据分页的策略
- Fireasy3 揭秘 — 数据按需更新及生成实体代理类
- Fireasy3 揭秘 — 用对象池技术管理上下文
- Fireasy3 揭秘 — Lambda 表达式解析的原理
- Fireasy3 揭秘 — 扩展选择的实现
- Fireasy3 揭秘 — 按需加载与惰性加载的区别与实现
- Fireasy3 揭秘 — 自定义函数的解析与绑定
- Fireasy3 揭秘 — 与 MongoDB 进行适配
- Fireasy3 揭秘 — 模块化的实现原理
实现 AOP(面向切面编程)的方式有很多种,但无外乎静态纺织和动态编织两种。
动态编织 在 Fireasy 2 中使用 Emit
实现了动态编织。Emit 的缺点很明显,首先就是要求对 IL
语言必须比较熟悉且考虑较全面,其次由于是动态编织,需要使用内存缓存来对代理类进行管理。
静态编织 是指在代码编译时就将拦截器相关的代码插入到代码内部,运行时不耗用时间和内存空间。常用的有 MSBuild
(如PostSharp) 和 Code Analyzers
(本文中用到的 ISourceGenerator
)。
首先,定义拦截器的接口,Initialize/InitializeAsync
方法用于首次初始化,Intercept/InterceptAsync
用于拦截方法的执行和属性的访问。如下:
/// <summary>
/// 提供对类成员进行拦截的方法。
/// </summary>
public interface IInterceptor
{
/// <summary>
/// 使用上下文对象对当前的拦截器进行初始化。
/// </summary>
/// <param name="context">包含拦截定义的上下文。</param>
void Initialize(InterceptContext context);
/// <summary>
/// 将自定义方法注入到当前的拦截点。
/// </summary>
/// <param name="info">拦截调用信息。</param>
void Intercept(InterceptCallInfo info);
}
/// <summary>
/// 提供对类成员进行拦截的异步方法。
/// </summary>
public interface IAsyncInterceptor
{
/// <summary>
/// 使用上下文对象对当前的拦截器进行初始化。
/// </summary>
/// <param name="context">包含拦截定义的上下文。</param>
ValueTask InitializeAsync(InterceptContext context);
/// <summary>
/// 将自定义方法注入到当前的拦截点。
/// </summary>
/// <param name="info">拦截调用信息。</param>
ValueTask InterceptAsync(InterceptCallInfo info);
}
InterceptCallInfo
是拦截的调用信息,其中,Arguments 属性是调用的入参,ReturnValue 属性是返回的值。如下:
/// <summary>
/// 用于通知客户端的拦截信息。无法继承此类。
/// </summary>
public sealed class InterceptCallInfo
{
/// <summary>
/// 获取或设置定义的类型。
/// </summary>
public Type? DefinedType { get; set; }
/// <summary>
/// 获取或设置当前被拦截的方法或属性。
/// </summary>
public MemberInfo? Member { get; set; }
/// <summary>
/// 获取或设置方法的返回类型。
/// </summary>
public Type? ReturnType { get; set; }
/// <summary>
/// 获取或设置当前被拦截的目标对象。
/// </summary>
public object? Target { get; set; }
/// <summary>
/// 获取或设置拦截的类型。
/// </summary>
public InterceptType InterceptType { get; set; }
/// <summary>
/// 获取或设置方法的参数数组。
/// </summary>
public object[]? Arguments { get; set; }
/// <summary>
/// 获取或设置方法的返回值。
/// </summary>
public object? ReturnValue { get; set; }
/// <summary>
/// 获取或设置触发的异常信息。
/// </summary>
public Exception? Exception { get; set; }
/// <summary>
/// 获取或设置取消 Before 事件之后调用基类的方法。
/// </summary>
public bool Cancel { get; set; }
/// <summary>
/// 获取或设置是否中断后继拦截器的执行。
/// </summary>
public bool Break { get; set; }
}
最后定义一个特性 InterceptAttribute
用于指定类或方法上的拦截器类型,只需要在类、属性或方法上指定即可:
[InterceptAttribute(typeof(SampleInterceptor))]
接下来,我们定义好注入代码的原型,以便下一步生成代码。比如方法的切面示例:
public override string Test(string str)
{
//定义由 InterceptAttribute 指定的拦截器实例
var interceptors = new List<IInterceptor> { new SampleInterceptor() };
var info = new InterceptCallInfo();
//拦截的对象为当前对象
info.Target = this;
//方法的入参
info.Arguments = new object[] { str };
//当前拦截的成员(方法)
info.Member = ((MethodInfo)MethodBase.GetCurrentMethod()).GetBaseDefinition();
try
{
//初始化
_Initialize(interceptors, info);
//通知拦截器方法即将被调用
_Intercept(interceptors, info, InterceptType.BeforeMethodCall);
//如果拦截器告知要取消执行
if (info.Cancel)
{
//返回值,如果方法为 void,那么直接 return
//在拦截器中可以给 ReturnValue 进行赋值,然后 Cancel = true
return info.ReturnValue == null ? default : (string)info.ReturnValue;
}
//调用父方法
info.ReturnValue = base.Test(str);
//通知拦截器方法已调用完成
_Intercept(interceptors, info, InterceptType.AfterMethodCall);
}
catch (System.Exception exp)
{
info.Exception = exp;
//通知拦截器,有异常抛出
_Intercept(interceptors, info, InterceptType.Catching);
throw exp;
}
finally
{
//任何时候,都会走到这一步
_Intercept(interceptors, info, InterceptType.Finally);
}
//返回值
return info.ReturnValue == null ? default : (string)info.ReturnValue;
}
属性的切面也差不多,只是对 get 和 set 分别处理。我们来看一下 _Initialize
方法要实现的目的:
private void _Initialize(List<IInterceptor> interceptors, InterceptCallInfo callInfo)
{
if (!this._initMarks.Contains(callInfo.Member))
{
for (int i = 0; i < interceptors.Count; i++)
{
InterceptContext context = new InterceptContext(callInfo.Member, this);
interceptors[i].Initialize(context);
}
this._initMarks.Add(callInfo.Member);
}
}
它的目的是让拦截器只调用一次 Initialize
方法。而 Intercept
方法用于通知拦截器进行拦截,在拦截器里给定 Break = true 时,后续的拦截器将不会被调用。
private void _Intercept(List<IInterceptor> interceptors, InterceptCallInfo callInfo, InterceptType interceptType)
{
callInfo.InterceptType = interceptType;
callInfo.Break = false;
for (int i = 0; i < interceptors.Count; i++)
{
if (callInfo.Break)
{
break;
}
interceptors[i].Intercept(callInfo);
}
}
接下来,我们用 ISourceGenretor
来实现代码的生成。
上篇 使用 SourceGeneraor 改进服务发现 已经讲解过 ISourceGenerator
的用法了,需要分别定义一个 ISyntaxContextReceiver
和 ISourceGenerator
的实现。
定义 DynamicProxySyntaxReceiver
类,用来接收语法节点,并进行分析,找出可以拦截的方法和属性,以及拦截器等内容。这里,我着重讲解一下 AnalyseClassSyntax
方法的实现,如下:
/// <summary>
/// 分析类型语法。
/// </summary>
/// <param name="model"></param>
/// <param name="syntax"></param>
private void AnalyseClassSyntax(SemanticModel model, ClassDeclarationSyntax syntax)
{
var typeSymbol = (ITypeSymbol)model.GetDeclaredSymbol(syntax)!;
if (typeSymbol.IsSealed)
{
return;
}
var interceptorMetadataOfClass = FindInterceptorMetadata(typeSymbol);
var metadata = new ClassMetadata(typeSymbol);
//获取所有成员
foreach (var memberSymbol in typeSymbol.GetMembers())
{
//如果不是方法或属性
if (memberSymbol.Kind != SymbolKind.Method && memberSymbol.Kind != SymbolKind.Property)
{
continue;
}
if (memberSymbol is IMethodSymbol method)
{
//构造器需要重载,所以也要记录下来
if (method.MethodKind == MethodKind.Constructor)
{
metadata.AddConstructor(method);
continue;
}
if (method.MethodKind != MethodKind.Ordinary)
{
continue;
}
}
//方法定义为 virtual 并且是公共的
if (!memberSymbol.IsVirtual || memberSymbol.DeclaredAccessibility != Accessibility.Public)
{
continue;
}
//查找方法上的 InterceptAttribute 特性
if (memberSymbol.GetAttributes().Any(s => s.AttributeClass!.ToDisplayString() == InterceptorAttributeName))
{
var interceptorMetadataOfMember = FindInterceptorMetadata(memberSymbol);
if (interceptorMetadataOfMember != null)
{
metadata.AddMember(memberSymbol, interceptorMetadataOfMember);
}
}
//没找到方法上的特性,则使用类上定义的 InterceptAttribute 特性
else if (interceptorMetadataOfClass != null)
{
var hasIgnoreThrowExpAttr = HasIgnoreThrowExceptionAttribute(memberSymbol);
metadata.AddMember(memberSymbol, interceptorMetadataOfClass.Clone(!hasIgnoreThrowExpAttr));
}
}
if (metadata.IsValid)
{
_metadata.Add(FindUsings(syntax, metadata));
}
}
FindInterceptorMetadata
方法用于查找在类或方法、属性上定义的 InterceptAttribute
特性,并记录下拦截器的类型。如下:
/// <summary>
/// 获取拦截器的元数据。
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
private InterceptorMetadata? FindInterceptorMetadata(ISymbol symbol)
{
var types = new List<ITypeSymbol>();
foreach (AttributeData classAttr in symbol.GetAttributes().Where(s => s.AttributeClass!.ToDisplayString() == InterceptorAttributeName))
{
var interceptorType = GetInterceptorType(classAttr.ConstructorArguments[0].Value);
if (interceptorType != null)
{
types.Add(interceptorType);
}
}
if (!types.Any())
{
return null;
}
var hasIgnoreThrowExpAttr = HasIgnoreThrowExceptionAttribute(symbol);
return new InterceptorMetadata(types, !hasIgnoreThrowExpAttr);
}
在 ClassMetadata
里,定义了可被拦截的方法、属性,以及构造器、引用的命名空间等信息,如下:
/// <summary>
/// 类的元数据。
/// </summary>
public class ClassMetadata
{
/// <summary>
/// 初始化 <see cref="ClassMetadata"/> 类的新实例。
/// </summary>
/// <param name="type">类型符号。</param>
public ClassMetadata(ITypeSymbol type)
{
Type = type;
}
/// <summary>
/// 获取类型符号。
/// </summary>
public ITypeSymbol Type { get; }
/// <summary>
/// 获取命名空间。
/// </summary>
public string Namespace => Type.ContainingNamespace.ToDisplayString();
/// <summary>
/// 获取类型的全名。
/// </summary>
public string TypeFullName => Type.ToDisplayString();
/// <summary>
/// 获取代理类的名称。
/// </summary>
public string ProxyTypeName => $"{Type.Name}_proxy_";
/// <summary>
/// 获取代理类的全名。
/// </summary>
public string ProxyTypeFullName => $"{Namespace}.{ProxyTypeName}";
/// <summary>
/// 获取源代码名称。
/// </summary>
public string SourceCodeName => Type.ToDisplayString().Replace(".", "_") + ".cs";
/// <summary>
/// 获取构造函数列表。
/// </summary>
public List<IMethodSymbol> Constructors { get; } = new();
/// <summary>
/// 获取可拦截的方法。
/// </summary>
public Dictionary<IMethodSymbol, InterceptorMetadata> Methods { get; } = new();
/// <summary>
/// 获取可拦截的属性。
/// </summary>
public Dictionary<IPropertySymbol, InterceptorMetadata> Properties { get; } = new();
/// <summary>
/// 获取引用的命名空间列表。
/// </summary>
public List<string> Usings { get; } = new();
/// <summary>
/// 添加可拦截的成员。
/// </summary>
/// <param name="symbol"></param>
/// <param name="metadata"></param>
public void AddMember(ISymbol symbol, InterceptorMetadata metadata)
{
if (symbol is IMethodSymbol method)
{
Methods.Add(method, metadata);
}
else if (symbol is IPropertySymbol property && property.Parameters.Count() == 0) //忽略索引器
{
Properties.Add(property, metadata);
}
}
/// <summary>
/// 添加构造方法。
/// </summary>
/// <param name="symbol"></param>
public void AddConstructor(IMethodSymbol symbol)
{
Constructors.Add(symbol);
}
/// <summary>
/// 添加引用的命名空间列表。
/// </summary>
/// <param name="usings"></param>
public void AddUsings(IEnumerable<string> usings)
{
Usings.AddRange(usings);
}
/// <summary>
/// 若有可拦截的方法或属性,则此元数据有效。
/// </summary>
public bool IsValid => Methods.Any() || Properties.Any();
}
接下来,在 DynamicProxyGenerator
类的 Execute
方法里,获取到以上记录到的元数据,分别创建 DynamicProxyClassBuilder
对象来生成代码,如下:
void ISourceGenerator.Execute(GeneratorExecutionContext context)
{
var mappers = new Dictionary<string, string>();
if (context.SyntaxContextReceiver is DynamicProxySyntaxReceiver receiver)
{
var metadatas = receiver.GetMetadatas();
metadatas.ForEach(s =>
{
context.AddSource(s.SourceCodeName, new DynamicProxyClassBuilder(s).BuildSource());
mappers.Add(s.TypeFullName, s.ProxyTypeFullName);
});
//代码生成完毕后,还需要在部署器中,将父类和代理类添加到 Container 容器中
if (mappers.Count > 0)
{
context.AddSource("DynamicProxyServicesDeployer.cs", BuildDeploySourceCode(mappers));
}
}
}
DynamicProxyClassBuilder
类的核心的实现,在这里,将对构造函数进行重载,对所有的方法、属性进行切面代码的生成。由于篇幅有限,这里只贴上类的构建方法,其他的对照原型进行参悟,也是比较容易理解的。如下:
/// <summary>
/// 生成源代码。
/// </summary>
/// <returns></returns>
public SourceText BuildSource()
{
var sb = new StringBuilder();
foreach (var u in _metadata.Usings)
{
sb.AppendLine(u.ToString());
}
sb.AppendLine("using System.Reflection;");
sb.AppendLine($@"
namespace {_metadata.Namespace}
{{
public class {_metadata.ProxyTypeName} : {_metadata.TypeFullName}, IDynamicProxyImplemented
{{
private List<System.Reflection.MemberInfo> _initMarks = new ();
{BuildConstructors()}
{BuildInitializeMethod()}
{BuildInterceptMethod()}
{BuildMethods()}
{BuildProperties()}
}}
}}");
return SourceText.From(sb.ToString(), Encoding.UTF8);
}
那么同样的,项目编译后,会生成一个 __DynamicProxyServicesDeployer
的部署器,目的是往 Container
里添加代理映射。如下:
[assembly: ServicesDeployAttribute(typeof(__DynamicProxyNs.__DynamicProxyServicesDeployer))]
internal class __DynamicProxyServicesDeployer : IServicesDeployer
{
void IServicesDeployer.Configure(IServiceCollection services)
{
Container.TryAdd(typeof(DependencyInjectionTests.TestDynamicProxyClass), typeof(TestDynamicProxyClass_proxy_));
Container.TryAdd(typeof(DynamicProxyTests.TestProxy), typeof(TestProxy_proxy_));
Container.TryAdd(typeof(ObjectActivatorTests.TestServiceProxy2), typeof(TestServiceProxy2_proxy_));
}
}
单元测试就不再赘述了,可以去这里查看 动态代理单元测试 。
最后,奉上 Fireasy 3
的开源地址:https://gitee.com/faib920/fireasy3 ,欢迎大家前来捧场。
本文相关代码请参考:
https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common/DynamicProxy
https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common.Analyzers/DynamicProxy
https://gitee.com/faib920/fireasy3/tests/Fireasy.Common.Tests/DynamicProxyTests.cs
更多内容请移步官网 http://www.fireasy.cn 。