Castle Windsor 拦截来自 class 内的方法调用
Castle Windsor intercept method call from within the class
我们像这样在 Castle Windsor 容器中注册组件
void RegisterComponent<TInterface, TImplementation>() {
var component = Component.For<TInterface>().ImplementedBy<TImplementation>();
component.Interceptors<SomeInterceptor>();
container.Register(component);
}
但是我们遇到了一个问题,当我们从 class 中进行方法调用时,它不会被拦截。例如我们有
这样的组件
ServiceA : IService {
public void MethodA1() {
// do some stuff
}
public void MethodA2() {
MethodA1();
}
}
如果我们从其他一些 class 调用 MethodA2
或 MethodA1
方法,它会被拦截,但是 MethodA1
显然在从 MethodA2
调用时不会被拦截因为呼叫来自 class.
我们发现与解决方案类似的情况Castle Dynamic Proxy not intercepting method calls when invoked from within the class
然而,该解决方案使用 new
运算符创建组件和代理,这在我们的案例中不合适,因为我们使用的是容器。我们可以像上面那样将这个解决方案用于组件注册吗?或者有其他方法可以解决这个问题吗?
为了在从 MethodA2
调用时拦截在 MethodA1
上工作,您需要使用基于继承的拦截(这是因为您正在使用 this
引用来进行调用)。
要使基于继承的拦截成为可能,您首先需要使 MethodA1
和 MethodA2
virtual
.
然后可以这样注册容器:
container.Register(Component.For<ServiceA>().Interceptors<SomeInterceptor>());
container.Register(Component.For<IService>().UsingFactoryMethod(c => c.Resolve<ServiceA>()));
首先将您的服务注册为本身应用拦截器(这将在服务上添加基于继承的拦截)。然后就可以注册接口了,这个接口会使用之前注册的服务。
我们使用 CreateClassProxy
方法为服务创建代理,正如在对问题 Castle Dynamic Proxy not intercepting method calls when invoked from within the class 的回答中所建议的那样。
然后我们将获得的代理注册为接口的实现。
所以我们的自定义 RegisterComponent
方法看起来像这样
private void RegisterComponent<TInterface, TImplementation>()
where TInterface : class
where TImplementation : class, TInterface
{
var proxyType = new ProxyGenerator().CreateClassProxy<TImplementation>().GetType();
Container.Register(Component.For<TInterface>().ImplementedBy(proxyType));
}
完整组件注册为
Container = new WindsorContainer();
Container.Kernel.Resolver.AddSubResolver(new CollectionResolver(Container.Kernel));
// Interceptor
Container.Register(Component.For<IInterceptor>().ImplementedBy<SomeInterceptor>().LifestyleTransient());
// Component registrations
RegisterComponent<ISomeService, SomeService>();
当然,您需要拦截的所有方法都应该是 virtual
,因为使用了基于继承的代理。
然而,此解决方案的一个缺点是您在创建代理对象时无法使用构造函数注入。
请注意,您使用 new
运算符创建 "dummy" 代理对象只是为了获取代理类型。因此,您不能仅在构建虚拟代理时使用构造函数注入,但是当您通过容器解析服务时,注入就可以正常工作。因此,这个缺点仅对于构造逻辑比依赖分配更复杂的组件才是关键的。如果您只需要依赖分配,您可以尝试在创建虚拟代理之前手动解决容器中的所有依赖关系
private object[] ResolveConstructorParameters<TType>()
{
return typeof(TType).GetConstructors()
.Single(c => c.IsPublic)
.GetParameters()
.Select(p => _container.Resolve(p.ParameterType))
.ToArray();
}
然后 RegisterComponent
会变成
private void RegisterComponent<TInterface, TImplementation>()
where TInterface : class
where TImplementation : class, TInterface
{
var constructorParameters = ResolveConstructorParameters<TImplementation>();
var proxyType = new ProxyGenerator().CreateClassProxy(typeof(TImplementation), constructorParameters).GetType();
_container.Register(Component.For<TInterface>().ImplementedBy(proxyType));
}
您也可以只用 null
填充参数。
将您的注册更改为以下,Windsor 应切换到 class 代理 - 即使用继承进行拦截,而不是组合。
void RegisterComponent<TInterface, TImplementation>() {
container.Register(Component.For<TInterface,TImplementation>().ImplementedBy<TImplementation>().Interceptors<SomeInterceptor>());
}
@NikolayKondratyev 我调查过 https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/Windsor/Proxy/DefaultProxyFactory.cs#L110
我已经以简单的方式完成了注册:
container.Register(Classes.FromThisAssembly().BasedOn(typeof(IRepositoryBase<,>))
.WithServiceAllInterfaces().WithServiceSelf()
.LifestyleTransient());
注意 .WithServiceSelf()
调用,这实际上切换了基于 class 的代理
我知道这是一个旧线程,但我只是在让 Castle 拦截器在 Blazor WASM 中工作时遇到它(他们 实际上 这样做,但要注意...Mono 可以'似乎支持代理任何具有任何通用方法的 class...)。
无论如何,为了在我的案例中解决这个问题,我只是将容器注入我的 class,并在需要通过 this
调用“同级方法”的方法中我只是解析了我的接口的一个新实例并调用了该方法。它不适用于具有共享 context/transient 状态的场景,但拦截器确实做了它的事情。
在 Blazor 的客户端 WASM 应用程序中 Program.cs:
public static async Task Main(string[] args)
{
WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.ConfigureContainer<IWindsorContainer>(new WindsorServiceProviderFactory(), container =>
{
container.Register(Component.For<IInterceptor>()
.ImplementedBy<BlazorInterceptor>()
.Named("BlazorInterceptor").LifestyleTransient());
});
...
builder.Services.AddScoped<IService, Service>();
...
await builder.Build().RunAsync();
}
示例服务和接口实现:
public Interface IService
{
MethodA(int arg);
MethodB(int arg);
}
[Interceptor("BlazorInterceptor")]
public class Service : IService
{
private readonly IServiceProvider _container;
public Service(IServiceProvider container)
{
this._container = container;
}
public MethodA(int arg)
{
IService service = this._container.GetRequiredService<IService>();
service.MethodB(arg);
}
public MethodB(int arg)
{
//should be intercepted...just in a different instance of the service unless you're using singletons...
}
}
优点:不需要虚拟化方法或使 DI 配置复杂化。
缺点:有点恶心(对无状态存储库有用,但可能会给 EF 之类的东西带来心脏病)。
我们像这样在 Castle Windsor 容器中注册组件
void RegisterComponent<TInterface, TImplementation>() {
var component = Component.For<TInterface>().ImplementedBy<TImplementation>();
component.Interceptors<SomeInterceptor>();
container.Register(component);
}
但是我们遇到了一个问题,当我们从 class 中进行方法调用时,它不会被拦截。例如我们有
这样的组件ServiceA : IService {
public void MethodA1() {
// do some stuff
}
public void MethodA2() {
MethodA1();
}
}
如果我们从其他一些 class 调用 MethodA2
或 MethodA1
方法,它会被拦截,但是 MethodA1
显然在从 MethodA2
调用时不会被拦截因为呼叫来自 class.
我们发现与解决方案类似的情况Castle Dynamic Proxy not intercepting method calls when invoked from within the class
然而,该解决方案使用 new
运算符创建组件和代理,这在我们的案例中不合适,因为我们使用的是容器。我们可以像上面那样将这个解决方案用于组件注册吗?或者有其他方法可以解决这个问题吗?
为了在从 MethodA2
调用时拦截在 MethodA1
上工作,您需要使用基于继承的拦截(这是因为您正在使用 this
引用来进行调用)。
要使基于继承的拦截成为可能,您首先需要使 MethodA1
和 MethodA2
virtual
.
然后可以这样注册容器:
container.Register(Component.For<ServiceA>().Interceptors<SomeInterceptor>());
container.Register(Component.For<IService>().UsingFactoryMethod(c => c.Resolve<ServiceA>()));
首先将您的服务注册为本身应用拦截器(这将在服务上添加基于继承的拦截)。然后就可以注册接口了,这个接口会使用之前注册的服务。
我们使用 CreateClassProxy
方法为服务创建代理,正如在对问题 Castle Dynamic Proxy not intercepting method calls when invoked from within the class 的回答中所建议的那样。
然后我们将获得的代理注册为接口的实现。
所以我们的自定义 RegisterComponent
方法看起来像这样
private void RegisterComponent<TInterface, TImplementation>()
where TInterface : class
where TImplementation : class, TInterface
{
var proxyType = new ProxyGenerator().CreateClassProxy<TImplementation>().GetType();
Container.Register(Component.For<TInterface>().ImplementedBy(proxyType));
}
完整组件注册为
Container = new WindsorContainer();
Container.Kernel.Resolver.AddSubResolver(new CollectionResolver(Container.Kernel));
// Interceptor
Container.Register(Component.For<IInterceptor>().ImplementedBy<SomeInterceptor>().LifestyleTransient());
// Component registrations
RegisterComponent<ISomeService, SomeService>();
当然,您需要拦截的所有方法都应该是 virtual
,因为使用了基于继承的代理。
然而,此解决方案的一个缺点是您在创建代理对象时无法使用构造函数注入。
请注意,您使用 new
运算符创建 "dummy" 代理对象只是为了获取代理类型。因此,您不能仅在构建虚拟代理时使用构造函数注入,但是当您通过容器解析服务时,注入就可以正常工作。因此,这个缺点仅对于构造逻辑比依赖分配更复杂的组件才是关键的。如果您只需要依赖分配,您可以尝试在创建虚拟代理之前手动解决容器中的所有依赖关系
private object[] ResolveConstructorParameters<TType>()
{
return typeof(TType).GetConstructors()
.Single(c => c.IsPublic)
.GetParameters()
.Select(p => _container.Resolve(p.ParameterType))
.ToArray();
}
然后 RegisterComponent
会变成
private void RegisterComponent<TInterface, TImplementation>()
where TInterface : class
where TImplementation : class, TInterface
{
var constructorParameters = ResolveConstructorParameters<TImplementation>();
var proxyType = new ProxyGenerator().CreateClassProxy(typeof(TImplementation), constructorParameters).GetType();
_container.Register(Component.For<TInterface>().ImplementedBy(proxyType));
}
您也可以只用 null
填充参数。
将您的注册更改为以下,Windsor 应切换到 class 代理 - 即使用继承进行拦截,而不是组合。
void RegisterComponent<TInterface, TImplementation>() {
container.Register(Component.For<TInterface,TImplementation>().ImplementedBy<TImplementation>().Interceptors<SomeInterceptor>());
}
@NikolayKondratyev 我调查过 https://github.com/castleproject/Windsor/blob/master/src/Castle.Windsor/Windsor/Proxy/DefaultProxyFactory.cs#L110
我已经以简单的方式完成了注册:
container.Register(Classes.FromThisAssembly().BasedOn(typeof(IRepositoryBase<,>))
.WithServiceAllInterfaces().WithServiceSelf()
.LifestyleTransient());
注意 .WithServiceSelf()
调用,这实际上切换了基于 class 的代理
我知道这是一个旧线程,但我只是在让 Castle 拦截器在 Blazor WASM 中工作时遇到它(他们 实际上 这样做,但要注意...Mono 可以'似乎支持代理任何具有任何通用方法的 class...)。
无论如何,为了在我的案例中解决这个问题,我只是将容器注入我的 class,并在需要通过 this
调用“同级方法”的方法中我只是解析了我的接口的一个新实例并调用了该方法。它不适用于具有共享 context/transient 状态的场景,但拦截器确实做了它的事情。
在 Blazor 的客户端 WASM 应用程序中 Program.cs:
public static async Task Main(string[] args)
{
WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.ConfigureContainer<IWindsorContainer>(new WindsorServiceProviderFactory(), container =>
{
container.Register(Component.For<IInterceptor>()
.ImplementedBy<BlazorInterceptor>()
.Named("BlazorInterceptor").LifestyleTransient());
});
...
builder.Services.AddScoped<IService, Service>();
...
await builder.Build().RunAsync();
}
示例服务和接口实现:
public Interface IService
{
MethodA(int arg);
MethodB(int arg);
}
[Interceptor("BlazorInterceptor")]
public class Service : IService
{
private readonly IServiceProvider _container;
public Service(IServiceProvider container)
{
this._container = container;
}
public MethodA(int arg)
{
IService service = this._container.GetRequiredService<IService>();
service.MethodB(arg);
}
public MethodB(int arg)
{
//should be intercepted...just in a different instance of the service unless you're using singletons...
}
}
优点:不需要虚拟化方法或使 DI 配置复杂化。 缺点:有点恶心(对无状态存储库有用,但可能会给 EF 之类的东西带来心脏病)。