Skip to content

Commit

Permalink
aop代理生成器 (#2)
Browse files Browse the repository at this point in the history
aop代理生成器
  • Loading branch information
MarvelTiter authored Aug 27, 2024
1 parent 889b830 commit 80b826b
Show file tree
Hide file tree
Showing 36 changed files with 1,092 additions and 60 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.{cs,vb}]

# IDE0130: 命名空间与文件夹结构不匹配
dotnet_diagnostic.IDE0130.severity = none
7 changes: 7 additions & 0 deletions .github/workflows/push_nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
251 changes: 251 additions & 0 deletions src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
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 const string IgnoreAspect = "AutoAopProxyGenerator.IgnoreAspectAttribute";
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<INamedTypeSymbol>();
}).Distinct(EqualityComparer<INamedTypeSymbol>.Default).ToArray();
#endregion

var file = CreateGeneratedProxyClassFile(targetSymbol, allInterfaces, allHandlers);
if (file != null)
{
//var ss = file.ToString();
context.AddSource(file);
}
});
}

private static CodeFile? CreateGeneratedProxyClassFile(INamedTypeSymbol classSymbol, INamedTypeSymbol[] interfaces, INamedTypeSymbol[] allHandlers)
{
// 代理字段和aspect handler 字段
List<Node> members = [
FieldBuilder.Default.MemberType(classSymbol.ToDisplayString()).FieldName("proxy")
, .. allHandlers.Select(n =>FieldBuilder.Default.MemberType(n.ToDisplayString()).FieldName(n.MetadataName))
];
// 构造函数
List<Statement> 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)
{
members.AddRange(CreateProxyMethod(iface, classSymbol));
}

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<MethodBuilder> CreateProxyMethod(INamedTypeSymbol iface, INamedTypeSymbol classSymbol)
{
var handlers = iface.GetAttributes(AspectHandler).Select(a => a.GetNamedValue("AspectType")).OfType<INamedTypeSymbol>().ToArray();

foreach (var m in iface.GetMethods())
{
MethodBuilder methodBuilder;
if (m.HasAttribute(IgnoreAspect))
{
methodBuilder = CreateDirectInvokeMethod();
}
else
{
methodBuilder = CreateProxyMethod();
}

yield return methodBuilder;

MethodBuilder CreateDirectInvokeMethod()
{
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;
}

MethodBuilder CreateProxyMethod()
{
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<Statement> 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<ProxyContext>.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]);

return builder;
}
}
}

private static IEnumerable<Statement> 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";
}
}
}
}
36 changes: 36 additions & 0 deletions src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Text;

namespace AutoAopProxyGenerator
{
internal class DiagnosticDefinitions
{
/// <summary>
/// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null
/// </summary>
/// <param name="location"></param>
/// <returns></returns>
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);

/// <summary>
/// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler
/// </summary>
/// <param name="location"></param>
/// <returns></returns>
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);
}
}
8 changes: 8 additions & 0 deletions src/AutoAopProxy.Roslyn/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"aop": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\TestProject1\\TestProject1.csproj"
}
}
}
16 changes: 16 additions & 0 deletions src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AutoAopProxyGenerator;

/// <summary>
/// 在类或者接口上配置切面处理
/// </summary>
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = true)]
public sealed class AddAspectHandlerAttribute : Attribute
{
public Type? AspectType { get; set; }
}
12 changes: 0 additions & 12 deletions src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs

This file was deleted.

12 changes: 12 additions & 0 deletions src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace AutoAopProxyGenerator;

/// <summary>
/// 标记需要生成代理类的类型
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class GenAspectProxyAttribute : Attribute
{

}
12 changes: 12 additions & 0 deletions src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace AutoAopProxyGenerator;

/// <summary>
/// 配置不需要切面的方法
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class IgnoreAspectAttribute : Attribute
{

}
Loading

0 comments on commit 80b826b

Please sign in to comment.