Autofac 类型拦截器
Castle.Core 是 Castle 框架 的一部分,它提供了一个名为 "DynamicProxy" 的方法拦截框架。
Autofac.Extras.DynamicProxy
集成包允许对 Autofac 组件的方法调用进行拦截,常见的使用场景包括事务处理、日志记录和声明式安全。你可以使用 Autofac.Extras.DynamicProxy2
与 Autofac 4.0.0 之前的版本配合使用。
启用拦截
要使 DynamicProxy 集成工作,基本步骤如下:
create_interceptors
register_interceptors
enable_type_interception
associate_interceptors
创建拦截器
拦截器实现 Castle.DynamicProxy.IInterceptor
接口。下面是一个简单的拦截器示例,用于记录方法调用,包括输入和输出:
public class CallLogger : IInterceptor
{
TextWriter _output;
public CallLogger(TextWriter output)
{
_output = output;
}
public void Intercept(IInvocation invocation)
{
_output.Write("Calling method {0} with parameters {1}... ",
invocation.Method.Name,
string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
invocation.Proceed();
_output.WriteLine("Done: result was {0}.", invocation.ReturnValue);
}
}
注册拦截器
拦截器必须在容器中注册。你可以将其注册为类型服务或命名服务。如果你选择命名服务,它们必须命名为 IInterceptor
注册项。
选择哪种方式取决于你如何决定将拦截器与被拦截的类型关联起来。
// 命名注册
builder.Register(c => new CallLogger(Console.Out))
.Named<IInterceptor>("log-calls");
// 类型注册
builder.Register(c => new CallLogger(Console.Out));
为类型启用拦截
当你注册一个被拦截的类型时,需要在注册时标记该类型,以便 Autofac 知道要为此设置拦截。你可以使用 EnableInterfaceInterceptors()
和 EnableClassInterceptors()
注册扩展来实现这一点。
var builder = new ContainerBuilder();
builder.RegisterType<SomeType>()
.As<ISomeInterface>()
.EnableInterfaceInterceptors();
builder.Register(c => new CallLogger(Console.Out));
var container = builder.Build();
var willBeIntercepted = container.Resolve<ISomeInterface>();
背后,EnableInterfaceInterceptors()
会创建一个执行拦截的接口代理,而 EnableClassInterceptors()
则会动态子类化目标组件,以拦截虚拟方法。
这两种技术都可以与 Assembly 扫描功能结合使用,因此你可以使用相同的方法配置一组组件。
特殊情况:WCF 代理和远程对象 虽然 WCF 代理对象看起来像接口,但 EnableInterfaceInterceptors()
机制无法工作,因为 .NET 在幕后实际上是使用了行为像接口的 System.Runtime.Remoting.TransparentProxy
对象。如果要在 WCF 代理上启用拦截,请使用 InterceptTransparentProxy()
方法。
var cb = new ContainerBuilder();
cb.RegisterType<TestServiceInterceptor>();
cb.Register(c => CreateChannelFactory()).SingleInstance();
cb
.Register(c => c.Resolve<ChannelFactory<ITestService>>().CreateChannel())
.InterceptTransparentProxy(typeof(IClientChannel))
.InterceptedBy(typeof(TestServiceInterceptor))
.UseWcfSafeRelease();
将拦截器与待拦截类型关联
为了选择与你的类型关联的拦截器,你有两种选择。
你的第一种选择是为类型添加一个属性,如下所示:
// 这个属性将查找具有特定类型的 TYPED 拦截器注册:
[Intercept(typeof(CallLogger))]
public class First
{
public virtual int GetValue()
{
// 执行一些计算并返回一个值
}
}
// 这个属性将查找具有特定名称的 NAMED 拦截器注册:
[Intercept("log-calls")]
public class Second
{
public virtual int GetValue()
{
// 执行一些计算并返回一个值
}
}
当使用属性将拦截器关联起来时,你不需要在注册时指定拦截器。只需启用拦截,拦截器类型将自动被发现。
// 使用 TYPED 属性:
var builder = new ContainerBuilder();
builder.RegisterType<First>()
.EnableClassInterceptors();
builder.Register(c => new CallLogger(Console.Out));
// 使用 NAMED 属性:
var builder = new ContainerBuilder();
builder.RegisterType<Second>()
.EnableClassInterceptors();
builder.Register(c => new CallLogger(Console.Out))
.Named<IInterceptor>("log-calls");
第二种选择是在 Autofac 注册时间声明拦截器。你可以使用 InterceptedBy()
注册扩展来实现:
var builder = new ContainerBuilder();
builder.RegisterType<SomeType>()
.EnableClassInterceptors()
.InterceptedBy(typeof(CallLogger));
builder.Register(c => new CallLogger(Console.Out));
提示
使用公共接口
接口拦截要求接口是公开的(至少对动态生成的代理程序集可见)。非公共接口类型无法被拦截。
如果你想代理 internal
接口,必须在包含接口的程序集中添加注解 [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
。
使用虚方法
类拦截要求被拦截的方法是虚方法,因为它使用子类化作为代理技术。
与表达式一起使用
使用表达式创建的组件或作为实例注册的组件,不能被 DynamicProxy2 引擎子类化。在这种情况下,需要使用基于接口的代理。
接口注册
要通过接口启用代理,组件必须只通过接口提供其服务。为了获得最佳性能,所有此类服务接口都应包含在注册中,即包含在 As<X>()
子句中。
WCF 代理
如前所述,WCF 代理和其他远程类型是特殊情况,不能使用标准的接口或类拦截。你需要为这些类型使用 InterceptTransparentProxy()
。
类拦截器和 UsingConstructor
如果你通过 EnableClassInterceptors()
使用类拦截器,请避免同时使用 UsingConstructor()
。当启用类拦截时,生成的代理会添加一些新的构造函数,这些构造函数也会接受你想要使用的拦截器集合。当你指定 UsingConstructor()
时,会跳过此逻辑,导致你的拦截器未被使用。
已知问题
异步方法拦截
Castle 拦截器仅提供了同步方法拦截的机制——没有显式的 async
/await
支持。然而,由于 async
/await
只是返回 Task
对象的语法糖,你可以在拦截器中使用 Task
和 ContinueWith()
类似的方法。这个 issue 中有一个示例。另一种选择是使用 辅助库,它们可以使异步操作更方便。
Castle.Core 版本管理
从 Castle.Core 4.2.0 开始,Castle.Core
的 NuGet 包版本
更新了,但 Assembly 版本
没有更新。此外,Castle.Core 4.1.0 的 Assembly 版本
与包(4.1.0.0)匹配,而 4.2.0 包回退到 4.0.0.0。在完整 .NET 框架项目中,关于 Castle.Core 版本的任何混淆可以通过添加一个 assembly 绑定重定向来解决,强制使用 Castle.Core 4.0.0.0。
不幸的是,.NET Core 不支持 assembly 绑定重定向。如果你直接依赖 Castle.Core,同时又通过库(如 Autofac.Extras.DynamicProxy)间接依赖 Castle.Core,你可能会看到类似这样的错误:
System.IO.FileLoadException: 无法加载文件或程序集 'Castle.Core, Version=4.1.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc'。已加载的程序集与程序集中引用的程序集的定义不匹配。 (异常来自 HRESULT: 0x80131040)
这是因为回退的 assembly。
确保你使用了最新版的 Autofac.Extras.DynamicProxy。我们会尽最大努力从 Autofac 方面解决问题。更新该库或 Castle.Core 可能会有帮助。
如果这还不行,有两个解决方案:
一是删除你的直接 Castle.Core 引用。间接引用应该会自行解决。
二是如果你不能删除直接引用,或者删除后不起作用……所有直接依赖项都需要更新到 Castle.Core 4.2.0 或更高版本。你需要向这些项目报告问题;这不是 Autofac 可以为你解决的问题。