3

我目前的大部分实施都基于此处提供的信息:

Ninject 拦截具有特定属性的任何方法?

我使用自定义计划策略类,它查找具有给定属性(不是 ninject 拦截器属性)的所有方法,如果它符合条件,则将被代理。

一个使用示例是:

Kernel.Components.Add<IPlanningStrategy, CustomPlanningStrategy<LoggingAttribute, LoggerInterceptor>>();

然后,这将查找任何具有[Logging]属性的方法,然后使用日志拦截器。

但是,当我尝试代理具有相关属性的方法时,我目前正在从动态代理获取 InvalidProxyConstructorArgumentsException。现在我记得读到你需要虚拟方法,但是我不记得看到你必须有一个无参数的构造函数。

所有绑定都是针对接口完成的,AOP 拦截器通过属性和上面链接中提到的自定义代理计划类发生。

那么有没有办法让动态代理(或 linfu 版本)来代理具有依赖关系的构造函数的类?(所有依赖项都在内核中,因此不像它们无法解决)。

4

2 回答 2

4

查看代理生成代码: https ://github.com/ninject/ninject.extensions.interception/blob/master/src/Ninject.Extensions.Interception.DynamicProxy/DynamicProxyProxyFactory.cs

    if (targetType.IsInterface)
        {
            reference.Instance = this.generator.CreateInterfaceProxyWithoutTarget(targetType, additionalInterfaces, InterfaceProxyOptions, wrapper);
        }
        else
        {
            object[] parameters = context.Parameters.OfType<ConstructorArgument>()
                .Select(parameter => parameter.GetValue(context, null))
                .ToArray();
            reference.Instance = this.generator.CreateClassProxy(targetType, additionalInterfaces, ProxyOptions, parameters, wrapper);
        }

可以看到ninject的动态代理扩展只是将ConstructorArguments传递给Castle Dynamic Proxy Generator。

因此 - 无需更改 ninject 扩展或创建自己的扩展 - 您需要将所有依赖项作为构造函数参数传递。您还可以尝试属性/方法注入是否有效(请参阅https://github.com/ninject/ninject/wiki/Injection-Patterns)。

如果您控制代码,则可以将接口添加到代理类,然后使用“带目标的接口代理”。这允许将代理实例化与目标(代理类)实例化分离——>目标可以注入依赖项ctor,而无需对 ninject(-extensions)进行任何更改。

澄清:具有以下应代理的类:

public interface IBar { }

public class Foo 
{
     public Foo(IBar bar)
     {
     }
}

以及以下绑定:

Bind<Foo>().ToSelf().Intercept().With<SomeInterceptor>();
Bind<IBar>().To<Bar>();

然后Foo从 ninject 容器中检索 a:

IResolutionRoot.Get<Foo>();

不会工作。

将所有构造函数参数放在 ninject 上下文中以使其工作

但是,我们可以更改检索Foo以使其工作:

var bar = IResolutionRoot.Get<IBar>();
IResolutionRoot.Get<Foo>(new ConstructorArgument("bar", bar);

现在这是次优的,因为 ninject 不会自动进行依赖解析。

向代理类添加接口以使其更好地工作

我们可以通过使用“带目标的接口代理”来解决这个问题。首先,我们为代理类添加一个接口:

public interface IFoo{ }

public class Foo : IFoo
{
     public Foo(IBar bar)
     {
     }
}

然后我们将绑定更改为:

Bind<IFoo>().To<Foo>().Intercept().With<SomeInterceptor>();

然后Foo从 ninject 容器中检索 a:

IResolutionRoot.Get<Foo>();

作品。

另一个可能更简单(&uglier?)的解决方案 根据@Daniel,这可行:将两个构造函数添加到代理类型:

  • 一个protected没有参数的构造函数。这个是给 DynamicProxy 创建代理的。
  • 一个public/internal带有参数的构造函数,用于 ninject 实例化代理类型。

Ninject 将自动选择具有最多可解析参数的构造函数。

于 2014-03-12T11:31:45.750 回答
3

另一种方法是对所有具有[Logging]属性方法的类使用基于约定的绑定。但是,这意味着向[Logging]方法添加属性会影响对象的绑定,这可能是不希望的。

所以这就是它的工作方式(验证工作):

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class LoggingAttribute : Attribute
{
}

public interface IClassNotToBeIntercepted
{
    void DoSomething();
}

public class ClassNotToBeIntercepted : IClassNotToBeIntercepted
{
    public void DoSomething() { }
}

public interface IClassToBeIntercepted
{
    void DoNotLogThis();
    void LogThis();
    void LogThisAsWell();
}

public class ClassToBeIntercepted : IClassToBeIntercepted
{
    public void DoNotLogThis() { }

    [Logging]
    public void LogThis() { }

    [Logging]
    public void LogThisAsWell() { }
}

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine("interceptor before {0}", BuildLogName(invocation));

        invocation.Proceed();

        Console.WriteLine("interceptor after {0}", BuildLogName(invocation));
    }

    private static string BuildLogName(IInvocation invocation)
    {
        return string.Format(
            "{0}.{1}", 
            invocation.Request.Target.GetType().Name,
            invocation.Request.Method.Name);
    }
}

public class DemoModule : NinjectModule
{
    public override void Load()
    {
        this.Bind(convention => convention
            .FromThisAssembly()
            .SelectAllClasses()
            .Where(ContainsMethodWithLoggingAttribute)
            .BindDefaultInterface()
            .Configure(x => x
                .Intercept()
                .With<LoggingInterceptor>()));

        this.Bind<IClassNotToBeIntercepted>()
            .To<ClassNotToBeIntercepted>();
    }

    private static bool ContainsMethodWithLoggingAttribute(Type type)
    {
        return type
            .GetMethods()
            .Any(method => method.HasAttribute<LoggingAttribute>());
    }
}

和一个测试:

    [Fact]
    public void InterceptorTest()
    {
        var kernel = new StandardKernel();
        kernel.Load<DemoModule>();

        kernel.Get<IClassNotToBeIntercepted>()
            .DoSomething();

        kernel.Get<IClassToBeIntercepted>()
            .LogThis();
    }

结果如下控制台输出:

interceptor before ClassToBeIntercepted.LogThis
interceptor after ClassToBeIntercepted.LogThis
于 2014-03-13T10:18:18.257 回答