Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aop代理生成器 #2

Merged
merged 3 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading