From 3c8800acba0b4ddd8d4bda856fec8e6714f38a59 Mon Sep 17 00:00:00 2001 From: MarvelTiter_yaoqinglin Date: Sun, 25 Aug 2024 20:53:59 +0800 Subject: [PATCH 1/3] aspect proxy class gen --- .editorconfig | 4 + .../AutoAopProxyClassGenerator.cs | 121 ++++++++++++++++++ .../DiagnosticDefinitions.cs | 36 ++++++ .../Properties/launchSettings.json | 8 ++ .../Attributes/AddAspectHandlerAttribute.cs | 34 +++++ .../Attributes/AspectInterceptorAttribute.cs | 12 -- .../AutoAopProxyServiceProviderFactory.cs | 1 + src/AutoAopProxyGenerator/IAspectHandler.cs | 10 ++ .../Models/ProxyContext.cs | 18 +++ src/AutoAopProxyGenerator/PipelineBuilder.cs | 86 +++++++++++++ src/AutoWasmApi.Roslyn/GeneratorHepers.cs | 8 +- src/AutoWasmApi.Roslyn/usings.cs | 3 +- .../AutoWasmApiGenerator.csproj | 4 +- .../Builder/MethodBuilder.cs | 27 +++- .../Extensions/MethodBuilderExtensions.cs | 39 ++++++ src/Generators.Shared/RoslynExtensions.cs | 32 ++++- src/TestProject1/AopGeneratorTest/LogAop.cs | 17 +++ .../AopGeneratorTest/PipeLineTest.cs | 64 +++++++++ src/TestProject1/AopGeneratorTest/User.cs | 108 ++++++++++++++++ src/TestProject1/Class1.cs | 20 --- src/TestProject1/TestProject1.csproj | 4 +- 21 files changed, 611 insertions(+), 45 deletions(-) create mode 100644 .editorconfig create mode 100644 src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs create mode 100644 src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs create mode 100644 src/AutoAopProxy.Roslyn/Properties/launchSettings.json create mode 100644 src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs delete mode 100644 src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs create mode 100644 src/AutoAopProxyGenerator/IAspectHandler.cs create mode 100644 src/AutoAopProxyGenerator/PipelineBuilder.cs create mode 100644 src/TestProject1/AopGeneratorTest/LogAop.cs create mode 100644 src/TestProject1/AopGeneratorTest/PipeLineTest.cs create mode 100644 src/TestProject1/AopGeneratorTest/User.cs delete mode 100644 src/TestProject1/Class1.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1650286 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.{cs,vb}] + +# IDE0130: 命名空间与文件夹结构不匹配 +dotnet_diagnostic.IDE0130.severity = none diff --git a/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs new file mode 100644 index 0000000..8b3423c --- /dev/null +++ b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs @@ -0,0 +1,121 @@ +using Generators.Shared; +using Generators.Shared.Builder; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AutoAopProxyGenerator; + +[Generator(LanguageNames.CSharp)] +public class AutoAopProxyClassGenerator : IIncrementalGenerator +{ + public const string Aspectable = "AutoAopProxyGenerator.GenAspectProxyAttribute"; + public const string AspectHandler = "AutoAopProxyGenerator.AddAspectHandlerAttribute"; + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var source = context.SyntaxProvider.ForAttributeWithMetadataName( + Aspectable + , static (node, _) => node is ClassDeclarationSyntax + , static (ctx, _) => ctx); + context.RegisterSourceOutput(source, static (context, source) => + { + var targetSymbol = (INamedTypeSymbol)source.TargetSymbol; + //INamedTypeSymbol[] needToCheckSymbol = [targetSymbol, .. targetSymbol.AllInterfaces]; + + + var allInterfaces = targetSymbol.AllInterfaces.Where(a => a.HasAttribute(AspectHandler)).ToArray(); + + #region 检查Attribute标注是否合法 + bool pass = true; + foreach (var i in allInterfaces) + { + if (!pass) + { + break; + } + var all = i.GetAttributes(AspectHandler); + foreach (var a in all) + { + var at = a.GetNamedValue("AspectType"); + if (at == null) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00001(source.TargetNode.GetLocation())); + pass = false; + break; + } + var att = (INamedTypeSymbol)at; + if (!att.HasInterface("AutoAopProxyGenerator.IAspectHandler")) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00002(source.TargetNode.GetLocation())); + pass = false; + break; + } + } + } + if (!pass) + { + return; + } + #endregion + + + #region 获取所有的AspectHandler + var allHandlers = allInterfaces.SelectMany(x => + { + var handlers = x.GetAttributes(AspectHandler).Select(a => + { + var at = a.GetNamedValue("AspectType"); + if (at == null) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00001(source.TargetNode.GetLocation())); + return null; + } + var att = (INamedTypeSymbol)at; + if (!att.HasInterface("AutoAopProxyGenerator.IAspectHandler")) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00002(source.TargetNode.GetLocation())); + return null; + } + return att; + }); + return handlers.Where(i => i != null).Cast(); + }).Distinct(EqualityComparer.Default).ToArray(); + #endregion + var file = CreateGeneratedProxyClassFile(targetSymbol, allInterfaces, allHandlers); + context.AddSource(file); + }); + } + + private static CodeFile? CreateGeneratedProxyClassFile(INamedTypeSymbol classSymbol, INamedTypeSymbol[] interfaces, INamedTypeSymbol[] allHandlers) + { + List methods = []; + // 构造函数 + + // 接口方法 + foreach (var iface in interfaces) + { + methods.AddRange(CreateProxyMethod(iface)); + } + + var ctor = ConstructorBuilder.Default.MethodName($"{classSymbol.FormatClassName()}GeneratedProxy"); + + var proxyClass = ClassBuilder.Default.ClassName($"{classSymbol.FormatClassName()}GeneratedProxy"); + + return CodeFile.New($"{classSymbol.FormatFileName()}GeneratedProxyClass.g.cs") + .AddMembers(NamespaceBuilder.Default.Namespace(classSymbol.ContainingNamespace.ToDisplayString()).AddMembers(proxyClass)); + } + + private static IEnumerable CreateProxyMethod(INamedTypeSymbol iface) + { + var handlers = iface.GetAttributes(AspectHandler); + + foreach (var m in iface.GetMethods()) + { + + } + yield break; + } +} diff --git a/src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs b/src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs new file mode 100644 index 0000000..9410d4b --- /dev/null +++ b/src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs @@ -0,0 +1,36 @@ +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Text; + +namespace AutoAopProxyGenerator +{ + internal class DiagnosticDefinitions + { + /// + /// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null + /// + /// + /// + public static Diagnostic AAPG00001(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( + id: "AAPG00001", + title: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null", + messageFormat: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null", + category: typeof(AutoAopProxyClassGenerator).FullName!, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true), location); + + /// + /// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler + /// + /// + /// + public static Diagnostic AAPG00002(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( + id: "AAPG00002", + title: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler", + messageFormat: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler", + category: typeof(AutoAopProxyClassGenerator).FullName!, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true), location); + } +} diff --git a/src/AutoAopProxy.Roslyn/Properties/launchSettings.json b/src/AutoAopProxy.Roslyn/Properties/launchSettings.json new file mode 100644 index 0000000..dadeb32 --- /dev/null +++ b/src/AutoAopProxy.Roslyn/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "aop": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\TestProject1\\TestProject1.csproj" + } + } +} \ No newline at end of file diff --git a/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs b/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs new file mode 100644 index 0000000..cb0235c --- /dev/null +++ b/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoAopProxyGenerator; + +/// +/// 标记需要生成代理类的类型 +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class GenAspectProxyAttribute : Attribute +{ + +} + +/// +/// 在类或者接口上配置切面处理 +/// +[AttributeUsage(AttributeTargets.Interface)] +public sealed class AddAspectHandlerAttribute : Attribute +{ + public Type? AspectType { get; set; } +} + +/// +/// 配置不需要切面的方法 +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class IgnoreAspectAttribute : Attribute +{ + +} diff --git a/src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs b/src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs deleted file mode 100644 index 5e751b3..0000000 --- a/src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace AutoAopProxyGenerator -{ - public abstract class AspectInterceptorAttribute : Attribute - { - public abstract Task Invoke(ProxyContext context, Func process); - } -} diff --git a/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs b/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs index 70f8760..fe07cab 100644 --- a/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs +++ b/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs @@ -18,6 +18,7 @@ public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilde foreach (var service in containerBuilder) { //ServiceDescriptor. + //serviceCollection.AddKeyedScoped } return serviceCollection.BuildServiceProvider(); } diff --git a/src/AutoAopProxyGenerator/IAspectHandler.cs b/src/AutoAopProxyGenerator/IAspectHandler.cs new file mode 100644 index 0000000..8b1d534 --- /dev/null +++ b/src/AutoAopProxyGenerator/IAspectHandler.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace AutoAopProxyGenerator; + +public interface IAspectHandler +{ + Task Invoke(ProxyContext context, Func process); +} + diff --git a/src/AutoAopProxyGenerator/Models/ProxyContext.cs b/src/AutoAopProxyGenerator/Models/ProxyContext.cs index bfe9adf..a8a26ca 100644 --- a/src/AutoAopProxyGenerator/Models/ProxyContext.cs +++ b/src/AutoAopProxyGenerator/Models/ProxyContext.cs @@ -1,10 +1,28 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Text; namespace AutoAopProxyGenerator { public class ProxyContext { + public ProxyContext(IServiceProvider services) + { + Services = services; + } + public ProxyContext() + { + + } + public bool Executed { get; set; } + public bool HasReturnValue { get; set; } + public object? ReturnValue { get; set; } + public object?[] Parameters { get; set; } = []; + public IServiceProvider? Services { get; } + public Type? ServiceType { get; set; } + public Type? ImplementType { get; set; } + public MethodInfo? ServiceMethod { get; set; } + public MethodInfo? ImplementMethod { get; set; } } } diff --git a/src/AutoAopProxyGenerator/PipelineBuilder.cs b/src/AutoAopProxyGenerator/PipelineBuilder.cs new file mode 100644 index 0000000..99aa62d --- /dev/null +++ b/src/AutoAopProxyGenerator/PipelineBuilder.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System; +using System.Linq; + +namespace AutoAopProxyGenerator; + +public class PipelineBuilder +{ + private readonly Action completeAction; + private readonly IList, Action>> pipelines = []; + public static PipelineBuilder Create(Action completeAction) => new(completeAction); + public PipelineBuilder(Action completeAction) + { + this.completeAction = completeAction; + } + public PipelineBuilder Use(Func, Action> middleware) + { + pipelines.Add(middleware); + return this; + } + public Action Build() + { + var request = completeAction; + foreach (var pipeline in pipelines.Reverse()) + { + request = pipeline(request); + } + return request; + } +} + +public class AsyncPipelineBuilder +{ + private readonly Func completeFunc; + private readonly IList, Func>> pipelines = []; + public static AsyncPipelineBuilder Create(Func completeFunc) => new(completeFunc); + public AsyncPipelineBuilder(Func completeFunc) + { + this.completeFunc = completeFunc; + } + + public AsyncPipelineBuilder Use(Func, Func> middleware) + { + pipelines.Add(middleware); + return this; + } + + + public Func Build() + { + var request = completeFunc; + foreach (var pipeline in pipelines.Reverse()) + { + request = pipeline(request); + } + return request; + } +} + +public static class PipeLineBuilderExtensions +{ + public static PipelineBuilder Use(this PipelineBuilder builder, Action action) + { + builder.Use(next => + { + return context => + { + action(context, () => next(context)); + }; + }); + return builder; + } + + public static AsyncPipelineBuilder Use(this AsyncPipelineBuilder builder, Func, Task> asyncaction) + { + builder.Use(next => + { + return context => + { + return asyncaction(context, () => next(context)); + }; + }); + return builder; + } +} diff --git a/src/AutoWasmApi.Roslyn/GeneratorHepers.cs b/src/AutoWasmApi.Roslyn/GeneratorHepers.cs index ed4fa1d..0414f24 100644 --- a/src/AutoWasmApi.Roslyn/GeneratorHepers.cs +++ b/src/AutoWasmApi.Roslyn/GeneratorHepers.cs @@ -57,13 +57,13 @@ public static IEnumerable GetAllMethods(this INamedTypeSymbol? sy } - public static CompilationUnitSyntax CreateCompilationUnit(this GeneratorAttributeSyntaxContext source) - { + //public static CompilationUnitSyntax CreateCompilationUnit(this GeneratorAttributeSyntaxContext source) + //{ //CompilationUnit() // .AddMembers(NamespaceDeclaration()) - return CompilationUnit(); - } + //return CompilationUnit(); + //} //public static NamespaceDeclarationSyntax CreateNamespaceDeclaration(this GeneratorAttributeSyntaxContext source, out UsingDirectiveSyntax[] usings) //{ diff --git a/src/AutoWasmApi.Roslyn/usings.cs b/src/AutoWasmApi.Roslyn/usings.cs index fe15e32..5764cf7 100644 --- a/src/AutoWasmApi.Roslyn/usings.cs +++ b/src/AutoWasmApi.Roslyn/usings.cs @@ -1,4 +1,3 @@ -global using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly:InternalsVisibleTo("TestProject1")] \ No newline at end of file diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj index 137532a..ded5bd7 100644 --- a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj +++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj @@ -15,7 +15,7 @@ - + @@ -24,7 +24,7 @@ - + diff --git a/src/Generators.Shared/Builder/MethodBuilder.cs b/src/Generators.Shared/Builder/MethodBuilder.cs index be018a0..d3cad9d 100644 --- a/src/Generators.Shared/Builder/MethodBuilder.cs +++ b/src/Generators.Shared/Builder/MethodBuilder.cs @@ -12,8 +12,8 @@ public MethodBuilder() Modifiers = "public"; } public override NodeType Type => NodeType.Method; - public bool IsAsync { get; set; } public bool IsLambdaBody { get; set; } + public bool IsAsync { get; set; } string Async => IsAsync ? " async " : " "; public string? ReturnType { get; set; } = "void"; @@ -191,3 +191,28 @@ public override string ToString() """; } } + +internal class LocalFunction : Statement +{ + public LocalFunction() : base("") + { + + } + public static LocalFunction Default => new LocalFunction(); + public string? ReturnType { get; set; } + public string? Name { get; set; } + public bool IsAsync { get; set; } + string Async => IsAsync ? "async " : ""; + public List Parameters { get; set; } = []; + public List Body { get; set; } = []; + public override string ToString() + { + return +$$""" +{{Indent}}{{Async}}{{Name}}({{string.Join(", ", Parameters)}}) +{ +{{string.Join("\n", Body.Select(s => $" {s}"))}} +} +"""; + } +} diff --git a/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs b/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs index f0a197f..1bb713b 100644 --- a/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs +++ b/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs @@ -126,4 +126,43 @@ public static IfStatement AddStatement(this IfStatement ifStatement, params stri return ifStatement; } #endregion + + #region LocalFunction + + public static T AddLocalFunction(this T builder, Action action) where T : MethodBase + { + var lf = LocalFunction.Default; + action.Invoke(lf); + builder.AddBody(lf); + return builder; + } + + public static LocalFunction MethodName(this LocalFunction localFunction, string name) + { + localFunction.Name = name; + return localFunction; + } + + public static LocalFunction Return(this LocalFunction localFunction, string returnType) + { + localFunction.ReturnType = returnType; + return localFunction; + } + + public static LocalFunction AddParameters(this LocalFunction localFunction, params string[] parameters) + { + foreach(var parameter in parameters) + { + localFunction.Parameters.Add(parameter); + } + return localFunction; + } + + public static LocalFunction AddBody(this LocalFunction localFunction, params Statement[] body) + { + localFunction.Body.AddRange(body); + return localFunction; + } + + #endregion } diff --git a/src/Generators.Shared/RoslynExtensions.cs b/src/Generators.Shared/RoslynExtensions.cs index 4ed10f4..ef64af1 100644 --- a/src/Generators.Shared/RoslynExtensions.cs +++ b/src/Generators.Shared/RoslynExtensions.cs @@ -36,7 +36,13 @@ public static bool GetNamedValue(this AttributeData? a, string key, out object? value = t; return t != null; } - + /// + /// 获取指定索引的构造函数参数 + /// + /// + /// + /// + /// public static bool GetConstructorValue(this AttributeData a, int index, out object? value) { if (a.ConstructorArguments.Length <= index) @@ -47,7 +53,13 @@ public static bool GetConstructorValue(this AttributeData a, int index, out obje value = a.ConstructorArguments[index].Value; return true; } - + /// + /// 获取指定索引的构造函数参数 + /// + /// + /// + /// + /// public static bool GetConstructorValues(this AttributeData a, int index, out object?[] values) { if (a.ConstructorArguments.Length <= index) @@ -58,7 +70,6 @@ public static bool GetConstructorValues(this AttributeData a, int index, out obj values = a.ConstructorArguments[index].Values.Select(v => v.Value).ToArray(); return true; } - /// /// 根据名称获取attribute的值 /// @@ -109,6 +120,21 @@ public static bool HasInterfaceAll(this ITypeSymbol? symbol, string fullName) { return symbol?.AllInterfaces.Any(i => i.ToDisplayString() == fullName) == true; } + /// + /// 获取方法符号 + /// + /// + /// + public static IEnumerable GetMethods(this INamedTypeSymbol? symbol) + { + foreach(var item in symbol?.GetMembers() ?? []) + { + if (item.Kind == SymbolKind.Method && item is IMethodSymbol method) + { + yield return method; + } + } + } public static bool CheckDisableGenerator(this AnalyzerConfigOptionsProvider options, string key) { diff --git a/src/TestProject1/AopGeneratorTest/LogAop.cs b/src/TestProject1/AopGeneratorTest/LogAop.cs new file mode 100644 index 0000000..8a9de7f --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/LogAop.cs @@ -0,0 +1,17 @@ +using AutoAopProxyGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + internal class LogAop : IAspectHandler + { + public Task Invoke(ProxyContext context, Func process) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/TestProject1/AopGeneratorTest/PipeLineTest.cs b/src/TestProject1/AopGeneratorTest/PipeLineTest.cs new file mode 100644 index 0000000..b652404 --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/PipeLineTest.cs @@ -0,0 +1,64 @@ +using AutoAopProxyGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + [TestClass] + public class PipeLineTest + { + [TestMethod] + public void Test() + { + var builder = new PipelineBuilder(s => { Console.WriteLine("job done"); }); + builder.Use(next => + { + return s => + { + Console.WriteLine("job 1 before"); + next(s); + Console.WriteLine("job 1 after"); + }; + }).Use((context, next) => + { + next(); + }); + + var job = builder.Build(); + job.Invoke("hello"); + } + + public Task Done(ProxyContext ctx) + { + Console.WriteLine($"Invoke Actual Method Result: {ctx.Executed}"); + return Task.CompletedTask; + } + + public class LogAop + { + public Task Invoke(ProxyContext ctx, Func next) + { + Console.WriteLine("LogAop Invoke"); + return next(); + } + } + + [TestMethod] + public async Task AsyncTest() + { + var log = new LogAop(); + var builder = AsyncPipelineBuilder.Create(ctx => + { + ctx.Executed = true; + return Done(ctx); + }); + builder.Use(log.Invoke); + var job = builder.Build(); + ProxyContext context = new ProxyContext(); + await job.Invoke(context); + } + } +} diff --git a/src/TestProject1/AopGeneratorTest/User.cs b/src/TestProject1/AopGeneratorTest/User.cs new file mode 100644 index 0000000..63c8f28 --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/User.cs @@ -0,0 +1,108 @@ +using AutoAopProxyGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + [AddAspectHandler(AspectType = typeof(LogAop))] + public interface IHello + { + void Hello(); + Task HelloAsync(); + int Count(); + Task CountAsync(); + } + + internal class User : IHello + { + public void Hello() + { + Console.WriteLine("Hello world"); + } + + public Task HelloAsync() + { + throw new NotImplementedException(); + } + public Task CountAsync() + { + return Task.FromResult(0); + } + + public int Count() + { + throw new NotImplementedException(); + } + } + + [GenAspectProxy] + //[AddAspectHandler(AspectType = typeof(LogAop))] + internal class UserProxy : IHello + { + private readonly User proxy; + public UserProxy(User proxy) + { + this.proxy = proxy; + } + + public void Hello() + { + Func done = ctx => + { + proxy.Hello(); + ctx.Executed = true; + return Task.CompletedTask; + }; + var builder = AsyncPipelineBuilder.Create(done); + var job = builder.Build(); + job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); + } + + public Task HelloAsync() + { + Func done = async ctx => + { + await proxy.HelloAsync(); + ctx.Executed = true; + }; + var builder = AsyncPipelineBuilder.Create(done); + var job = builder.Build(); + return job.Invoke(new ProxyContext()); + } + + public int Count() + { + int returnValue = default; + Func done = ctx => + { + returnValue = proxy.Count(); + ctx.ReturnValue = returnValue; + ctx.Executed = true; + return Task.CompletedTask; + }; + var builder = AsyncPipelineBuilder.Create(done); + var job = builder.Build(); + job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); + return returnValue; + } + + public async Task CountAsync() + { + int returnValue = default; + async Task done(ProxyContext ctx) + { + returnValue = await proxy.CountAsync(); + ctx.ReturnValue = returnValue; + ctx.Executed = true; + } + var builder = AsyncPipelineBuilder.Create(done); + var job = builder.Build(); + await job.Invoke(new ProxyContext()); + return returnValue; + } + + } +} diff --git a/src/TestProject1/Class1.cs b/src/TestProject1/Class1.cs deleted file mode 100644 index c58bde4..0000000 --- a/src/TestProject1/Class1.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TestProject1 -{ - internal interface ITest - { - void Log(string message); - } - internal class Class1 : ITest - { - public void Log(string message) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/TestProject1/TestProject1.csproj b/src/TestProject1/TestProject1.csproj index 9a372d8..e1e9b10 100644 --- a/src/TestProject1/TestProject1.csproj +++ b/src/TestProject1/TestProject1.csproj @@ -17,7 +17,9 @@ - + + + From 437c20a2f1ebe6233adb4451dcdee96e5adfb69d Mon Sep 17 00:00:00 2001 From: MarvelTiter_yaoqinglin Date: Mon, 26 Aug 2024 23:32:28 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E7=94=9F=E6=88=90=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AutoAopProxyClassGenerator.cs | 122 +++++++++++-- src/AutoAopProxyGenerator/IAspectHandler.cs | 24 +++ .../Models/ProxyContext.cs | 2 +- .../Builder/MethodBuilder.cs | 8 +- src/Generators.Shared/Builder/TypeBuilder.cs | 2 +- .../Extensions/MethodBuilderExtensions.cs | 10 +- src/TestProject1/AopGeneratorTest/LogAop.cs | 4 +- .../AopGeneratorTest/ProxyTest.cs | 24 +++ src/TestProject1/AopGeneratorTest/User.cs | 168 ++++++++++++------ 9 files changed, 288 insertions(+), 76 deletions(-) create mode 100644 src/TestProject1/AopGeneratorTest/ProxyTest.cs diff --git a/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs index 8b3423c..683aef0 100644 --- a/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs +++ b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs @@ -61,7 +61,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } #endregion - #region 获取所有的AspectHandler var allHandlers = allInterfaces.SelectMany(x => { @@ -84,38 +83,139 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return handlers.Where(i => i != null).Cast(); }).Distinct(EqualityComparer.Default).ToArray(); #endregion + var file = CreateGeneratedProxyClassFile(targetSymbol, allInterfaces, allHandlers); - context.AddSource(file); + if (file != null) + { + //var ss = file.ToString(); + context.AddSource(file); + } }); } private static CodeFile? CreateGeneratedProxyClassFile(INamedTypeSymbol classSymbol, INamedTypeSymbol[] interfaces, INamedTypeSymbol[] allHandlers) { - List methods = []; + // 代理字段和aspect handler 字段 + List members = [ + FieldBuilder.Default.MemberType(classSymbol.ToDisplayString()).FieldName("proxy") + , .. allHandlers.Select(n =>FieldBuilder.Default.MemberType(n.ToDisplayString()).FieldName(n.MetadataName)) + ]; // 构造函数 - + List ctorbody = [ + "this.proxy = proxy" + , ..allHandlers.Select(n => $"this.{n.MetadataName} = {n.MetadataName}") + ]; + var ctor = ConstructorBuilder.Default.MethodName($"{classSymbol.FormatClassName()}GeneratedProxy") + .AddParameter([$"{classSymbol.ToDisplayString()} proxy", .. allHandlers.Select(n => $"{n.ToDisplayString()} {n.MetadataName}")]).AddBody([.. ctorbody]); + members.Add(ctor); // 接口方法 foreach (var iface in interfaces) { - methods.AddRange(CreateProxyMethod(iface)); + members.AddRange(CreateProxyMethod(iface, classSymbol)); } - var ctor = ConstructorBuilder.Default.MethodName($"{classSymbol.FormatClassName()}GeneratedProxy"); - - var proxyClass = ClassBuilder.Default.ClassName($"{classSymbol.FormatClassName()}GeneratedProxy"); + var proxyClass = ClassBuilder.Default + .ClassName($"{classSymbol.FormatClassName()}GeneratedProxy") + .AddMembers([.. members]) + .Interface([.. interfaces.Select(i => i.ToDisplayString())]) + .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); return CodeFile.New($"{classSymbol.FormatFileName()}GeneratedProxyClass.g.cs") + .AddUsings("using AutoAopProxyGenerator;") .AddMembers(NamespaceBuilder.Default.Namespace(classSymbol.ContainingNamespace.ToDisplayString()).AddMembers(proxyClass)); } - private static IEnumerable CreateProxyMethod(INamedTypeSymbol iface) + private static IEnumerable CreateProxyMethod(INamedTypeSymbol iface, INamedTypeSymbol classSymbol) { - var handlers = iface.GetAttributes(AspectHandler); + var handlers = iface.GetAttributes(AspectHandler).Select(a => a.GetNamedValue("AspectType")).OfType().ToArray(); foreach (var m in iface.GetMethods()) { + var method = m.IsGenericMethod ? m.ConstructedFrom : m; + var returnType = method.ReturnType.GetGenericTypes().FirstOrDefault() ?? method.ReturnType; + var isAsync = method.IsAsync || method.ReturnType.ToDisplayString().StartsWith("System.Threading.Tasks.Task"); + var builder = MethodBuilder.Default + .MethodName(method.Name) + .Async(isAsync) + .Generic([.. method.GetTypeParameters()]) + .AddParameter([.. method.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) + .ReturnType(method.ReturnType.ToDisplayString()) + .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); + List statements = []; + var hasReturn = !method.ReturnsVoid && returnType.ToDisplayString() != "System.Threading.Tasks.Task"; + if (hasReturn) + { + statements.Add($"{returnType.ToDisplayString()} returnValue = default;"); + } + var done = LocalFunction.Default + .MethodName("Done") + .AddParameters("ProxyContext ctx") + .Async(isAsync) + .Return("System.Threading.Tasks.Task") + .AddBody([.. CreateLocalFunctionBody(method, builder.ConstructedMethodName, isAsync, hasReturn)]); + + statements.Add(done); + statements.Add("var builder = AsyncPipelineBuilder.Create(Done)"); + foreach (var handler in handlers) + { + statements.Add($"builder.Use({handler.MetadataName}.Invoke)"); + } + statements.Add("var job = builder.Build()"); + var ptypes = method.Parameters.Length > 0 ? $"[{string.Join(", ", method.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))}]" : "Type.EmptyTypes"; + statements.Add($"var context = ContextHelper.GetOrCreate(typeof({iface.ToDisplayString()}), typeof({classSymbol.ToDisplayString()}), nameof({method.Name}), {ptypes})"); + if (hasReturn) + { + statements.Add("context.HasReturnValue = true"); + } + statements.Add($"context.Parameters = new object?[] {{{string.Join(", ", method.Parameters.Select(p => p.Name))}}};"); + if (isAsync) + { + statements.Add("await job.Invoke(context)"); + } + else + { + statements.Add("job.Invoke(context).GetAwaiter().GetResult()"); + } + if (hasReturn) + statements.Add("return returnValue"); + builder.AddBody([.. statements]); + + yield return builder; + } + } + + private static IEnumerable CreateLocalFunctionBody(IMethodSymbol method, string proxyName, bool isAsync, bool hasReturn) + { + if (isAsync) + { + if (hasReturn) + { + yield return $"returnValue = await proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + yield return "ctx.ReturnValue = returnValue"; + } + else + { + yield return $"await proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + } + } + else + { + if (hasReturn) + { + yield return $"returnValue = proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + yield return "ctx.ReturnValue = returnValue"; + yield return "return global::System.Threading.Tasks.Task.CompletedTask"; + } + else + { + yield return $"proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + yield return "return global::System.Threading.Tasks.Task.CompletedTask"; + } } - yield break; } } diff --git a/src/AutoAopProxyGenerator/IAspectHandler.cs b/src/AutoAopProxyGenerator/IAspectHandler.cs index 8b1d534..26959cb 100644 --- a/src/AutoAopProxyGenerator/IAspectHandler.cs +++ b/src/AutoAopProxyGenerator/IAspectHandler.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Concurrent; +using System.Linq; using System.Threading.Tasks; namespace AutoAopProxyGenerator; @@ -8,3 +10,25 @@ public interface IAspectHandler Task Invoke(ProxyContext context, Func process); } +public class ContextHelper +{ + private static readonly ConcurrentDictionary caches = []; + public static ProxyContext GetOrCreate(Type @interface, Type impl, string methodName, Type[] types) + { + var key = $"{@interface.FullName}_{impl.FullName}_{methodName}_{string.Join("_", types.Select(t => t.Name))}"; + return caches.GetOrAdd(key, (k) => + { + var interfacesMethods = @interface.GetMethod(methodName, types); + var implMethod = impl.GetMethod(methodName, types); + return new ProxyContext() + { + ServiceType = @interface, + ImplementType = impl, + ServiceMethod = interfacesMethods, + ImplementMethod = implMethod + }; + }); + + } +} + diff --git a/src/AutoAopProxyGenerator/Models/ProxyContext.cs b/src/AutoAopProxyGenerator/Models/ProxyContext.cs index a8a26ca..2982d90 100644 --- a/src/AutoAopProxyGenerator/Models/ProxyContext.cs +++ b/src/AutoAopProxyGenerator/Models/ProxyContext.cs @@ -5,7 +5,7 @@ namespace AutoAopProxyGenerator { - public class ProxyContext + public record ProxyContext { public ProxyContext(IServiceProvider services) { diff --git a/src/Generators.Shared/Builder/MethodBuilder.cs b/src/Generators.Shared/Builder/MethodBuilder.cs index d3cad9d..8e437e5 100644 --- a/src/Generators.Shared/Builder/MethodBuilder.cs +++ b/src/Generators.Shared/Builder/MethodBuilder.cs @@ -16,7 +16,7 @@ public MethodBuilder() public bool IsAsync { get; set; } string Async => IsAsync ? " async " : " "; public string? ReturnType { get; set; } = "void"; - + public string ConstructedMethodName => $"{Name}{Types}"; public override string ToString() { if (IsLambdaBody) @@ -209,10 +209,10 @@ public override string ToString() { return $$""" -{{Indent}}{{Async}}{{Name}}({{string.Join(", ", Parameters)}}) -{ +{{Indent}}{{Async}}{{ReturnType}} {{Name}}({{string.Join(", ", Parameters)}}) +{{Indent}}{ {{string.Join("\n", Body.Select(s => $" {s}"))}} -} +{{Indent}}} """; } } diff --git a/src/Generators.Shared/Builder/TypeBuilder.cs b/src/Generators.Shared/Builder/TypeBuilder.cs index dfda864..2a2c114 100644 --- a/src/Generators.Shared/Builder/TypeBuilder.cs +++ b/src/Generators.Shared/Builder/TypeBuilder.cs @@ -36,7 +36,7 @@ protected string TypeConstraints get { if (TypeArguments.Count == 0) return ""; - return $"\n{string.Join("\n", TypeArguments.Select(ta => $"{Indent} where {ta.Name} : {string.Join(", ", ta.Constraints)}"))}"; + return $"\n{string.Join("\n", TypeArguments.Where(ta => ta.Constraints.Length > 0).Select(ta => $"{Indent} where {ta.Name} : {string.Join(", ", ta.Constraints)}"))}"; } } } diff --git a/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs b/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs index 1bb713b..cae8dd8 100644 --- a/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs +++ b/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs @@ -30,9 +30,9 @@ public static MethodBuilder ReturnType(this MethodBuilder builder, string return builder.ReturnType = returnType; return builder; } - public static MethodBuilder Async(this MethodBuilder builder) + public static MethodBuilder Async(this MethodBuilder builder, bool isAsync = true) { - builder.IsAsync = true; + builder.IsAsync = isAsync; return builder; } @@ -143,6 +143,12 @@ public static LocalFunction MethodName(this LocalFunction localFunction, string return localFunction; } + public static LocalFunction Async(this LocalFunction localFunction, bool isAsync = true) + { + localFunction.IsAsync = isAsync; + return localFunction; + } + public static LocalFunction Return(this LocalFunction localFunction, string returnType) { localFunction.ReturnType = returnType; diff --git a/src/TestProject1/AopGeneratorTest/LogAop.cs b/src/TestProject1/AopGeneratorTest/LogAop.cs index 8a9de7f..39ba420 100644 --- a/src/TestProject1/AopGeneratorTest/LogAop.cs +++ b/src/TestProject1/AopGeneratorTest/LogAop.cs @@ -7,11 +7,11 @@ namespace TestProject1.AopGeneratorTest { - internal class LogAop : IAspectHandler + public class LogAop : IAspectHandler { public Task Invoke(ProxyContext context, Func process) { - throw new NotImplementedException(); + return process.Invoke(); } } } diff --git a/src/TestProject1/AopGeneratorTest/ProxyTest.cs b/src/TestProject1/AopGeneratorTest/ProxyTest.cs new file mode 100644 index 0000000..bb31a4a --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/ProxyTest.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + [TestClass] + public class ProxyTest + { + [TestMethod] + public void TestReturn() + { + + } + + [TestMethod] + public Task TestReturnAsync() + { + return Task.FromResult(0); + } + } +} diff --git a/src/TestProject1/AopGeneratorTest/User.cs b/src/TestProject1/AopGeneratorTest/User.cs index 63c8f28..0ddaf5f 100644 --- a/src/TestProject1/AopGeneratorTest/User.cs +++ b/src/TestProject1/AopGeneratorTest/User.cs @@ -11,12 +11,20 @@ namespace TestProject1.AopGeneratorTest public interface IHello { void Hello(); + void Hello(string message); Task HelloAsync(); + Task HelloAsync(string message); int Count(); + int Count(string message); Task CountAsync(); + Task CountAsync(string message); + Task RunJobAsync(); + Task RunJobAsync(string message); + } - internal class User : IHello + [GenAspectProxy] + public class User : IHello { public void Hello() { @@ -29,80 +37,130 @@ public Task HelloAsync() } public Task CountAsync() { - return Task.FromResult(0); + return Task.FromResult(3); } public int Count() { - throw new NotImplementedException(); + return 2; } - } - [GenAspectProxy] - //[AddAspectHandler(AspectType = typeof(LogAop))] - internal class UserProxy : IHello - { - private readonly User proxy; - public UserProxy(User proxy) + public Task RunJobAsync() { - this.proxy = proxy; + throw new NotImplementedException(); } - public void Hello() + public void Hello(string message) { - Func done = ctx => - { - proxy.Hello(); - ctx.Executed = true; - return Task.CompletedTask; - }; - var builder = AsyncPipelineBuilder.Create(done); - var job = builder.Build(); - job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); + throw new NotImplementedException(); } - public Task HelloAsync() + public Task HelloAsync(string message) { - Func done = async ctx => - { - await proxy.HelloAsync(); - ctx.Executed = true; - }; - var builder = AsyncPipelineBuilder.Create(done); - var job = builder.Build(); - return job.Invoke(new ProxyContext()); + throw new NotImplementedException(); } - public int Count() + public int Count(string message) { - int returnValue = default; - Func done = ctx => - { - returnValue = proxy.Count(); - ctx.ReturnValue = returnValue; - ctx.Executed = true; - return Task.CompletedTask; - }; - var builder = AsyncPipelineBuilder.Create(done); - var job = builder.Build(); - job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); - return returnValue; + throw new NotImplementedException(); } - public async Task CountAsync() + public Task CountAsync(string message) { - int returnValue = default; - async Task done(ProxyContext ctx) - { - returnValue = await proxy.CountAsync(); - ctx.ReturnValue = returnValue; - ctx.Executed = true; - } - var builder = AsyncPipelineBuilder.Create(done); - var job = builder.Build(); - await job.Invoke(new ProxyContext()); - return returnValue; + throw new NotImplementedException(); } + public Task RunJobAsync(string message) + { + throw new NotImplementedException(); + } } + + //[GenAspectProxy] + ////[AddAspectHandler(AspectType = typeof(LogAop))] + //public class UserProxy : IHello + //{ + // private readonly User proxy; + // LogAop log; + // public UserProxy() + // { + // this.proxy = new User(); + // this.log = new LogAop(); + // } + + // public void Hello() + // { + // Task Done(ProxyContext ctx) + // { + // proxy.Hello(); + // ctx.Executed = true; + // return Task.CompletedTask; + // } + // var builder = AsyncPipelineBuilder.Create(Done); + // var job = builder.Build(); + // var context = ContextHelper.GetOrCreate(typeof(IHello), typeof(User), "Hello", Type.EmptyTypes); + + // job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); + // } + + // public Task HelloAsync() + // { + // async Task done(ProxyContext ctx) + // { + // await proxy.HelloAsync(); + // ctx.Executed = true; + // } + // var builder = AsyncPipelineBuilder.Create(done); + // var job = builder.Build(); + // return job.Invoke(new ProxyContext()); + // } + + // public int Count() + // { + // int returnValue = default; + // Task done(ProxyContext ctx) + // { + // returnValue = proxy.Count(); + // ctx.ReturnValue = returnValue; + // ctx.Executed = true; + // return global::System.Threading.Tasks.Task.CompletedTask; + // } + // var builder = AsyncPipelineBuilder.Create(done); + // builder.Use(log.Invoke); + // var job = builder.Build(); + // job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); + // return returnValue; + // } + + // public async Task CountAsync() + // { + // int returnValue = default; + // async Task done(ProxyContext ctx) + // { + // returnValue = await proxy.CountAsync(); + // ctx.ReturnValue = returnValue; + // ctx.Executed = true; + // } + // var builder = AsyncPipelineBuilder.Create(done); + // var job = builder.Build(); + // await job.Invoke(new ProxyContext()); + // return returnValue; + // } + + // public async Task RunJobAsync() + // { + // bool returnValue = default; + // async Task Done(ProxyContext ctx) + // { + // returnValue = await proxy.RunJobAsync(); + // ctx.ReturnValue = returnValue; + // ctx.Executed = true; + // } + // var builder = AsyncPipelineBuilder.Create(Done); + // var job = builder.Build(); + // await job.Invoke(new ProxyContext()); + // return returnValue; + // } + //} } + From d3fa5f5b7f2b07d767353a5907e406feb1c94216 Mon Sep 17 00:00:00 2001 From: MarvelTiter_yaoqinglin Date: Tue, 27 Aug 2024 16:09:14 +0800 Subject: [PATCH 3/3] =?UTF-8?q?aop=E4=BB=A3=E7=90=86=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push_nuget.yml | 7 ++ .../AutoAopProxyClassGenerator.cs | 114 +++++++++++------- .../Attributes/AddAspectHandlerAttribute.cs | 20 +-- .../Attributes/GenAspectProxyAttribute.cs | 12 ++ .../Attributes/IgnoreAspectAttribute.cs | 12 ++ .../AutoAopProxyServiceProviderFactory.cs | 93 +++++++++++++- src/AutoAopProxyGenerator/ContextHelper.cs | 30 +++++ src/AutoAopProxyGenerator/IAspectHandler.cs | 22 ---- src/AutoAopProxyGenerator/readme.md | 8 +- .../Blazor.Test.Client/Aop/ExceptionAop.cs | 25 ++++ .../Blazor.Test.Client/Aop/TestAop.cs | 20 +++ .../Blazor.Test.Client.csproj | 4 +- .../Blazor.Test.Client/Pages/Counter.razor | 9 +- .../Services/IHelloService.cs | 36 ++++++ .../Blazor.Test.Client/_Imports.razor | 1 + .../Blazor.Test/Components/App.razor | 4 +- src/Blazor.Test/Blazor.Test/Program.cs | 10 ++ src/TestProject1/AopGeneratorTest/LogAop.cs | 6 +- .../AopGeneratorTest/ProxyTest.cs | 17 ++- src/TestProject1/AopGeneratorTest/User.cs | 4 + 20 files changed, 354 insertions(+), 100 deletions(-) create mode 100644 src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs create mode 100644 src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs create mode 100644 src/AutoAopProxyGenerator/ContextHelper.cs create mode 100644 src/Blazor.Test/Blazor.Test.Client/Aop/ExceptionAop.cs create mode 100644 src/Blazor.Test/Blazor.Test.Client/Aop/TestAop.cs create mode 100644 src/Blazor.Test/Blazor.Test.Client/Services/IHelloService.cs diff --git a/.github/workflows/push_nuget.yml b/.github/workflows/push_nuget.yml index 1c1eeaf..ec9d1f2 100644 --- a/.github/workflows/push_nuget.yml +++ b/.github/workflows/push_nuget.yml @@ -49,5 +49,12 @@ jobs: - name: Push run: | dotnet nuget push 'publish/genmapper/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.GEN_MAPPER}} --skip-duplicate + # AutoAopProxyGenerator + - name: Pack + run: | + dotnet pack ./src/AutoAopProxyGenerator/AutoAopProxyGenerator.csproj -c Release -o publish/genaopproxy + - name: Push + run: | + dotnet nuget push 'publish/genaopproxy/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.GEN_AOPPROXY}} --skip-duplicate diff --git a/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs index 683aef0..03a1dea 100644 --- a/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs +++ b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs @@ -14,6 +14,7 @@ public class AutoAopProxyClassGenerator : IIncrementalGenerator { public const string Aspectable = "AutoAopProxyGenerator.GenAspectProxyAttribute"; public const string AspectHandler = "AutoAopProxyGenerator.AddAspectHandlerAttribute"; + public const string IgnoreAspect = "AutoAopProxyGenerator.IgnoreAspectAttribute"; public void Initialize(IncrementalGeneratorInitializationContext context) { var source = context.SyntaxProvider.ForAttributeWithMetadataName( @@ -131,57 +132,86 @@ private static IEnumerable CreateProxyMethod(INamedTypeSymbol ifa foreach (var m in iface.GetMethods()) { - var method = m.IsGenericMethod ? m.ConstructedFrom : m; - var returnType = method.ReturnType.GetGenericTypes().FirstOrDefault() ?? method.ReturnType; - var isAsync = method.IsAsync || method.ReturnType.ToDisplayString().StartsWith("System.Threading.Tasks.Task"); - var builder = MethodBuilder.Default - .MethodName(method.Name) - .Async(isAsync) - .Generic([.. method.GetTypeParameters()]) - .AddParameter([.. method.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) - .ReturnType(method.ReturnType.ToDisplayString()) - .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); - List statements = []; - var hasReturn = !method.ReturnsVoid && returnType.ToDisplayString() != "System.Threading.Tasks.Task"; - if (hasReturn) - { - statements.Add($"{returnType.ToDisplayString()} returnValue = default;"); - } - var done = LocalFunction.Default - .MethodName("Done") - .AddParameters("ProxyContext ctx") - .Async(isAsync) - .Return("System.Threading.Tasks.Task") - .AddBody([.. CreateLocalFunctionBody(method, builder.ConstructedMethodName, isAsync, hasReturn)]); - - statements.Add(done); - statements.Add("var builder = AsyncPipelineBuilder.Create(Done)"); - foreach (var handler in handlers) + MethodBuilder methodBuilder; + if (m.HasAttribute(IgnoreAspect)) { - statements.Add($"builder.Use({handler.MetadataName}.Invoke)"); + methodBuilder = CreateDirectInvokeMethod(); } - statements.Add("var job = builder.Build()"); - var ptypes = method.Parameters.Length > 0 ? $"[{string.Join(", ", method.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))}]" : "Type.EmptyTypes"; - statements.Add($"var context = ContextHelper.GetOrCreate(typeof({iface.ToDisplayString()}), typeof({classSymbol.ToDisplayString()}), nameof({method.Name}), {ptypes})"); - if (hasReturn) + else { - statements.Add("context.HasReturnValue = true"); + methodBuilder = CreateProxyMethod(); } - statements.Add($"context.Parameters = new object?[] {{{string.Join(", ", method.Parameters.Select(p => p.Name))}}};"); - if (isAsync) + + yield return methodBuilder; + + MethodBuilder CreateDirectInvokeMethod() { - statements.Add("await job.Invoke(context)"); + var builder = MethodBuilder.Default + .MethodName(m.Name) + .Generic([.. m.GetTypeParameters()]) + .AddParameter([.. m.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) + .ReturnType(m.ReturnType.ToDisplayString()) + .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); + builder = builder + .Lambda($"proxy.{builder.ConstructedMethodName}({string.Join(", ", m.Parameters.Select(p => p.Name))})"); + + return builder; } - else + + MethodBuilder CreateProxyMethod() { - statements.Add("job.Invoke(context).GetAwaiter().GetResult()"); - } - if (hasReturn) - statements.Add("return returnValue"); + var method = m.IsGenericMethod ? m.ConstructedFrom : m; + var returnType = method.ReturnType.GetGenericTypes().FirstOrDefault() ?? method.ReturnType; + var isAsync = method.IsAsync || method.ReturnType.ToDisplayString().StartsWith("System.Threading.Tasks.Task"); + var builder = MethodBuilder.Default + .MethodName(method.Name) + .Async(isAsync) + .Generic([.. method.GetTypeParameters()]) + .AddParameter([.. method.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) + .ReturnType(method.ReturnType.ToDisplayString()) + .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); + List statements = []; + var hasReturn = !method.ReturnsVoid && returnType.ToDisplayString() != "System.Threading.Tasks.Task"; + if (hasReturn) + { + statements.Add($"{returnType.ToDisplayString()} returnValue = default;"); + } + var done = LocalFunction.Default + .MethodName("Done") + .AddParameters("ProxyContext ctx") + .Async(isAsync) + .Return("System.Threading.Tasks.Task") + .AddBody([.. CreateLocalFunctionBody(method, builder.ConstructedMethodName, isAsync, hasReturn)]); + + statements.Add(done); + statements.Add("var builder = AsyncPipelineBuilder.Create(Done)"); + foreach (var handler in handlers) + { + statements.Add($"builder.Use({handler.MetadataName}.Invoke)"); + } + statements.Add("var job = builder.Build()"); + var ptypes = method.Parameters.Length > 0 ? $"[{string.Join(", ", method.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))}]" : "Type.EmptyTypes"; + statements.Add($"var context = ContextHelper<{iface.ToDisplayString()}, {classSymbol.ToDisplayString()}>.GetOrCreate(nameof({method.Name}), {ptypes})"); + if (hasReturn) + { + statements.Add("context.HasReturnValue = true"); + } + statements.Add($"context.Parameters = new object?[] {{{string.Join(", ", method.Parameters.Select(p => p.Name))}}};"); + if (isAsync) + { + statements.Add("await job.Invoke(context)"); + } + else + { + statements.Add("job.Invoke(context).GetAwaiter().GetResult()"); + } + if (hasReturn) + statements.Add("return returnValue"); - builder.AddBody([.. statements]); + builder.AddBody([.. statements]); - yield return builder; + return builder; + } } } diff --git a/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs b/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs index cb0235c..2a57860 100644 --- a/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs +++ b/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs @@ -6,29 +6,11 @@ namespace AutoAopProxyGenerator; -/// -/// 标记需要生成代理类的类型 -/// -[AttributeUsage(AttributeTargets.Class)] -public sealed class GenAspectProxyAttribute : Attribute -{ - -} - /// /// 在类或者接口上配置切面处理 /// -[AttributeUsage(AttributeTargets.Interface)] +[AttributeUsage(AttributeTargets.Interface, AllowMultiple = true)] public sealed class AddAspectHandlerAttribute : Attribute { public Type? AspectType { get; set; } } - -/// -/// 配置不需要切面的方法 -/// -[AttributeUsage(AttributeTargets.Method)] -public sealed class IgnoreAspectAttribute : Attribute -{ - -} diff --git a/src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs b/src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs new file mode 100644 index 0000000..f1f113b --- /dev/null +++ b/src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace AutoAopProxyGenerator; + +/// +/// 标记需要生成代理类的类型 +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class GenAspectProxyAttribute : Attribute +{ + +} diff --git a/src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs b/src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs new file mode 100644 index 0000000..9cf2343 --- /dev/null +++ b/src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace AutoAopProxyGenerator; + +/// +/// 配置不需要切面的方法 +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class IgnoreAspectAttribute : Attribute +{ + +} diff --git a/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs b/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs index fe07cab..13348b2 100644 --- a/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs +++ b/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.Reflection; using System.Text; namespace AutoAopProxyGenerator @@ -15,12 +16,98 @@ public IServiceCollection CreateBuilder(IServiceCollection services) public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) { IServiceCollection serviceCollection = new ServiceCollection(); - foreach (var service in containerBuilder) + foreach (var sd in containerBuilder) { - //ServiceDescriptor. - //serviceCollection.AddKeyedScoped + //service. + var implType = GetImplType(sd); + if (implType?.GetCustomAttribute() != null) + { + var proxyType = implType.Assembly.GetType($"{implType.FullName}GeneratedProxy"); + if (proxyType != null) + { + AddServiceDescriptors(serviceCollection, sd, implType, proxyType); + continue; + } + } + serviceCollection.Add(sd); } return serviceCollection.BuildServiceProvider(); + + static Type? GetImplType(ServiceDescriptor sd) + { +#if NET8_0_OR_GREATER + if (sd.IsKeyedService) + { + if (sd.KeyedImplementationType != null) + { + return sd.KeyedImplementationType; + } + else if (sd.KeyedImplementationInstance != null) + { + return sd.KeyedImplementationInstance.GetType(); + } + else if (sd.KeyedImplementationFactory != null) + { + var typeArguments = sd.KeyedImplementationFactory.GetType().GenericTypeArguments; + + return typeArguments[1]; + } + + return null; + } +#endif + if (sd.ImplementationType != null) + { + return sd.ImplementationType; + } + else if (sd.ImplementationInstance != null) + { + return sd.ImplementationInstance.GetType(); + } + else if (sd.ImplementationFactory != null) + { + var typeArguments = sd.ImplementationFactory.GetType().GenericTypeArguments; + + return typeArguments[1]; + } + + return null; + } + } + + + private static void AddServiceDescriptors(IServiceCollection serviceCollection, ServiceDescriptor sd, Type implType, Type proxyType) + { + // 将原实现注册为自身,在代理类中注入 + var nsd = sd.IsKeyedService + ? ServiceDescriptor.DescribeKeyed( + implType, + sd.ServiceKey, + implType, + sd.Lifetime + ) + : ServiceDescriptor.Describe( + implType, + implType, + sd.Lifetime + ); + serviceCollection.Add(nsd); + + var proxySd = sd.IsKeyedService + ? ServiceDescriptor.DescribeKeyed( + sd.ServiceType, + sd.ServiceKey, + proxyType, + sd.Lifetime + ) + : ServiceDescriptor.Describe( + sd.ServiceType, + proxyType, + sd.Lifetime + ); + + serviceCollection.Add(nsd); + serviceCollection.Add(proxySd); } } } diff --git a/src/AutoAopProxyGenerator/ContextHelper.cs b/src/AutoAopProxyGenerator/ContextHelper.cs new file mode 100644 index 0000000..aaf8245 --- /dev/null +++ b/src/AutoAopProxyGenerator/ContextHelper.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace AutoAopProxyGenerator; + +public static class ContextHelper +{ + private static readonly ConcurrentDictionary caches = []; + private static readonly Type ServiceType = typeof(TService); + private static readonly Type ImplType = typeof(TImpl); + public static ProxyContext GetOrCreate(string methodName, Type[] types) + { + var key = $"{ServiceType.FullName}_{ImplType.FullName}_{methodName}_{string.Join("_", types.Select(t => t.Name))}"; + return caches.GetOrAdd(key, (k) => + { + var serviceMethod = ServiceType.GetMethod(methodName, types); + var implMethod = ImplType.GetMethod(methodName, types); + return new ProxyContext() + { + ServiceType = ServiceType, + ImplementType = ImplType, + ServiceMethod = serviceMethod, + ImplementMethod = implMethod + }; + }); + + } +} + diff --git a/src/AutoAopProxyGenerator/IAspectHandler.cs b/src/AutoAopProxyGenerator/IAspectHandler.cs index 26959cb..250b069 100644 --- a/src/AutoAopProxyGenerator/IAspectHandler.cs +++ b/src/AutoAopProxyGenerator/IAspectHandler.cs @@ -10,25 +10,3 @@ public interface IAspectHandler Task Invoke(ProxyContext context, Func process); } -public class ContextHelper -{ - private static readonly ConcurrentDictionary caches = []; - public static ProxyContext GetOrCreate(Type @interface, Type impl, string methodName, Type[] types) - { - var key = $"{@interface.FullName}_{impl.FullName}_{methodName}_{string.Join("_", types.Select(t => t.Name))}"; - return caches.GetOrAdd(key, (k) => - { - var interfacesMethods = @interface.GetMethod(methodName, types); - var implMethod = impl.GetMethod(methodName, types); - return new ProxyContext() - { - ServiceType = @interface, - ImplementType = impl, - ServiceMethod = interfacesMethods, - ImplementMethod = implMethod - }; - }); - - } -} - diff --git a/src/AutoAopProxyGenerator/readme.md b/src/AutoAopProxyGenerator/readme.md index f4fa8bc..4dd4f72 100644 --- a/src/AutoAopProxyGenerator/readme.md +++ b/src/AutoAopProxyGenerator/readme.md @@ -1,5 +1,7 @@ -`AutoInjectAttribute` +`AddAspectHandlerAttribute` -`AutoInjectContextAttribute` +`GenAspectProxyAttribute` -`AutoInjectConfiguration` \ No newline at end of file +`IgnoreAspectAttribute` + +`IAspectHandler` \ No newline at end of file diff --git a/src/Blazor.Test/Blazor.Test.Client/Aop/ExceptionAop.cs b/src/Blazor.Test/Blazor.Test.Client/Aop/ExceptionAop.cs new file mode 100644 index 0000000..7045531 --- /dev/null +++ b/src/Blazor.Test/Blazor.Test.Client/Aop/ExceptionAop.cs @@ -0,0 +1,25 @@ +using AutoAopProxyGenerator; + +namespace Blazor.Test.Client.Aop +{ + public class ExceptionAop : IAspectHandler + { + private readonly ILogger logger; + + public ExceptionAop(ILogger logger) + { + this.logger = logger; + } + public async Task Invoke(ProxyContext context, Func process) + { + try + { + await process(); + } + catch (Exception ex) + { + logger.LogError("Exception {Message}", ex.Message); + } + } + } +} diff --git a/src/Blazor.Test/Blazor.Test.Client/Aop/TestAop.cs b/src/Blazor.Test/Blazor.Test.Client/Aop/TestAop.cs new file mode 100644 index 0000000..6623472 --- /dev/null +++ b/src/Blazor.Test/Blazor.Test.Client/Aop/TestAop.cs @@ -0,0 +1,20 @@ +using AutoAopProxyGenerator; + +namespace Blazor.Test.Client.Aop +{ + public class TestAop : IAspectHandler + { + private readonly ILogger logger; + + public TestAop(ILogger logger) + { + this.logger = logger; + } + public async Task Invoke(ProxyContext context, Func process) + { + logger.LogInformation("执行前"); + await process(); + logger.LogInformation("执行后"); + } + } +} diff --git a/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj b/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj index 6a4f756..b996675 100644 --- a/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj +++ b/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj @@ -13,7 +13,9 @@ - + + + diff --git a/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor b/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor index ef23cb3..608d4e4 100644 --- a/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor +++ b/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor @@ -1,4 +1,6 @@ @page "/counter" +@using Blazor.Test.Client.Services + Counter @@ -7,12 +9,15 @@

Current count: @currentCount

- +SayHello Response: @response @code { private int currentCount = 0; + string? response; + [Inject, NotNull] IHelloService? Service { get; set; } - private void IncrementCount() + private async Task IncrementCount() { currentCount++; + response = await Service.SayHelloAsync($"Marvel{currentCount}"); } } diff --git a/src/Blazor.Test/Blazor.Test.Client/Services/IHelloService.cs b/src/Blazor.Test/Blazor.Test.Client/Services/IHelloService.cs new file mode 100644 index 0000000..bf18308 --- /dev/null +++ b/src/Blazor.Test/Blazor.Test.Client/Services/IHelloService.cs @@ -0,0 +1,36 @@ +using AutoAopProxyGenerator; +using Blazor.Test.Client.Aop; + +namespace Blazor.Test.Client.Services; + +[AddAspectHandler(AspectType = typeof(ExceptionAop))] +[AddAspectHandler(AspectType = typeof(TestAop))] +public interface IHelloService +{ + Task SayHelloAsync(string name); + [IgnoreAspect] + string SayHelloDirectly(string name); +} + +[GenAspectProxy] +public class HelloService : IHelloService +{ + public async Task SayHelloAsync(string name) + { + if (name.IndexOf("M") > -1) + { + throw new Exception("Name Error"); + } + await Task.Delay(500); + return $"Hello, {name}!"; + } + + public string SayHelloDirectly(string name) + { + if (name.IndexOf("M") > -1) + { + throw new Exception("Name Error"); + } + return $"Hello, {name}!"; + } +} diff --git a/src/Blazor.Test/Blazor.Test.Client/_Imports.razor b/src/Blazor.Test/Blazor.Test.Client/_Imports.razor index d0955d6..c4903f9 100644 --- a/src/Blazor.Test/Blazor.Test.Client/_Imports.razor +++ b/src/Blazor.Test/Blazor.Test.Client/_Imports.razor @@ -7,3 +7,4 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop @using Blazor.Test.Client +@using System.Diagnostics.CodeAnalysis diff --git a/src/Blazor.Test/Blazor.Test/Components/App.razor b/src/Blazor.Test/Blazor.Test/Components/App.razor index 698deac..0a3e6d3 100644 --- a/src/Blazor.Test/Blazor.Test/Components/App.razor +++ b/src/Blazor.Test/Blazor.Test/Components/App.razor @@ -9,11 +9,11 @@ - + - + diff --git a/src/Blazor.Test/Blazor.Test/Program.cs b/src/Blazor.Test/Blazor.Test/Program.cs index d18c87a..d7befdd 100644 --- a/src/Blazor.Test/Blazor.Test/Program.cs +++ b/src/Blazor.Test/Blazor.Test/Program.cs @@ -1,4 +1,6 @@ +using Blazor.Test.Client.Aop; using Blazor.Test.Client.Pages; +using Blazor.Test.Client.Services; using Blazor.Test.Components; [assembly:AutoWasmApiGenerator.WebControllerAssembly] @@ -10,8 +12,16 @@ .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Host.UseServiceProviderFactory(new AutoAopProxyGenerator.AutoAopProxyServiceProviderFactory()); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/src/TestProject1/AopGeneratorTest/LogAop.cs b/src/TestProject1/AopGeneratorTest/LogAop.cs index 39ba420..b27b7d0 100644 --- a/src/TestProject1/AopGeneratorTest/LogAop.cs +++ b/src/TestProject1/AopGeneratorTest/LogAop.cs @@ -9,9 +9,11 @@ namespace TestProject1.AopGeneratorTest { public class LogAop : IAspectHandler { - public Task Invoke(ProxyContext context, Func process) + public async Task Invoke(ProxyContext context, Func process) { - return process.Invoke(); + Console.WriteLine("Before"); + await process.Invoke(); + Console.WriteLine("After"); } } } diff --git a/src/TestProject1/AopGeneratorTest/ProxyTest.cs b/src/TestProject1/AopGeneratorTest/ProxyTest.cs index bb31a4a..47f5bef 100644 --- a/src/TestProject1/AopGeneratorTest/ProxyTest.cs +++ b/src/TestProject1/AopGeneratorTest/ProxyTest.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,13 +13,21 @@ public class ProxyTest [TestMethod] public void TestReturn() { - + } [TestMethod] - public Task TestReturnAsync() + public async Task TestReturnAsync() { - return Task.FromResult(0); + var services = new ServiceCollection(); + services.AddScoped(); + services.AddScoped(); + //services.AddScoped(); + + var provider = services.BuildServiceProvider(); + var hello = provider.GetService()!; + var i = await hello.CountAsync(); + Assert.IsTrue(3 == i); } } } diff --git a/src/TestProject1/AopGeneratorTest/User.cs b/src/TestProject1/AopGeneratorTest/User.cs index 0ddaf5f..0cd10a6 100644 --- a/src/TestProject1/AopGeneratorTest/User.cs +++ b/src/TestProject1/AopGeneratorTest/User.cs @@ -10,6 +10,7 @@ namespace TestProject1.AopGeneratorTest [AddAspectHandler(AspectType = typeof(LogAop))] public interface IHello { + [IgnoreAspect] void Hello(); void Hello(string message); Task HelloAsync(); @@ -17,8 +18,11 @@ public interface IHello int Count(); int Count(string message); Task CountAsync(); + [IgnoreAspect] Task CountAsync(string message); + [IgnoreAspect] Task RunJobAsync(); + [IgnoreAspect] Task RunJobAsync(string message); }