Castle.DynamicProxy拦截失效主因是方法未声明为virtual或未实现接口:CreateClassProxy仅代理virtual方法,否则抛“Method is not virtual”异常;接口代理则无此限制。

Castle.DynamicProxy 是目前 C# 生产环境中最稳定、兼容性最好的运行时 AOP 方案,但它不是“开箱即用”的魔法,必须满足接口或虚方法约束,且拦截器逻辑无法作用于 private、static、sealed 方法。
为什么 CreateClassProxy 会报错 “Method is not virtual”
这是最常见的拦路虎。Castle 只能通过继承方式生成子类代理,而 C# 要求被重写的方法必须显式标记为 virtual(或 abstract)。哪怕你实现了接口,只要用的是 CreateClassProxy<T>,目标类里对应方法没加 virtual,就会在运行时报 System.Reflection.TargetInvocationException,内层异常信息通常是 “The method … must be virtual or abstract”。
-
CreateClassProxy<T>:只能代理T类中声明为virtual的实例方法,不依赖接口 -
CreateInterfaceProxyWithTarget<TInterface>:代理实现类,不要求方法virtual,但必须传入一个已存在的实现对象 - 接口代理(
CreateInterfaceProxyWithoutTarget):完全不依赖实现类,但需要手动 new 出所有依赖并注入到拦截器中
Intercept 中调用 invocation.Proceed() 的实际含义
invocation.Proceed() 不是“继续执行下一行代码”,而是把控制权交还给 Castle 生成的代理链——它可能触发其他拦截器,也可能最终落到原始方法体。如果在 Intercept 里忘了调用它,原始业务逻辑就永远不会执行;如果调用两次,会抛出 InvalidOperationException: "Proceed has already been called"。
- 前置逻辑写在
invocation.Proceed()之前,后置逻辑写在之后 - 想跳过原方法?直接不调用
invocation.Proceed(),并设置invocation.ReturnValue(对 void 方法可忽略) - 想捕获异常?用 try/catch 包住
invocation.Proceed(),但注意:异常发生后invocation.ReturnValue无效,应改用invocation.SetException(...)
Autofac 集成 Castle 时,IInterceptor 实例生命周期很关键
Autofac 默认注册的 IInterceptor 是单例(Singleton),如果拦截器里持有非线程安全的状态(比如一个 Dictionary<string, int> 计数器),高并发下会出数据错乱。反过来,如果注册成 InstancePerLifetimeScope 或 InstancePerDependency,又可能导致内存泄漏或状态丢失——比如你希望每个 HTTP 请求共用一个日志上下文 ID,那就得配合 ASP.NET Core 的 HttpContextAccessor 手动提取 scope。
- 无状态拦截器(如纯日志、纯计时):注册为
SingleInstance()最安全 - 需绑定请求/事务上下文的拦截器:注册为
InstancePerLifetimeScope(),并在Intercept中通过invocation.InvocationTarget或invocation.Proxy获取当前上下文 - 绝对避免在拦截器字段里缓存
HttpContext、DbContext等 Scoped 对象,它们会在后续调用中失效
真正难的从来不是写一个 IInterceptor,而是判断该拦截哪一层:是仓储接口、服务接口,还是控制器方法?不同层级的代理成本、异常传播路径、事务边界都不同。别为了“统一加日志”而把拦截器挂到最外层控制器上——那会错过内部服务间的调用,也容易干扰 MVC 自身的模型绑定和过滤器链。



















