代码之家  ›  专栏  ›  技术社区  ›  Nikolay K

Castle Windsor从类内拦截方法调用

  •  3
  • Nikolay K  · 技术社区  · 8 年前

    我们在Castle Windsor集装箱中有组件注册

    void RegisterComponent<TInterface, TImplementation>() {
        var component = Component.For<TInterface>().ImplementedBy<TImplementation>();
        component.Interceptors<SomeInterceptor>();
        container.Register(component);
    }
    

    然而,我们遇到了一个问题,当我们从类中进行方法调用时,它不会被拦截。例如,我们有这样的组件

    ServiceA : IService {
    
        public void MethodA1() {
            // do some stuff
        }
    
        public void MethodA2() {
            MethodA1();
        }
    
    }
    

    如果我们打电话 MethodA2 MethodA1 方法,但 方法A1 从调用时显然未被拦截 方法A2 因为调用来自类内。

    我们发现了类似的解决方案 Castle Dynamic Proxy not intercepting method calls when invoked from within the class 然而,该解决方案的特点是使用 new 因为我们使用的是容器,所以不适合我们的情况。我们可以将此解决方案与上述组件注册一起使用吗?还是有其他方法可以解决这个问题?

    5 回复  |  直到 7 年前
        1
  •  3
  •   Krzysztof Branicki    8 年前

    用于拦截 MethodA1 从调用时 MethodA2 您需要使用基于继承的拦截(这是因为您正在使用 this 进行调用的参考)。

    要使基于继承的拦截成为可能,首先需要 方法A1 方法A2 virtual .

    然后,您可以像这样进行容器注册:

    container.Register(Component.For<ServiceA>().Interceptors<SomeInterceptor>());
    container.Register(Component.For<IService>().UsingFactoryMethod(c => c.Resolve<ServiceA>()));
    

    首先将服务注册为应用拦截器的服务(这将在服务上添加基于继承的拦截)。然后您可以注册将使用先前注册的服务的接口。

        2
  •  2
  •   Phil Degenhardt    8 年前

    将您的注册更改为以下内容,Windsor应切换到类代理-即使用继承进行拦截,而不是组合。

    void RegisterComponent<TInterface, TImplementation>() {
        container.Register(Component.For<TInterface,TImplementation>().ImplementedBy<TImplementation>().Interceptors<SomeInterceptor>());
    }
    
        3
  •  1
  •   Community Dai    7 年前

    我们使用 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 运算符只获取代理的类型。因此,您不能仅在构建虚拟代理时使用构造函数注入,但当您通过容器解析服务时,注入将正常工作。因此,这个缺点仅对构造逻辑比分配依赖关系更复杂的组件至关重要。如果您只需要依赖项分配,则可以尝试在创建虚拟代理之前手动解析容器中的所有依赖项

    private object[] ResolveConstructorParameters<TType>()
    {
        return typeof(TType).GetConstructors()
                            .Single(c => c.IsPublic)
                            .GetParameters()
                            .Select(p => _container.Resolve(p.ParameterType))
                            .ToArray();
    }
    

    然后 注册组件 将成为

    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 .

        4
  •  1
  •   xumix    8 年前

    @我调查过的尼古拉·康德拉季耶夫 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() 调用,这实际上切换了基于类的代理

        5
  •  0
  •   Dharman thijsvdp    4 年前

    我知道这是一条老线索,但我是在Blazor WASM(他们在Blazor-WASM)的城堡拦截器工作时偶然发现的 事实上 可以,但请注意…Mono似乎不支持代理任何具有任何泛型方法的类…)。

    无论如何,在我的例子中,为了解决这个问题,我简单地将容器注入到我的类中,并在需要通过调用“兄弟方法”的方法中 this 我简单地解析了接口的一个新实例,并调用了该方法。它不适用于共享上下文/瞬态的场景,但拦截器确实做到了。

    在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之类的东西心脏病发作)。