Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,5 @@ publish.*.bat

BenchmarkDotNet.Artifacts/
tools/
.omc
.oh-my-code
221 changes: 220 additions & 1 deletion AspectCore-Framework.sln

Large diffs are not rendered by default.

300 changes: 300 additions & 0 deletions docs/1.使用指南.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,306 @@
.UseServiceProviderFactory(new DynamicProxyServiceProviderFactory());
```
---

## Source Generator AOP(可选)

从现有版本开始,AspectCore 在传统的运行时 `DynamicProxy` 之外,新增了**编译期 Source Generator AOP**(下文简称 SG AOP)。它对现有用户是**显式 opt-in**:

- **默认不变**:不做任何新配置时,仍然使用运行时 `DynamicProxy`(与旧版本行为一致)。
- 你仍然使用原来的拦截器配置 API:`ConfigureDynamicProxy(...)`、`IAspectConfiguration`、`Predicates` 等都不变。

### 1) 三种引擎:DynamicProxy vs SourceGenerator vs Auto

引擎选择通过 `ProxyEngineOptions.Engine` 完成:

```csharp
using AspectCore.DynamicProxy;

// ProxyEngine.DynamicProxy // 运行时 Emit(默认)
// ProxyEngine.SourceGenerator // 仅使用编译期生成物(缺失则报错)
// ProxyEngine.Auto // 优先 SG,缺失时可按策略回退 DynamicProxy
```

推荐理解方式:

- **DynamicProxy**:运行时生成代理(默认)。
- **SourceGenerator**:编译期生成代理 + registry,运行时直接查表拿到代理类型。
- **Auto(优先 SG)**:能用 SG 就用 SG;找不到生成物时,**可选**回退到 DynamicProxy(适合渐进迁移)。

### 2) 默认行为不变(不配置仍是 DynamicProxy)

只有当你显式调用下列 API 之一时,才会启用“可选引擎”逻辑:

- MS.DI:`ServiceCollectionExtensions.ConfigureDynamicProxyEngine(...)`
- ServiceContext:`ServiceContextExtensions.ConfigureDynamicProxyEngine(...)`

否则:即使你引用了 `AspectCore.SourceGenerator`,也仍然按默认 `DynamicProxy` 工作。

### 3) 如何启用 SG AOP(必须同时满足三件事)

启用 SG AOP 需要同时满足:

1. **引用 analyzer**:`AspectCore.SourceGenerator`(以 analyzer 方式引用)
2. **触发生成**:在要生成代理的类型上标记 `[AspectCoreGenerateProxy]`
3. **运行时选择引擎**:调用 `ConfigureDynamicProxyEngine(...)` 选择 `SourceGenerator` 或 `Auto`

#### 3.1 引用 analyzer:AspectCore.SourceGenerator

以下是常见写法(示例):

```xml
<!-- 以 NuGet analyzer 引用(推荐) -->
<ItemGroup>
<PackageReference Include="AspectCore.SourceGenerator" Version="x.y.z" PrivateAssets="all" />
</ItemGroup>

<!-- 或者:以 ProjectReference 引用并作为 Analyzer 输出(仓库 sample 用法) -->
<ItemGroup>
<ProjectReference Include="..\..\src\AspectCore.SourceGenerator\AspectCore.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
```

#### 3.2 触发生成:在类型上标记 [AspectCoreGenerateProxy]

当前版本主要支持 **type-level** 触发:把特性标到 class 或 interface 上。

```csharp
using AspectCore.DynamicProxy;

[AspectCoreGenerateProxy]
public class MyService
{
public virtual void DoWork() { }
}

[AspectCoreGenerateProxy]
public interface IMyService
{
void DoWork();
}
```

注意:

- **class 代理只会拦截可重写成员**(例如 `virtual` 方法、可重写属性)。
- 当前 SG 节点对一些类型形态可能有限制(例如泛型类型、嵌套类型等):如果没生成成功,请看本文末 FAQ 的排查建议。

#### 3.3 运行时选择引擎:ConfigureDynamicProxyEngine

MS.DI(`IServiceCollection`)示例:

```csharp
using AspectCore.DynamicProxy;
using AspectCore.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

services.ConfigureDynamicProxyEngine(o =>
{
o.Engine = ProxyEngine.Auto; // 推荐从 Auto 开始
});
```

ServiceContext 示例:

```csharp
using AspectCore.DependencyInjection;
using AspectCore.DynamicProxy;

serviceContext.ConfigureDynamicProxyEngine(o =>
{
o.Engine = ProxyEngine.Auto;
});
```

### 4) Strict / AllowRuntimeFallback / Auto:行为与适用场景

`ProxyEngineOptions` 提供三个关键开关:

```csharp
using AspectCore.DynamicProxy;

services.ConfigureDynamicProxyEngine(o =>
{
o.Engine = ProxyEngine.Auto;
o.AllowRuntimeFallback = true; // 可选:是否允许缺失生成物时回退 DynamicProxy
o.Strict = false; // 可选:严格模式,缺失生成物直接抛异常
});
```

行为总结(按当前实现):

- `Engine = DynamicProxy`:始终走运行时 DynamicProxy(与旧版本一致)。
- `Engine = SourceGenerator`:只使用 SG 生成物;**缺失时会抛异常**(即使你设置了 `AllowRuntimeFallback=true` 也不会回退)。
- `Engine = Auto`:优先使用 SG;缺失时**默认允许**回退到 DynamicProxy。
- 如显式设置 `AllowRuntimeFallback=false`,则缺失生成物会抛异常。
- 如 `Strict=true`,则缺失生成物也会抛异常(常用于 CI 做“覆盖率”强约束)。

推荐实践:

- **渐进迁移/本地开发**:`Auto`(默认允许回退),先让系统跑起来。
- **CI 强约束**:`Auto + Strict=true` 或 `Auto + AllowRuntimeFallback=false`,确保“该生成的都生成了”。
- **AOT/Trim 场景**:通常选择 `SourceGenerator`(并配合手动 registry 注册;见下文)。

### 5) 常见坑(必读)

#### 5.1 构造函数(ctor)匹配:为什么“看起来能注入”但运行时报错?

SG AOP 下,最终注入到容器里的其实是“代理类型”。以 **class 代理**为例:

- 代理类型会在你的构造函数参数列表前面额外增加一个 `IAspectActivatorFactory` 参数。
- 代理类型会生成一组构造函数,去匹配你原本可见的构造函数(public/protected 等)。

因此:

- 你的服务类型必须存在**可用的构造函数**(public/protected),并且其参数都能被容器解析。
- 如果你的服务只有私有构造函数,或依赖未注册,就会在构建/解析服务时失败。

#### 5.2 为什么需要 [Dynamically]?要不要我自己加?

不需要你手动添加。

`[Dynamically]` 是 AspectCore 在生成的代理类型/成员上附加的一个标记(见 `AspectCore.DynamicProxy.DynamicallyAttribute`)。它主要用于标识这些类型属于“动态代理生成物”,在诊断、反射或裁剪(trimming)相关场景中作为识别点。

#### 5.3 ServiceContext 也支持,但注册方式不同

在 ServiceContext 模式下:

- 仍可通过 `serviceContext.ConfigureDynamicProxyEngine(...)` 选择引擎。
- 手动注册 registry 的 API 为 `serviceContext.AddSourceGeneratedProxyRegistry(ISourceGeneratedProxyRegistry registry)`(不是泛型)。

示例:

```csharp
using AspectCore.DependencyInjection;
using AspectCore.DynamicProxy;

serviceContext.ConfigureDynamicProxyEngine(o => o.Engine = ProxyEngine.SourceGenerator);
// registry 类型名/命名空间以实际生成结果为准(下同)
serviceContext.AddSourceGeneratedProxyRegistry(new YourGeneratedRegistry());
```

#### 5.4 Trimming/AOT 下 registry discovery 可能不可用:需要手动注册

运行时为了找到 SG 生成的 registry,会做两件事(概念层面):

1. 扫描已加载程序集的 assembly attribute:`[AspectCoreSourceGeneratedProxyRegistry(...)]`
2. 找到 `RegistryType` 后使用无参构造创建 registry 实例

在 trimming/AOT 环境里,程序集扫描与反射创建可能受限,导致“生成了但运行时找不到”。此时请使用**手动注册**:

- MS.DI:

```csharp
using AspectCore.Extensions.DependencyInjection;

// registry 类型名/命名空间以实际生成结果为准
services.AddSourceGeneratedProxyRegistry<YourGeneratedRegistry>();
```

- ServiceContext:

```csharp
serviceContext.AddSourceGeneratedProxyRegistry(new YourGeneratedRegistry());
```

> 提示:当前版本的生成器默认会输出 `AspectCore.SourceGenerated.AspectCoreSourceGeneratedProxyRegistry`,并通过程序集级特性 `AspectCore.DynamicProxy.AspectCoreSourceGeneratedProxyRegistryAttribute` 暴露给运行时扫描;但具体类型名/命名空间仍应以你的项目实际生成结果为准。

### 6) 最小可运行示例(MS.DI + 一个拦截器 + 一个服务)

下面示例展示:引用 SG analyzer + 标记生成 + 运行时选择引擎 + 全局拦截器。

```csharp
using System;
using System.Threading.Tasks;
using AspectCore.Configuration;
using AspectCore.DynamicProxy;
using AspectCore.DynamicProxy.Parameters;
using AspectCore.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

public sealed class LogInterceptor : AbstractInterceptor
{
public override Task Invoke(AspectContext context, AspectDelegate next)
{
Console.WriteLine($"[AOP] before: {context.ImplementationMethod.Name}");
return next(context);
}
}

[AspectCoreGenerateProxy] // 触发生成 class proxy
public class HelloService
{
public virtual void Say(string name)
=> Console.WriteLine($"Hello, {name}");
}

public static class Demo
{
public static void Main()
{
var services = new ServiceCollection();

services.AddTransient<HelloService>();

// 选择引擎:建议从 Auto 开始,稳定后再切 Strict/SG
services.ConfigureDynamicProxyEngine(o =>
{
o.Engine = ProxyEngine.Auto;
});

// 拦截器配置方式不变
services.ConfigureDynamicProxy(config =>
{
config.Interceptors.AddTyped<LogInterceptor>(Predicates.ForService("*Service"));
});

var sp = services.BuildDynamicProxyProvider();

sp.GetRequiredService<HelloService>().Say("AspectCore");
}
}
```

### 7) FAQ

#### Q1:我启用了 SourceGenerator/Auto,但运行时提示“找不到生成的 proxy”怎么办?

优先按顺序排查:

1. 你的项目是否**真的引用了** `AspectCore.SourceGenerator`(以 analyzer 方式引用)?
2. 目标类型是否标了 `[AspectCoreGenerateProxy]`?(建议从一个最小类型开始验证)
3. 你是否调用了 `ConfigureDynamicProxyEngine(...)` 并把 `Engine` 设为 `Auto` 或 `SourceGenerator`?
4. 你的类型是否属于当前 SG 暂不支持的形态(例如泛型/嵌套类型等)?
5. 如果是 trimming/AOT:是否需要改为手动注册 registry(见 5.4)?

#### Q2:怎么快速回退到 DynamicProxy?

两种方式:

- 最简单:不调用 `ConfigureDynamicProxyEngine(...)`(默认就是 DynamicProxy)。
- 或者显式指定:

```csharp
services.ConfigureDynamicProxyEngine(o =>
{
o.Engine = ProxyEngine.DynamicProxy;
});
```

#### Q3:我只标了实现类,但注册的是接口,为什么还是没走 SG?

当你以接口方式注册(例如 `services.AddTransient<IMyService, MyService>()`)时,容器需要的是**接口代理**。

当前 SG 的 type-level 触发方式通常要求你在**接口类型**上也标记 `[AspectCoreGenerateProxy]`,以生成对应的 interface proxy:

```csharp
[AspectCoreGenerateProxy]
public interface IMyService { void DoWork(); }
```

---
## 高级拦截器功能

### 获取方法信息
Expand Down
Loading
Loading