Castle DynamicProxy 动态代理(C#)

  • Castle Core版本 v4.4.0 Github
  • .net core 2.2
  • 本文章的被代理方法均为同步方法,异步方法不适用。

基本概念


动态代理是实现代理模式的一种方法,而代理模式可以在不修改原有对象的情况下为对象添加新的功能,也是面向切面(AOP)的一种实现。

对现有对象添加新功能,那么相对于调用者来说接口应该是不变的,所以代理对象应该要与被代理对象实现相同的接口。并且一般来说在代理对象中都会有一个被代理对象的引用字段,那么在代理对象中实现的接口就可以通过直接调用被代理对象,并且在调用被代理对象前后添加新的功能逻辑实现添加新功能。

  • 静态代理,手动为一个类创建代理对象,在编译期代理对象就已经存在。
  • 动态代理,在运行时动态创建代理对象。

C# 中的动态代理可以通过System.Reflection.Emit,或者其他第三方类库实现。前者需要一些中间语言的知识,所以这里我选择了Castle DynamicProxy类库。

关于代理模式的更多介绍就不一一介绍了。

NuGet包


在VS的NuGet包管理器中搜索Castle.Core添加到项目中即可。Castle.Core包含了LoggingDynamicProxyDictionaryAdapter。只是这里我们只用到了DynamicProxy。

创建代理对象


ProxyGenerator对象

ProxyGenerator对象的作用是用来生成代理对象,下面的例子说明ProxyGenerator对象的常用使用方式:

ProxyGenerator proxyGenerator = new ProxyGenerator();
SomeInterface proxyClass = proxyGenerator.CreateClassProxy<ImpClass>(new SomeInterceptor());

其中:

public interface SomeInterface
{
    void DoSome();
}

//被代理类,也就是需要拦截这个类中的方法
//往这个类中添加功能
public class ImpClass : SomeInterface
{
    public virtual void DoSome()
    {
        //....
    }
}

注意:方法需要声明为 virtual,即虚方法。否则无法被代理。

从上面的例子可以看到我们是创建了ProxyGenerator的实例对象,然后通过调用实例对象的CreateClassProxy()方法来创建代理对象。那么下面我们来看下CreateClassProxy()的方法签名:

public class ProxyGenerator : IProxyGenerator
{
    //...
    public TClass CreateClassProxy<TClass>(params IInterceptor[] interceptors) where TClass : class;
    //...
}

在上面的方法定义中TClass就是需要被代理的对象,而方法的参数是一个拦截器数组,意思就是创建一个对象通过一系列的拦截器拦截TClass类的方法,而这个泛型方法的返回值也是TClass对象,所以可以被隐式转换为对象实现的接口。

IInterceptor(拦截器)

拦截器这个概念在Castle DynamicProxy中可谓是非常的重要,由上面创建代理对象的ProxyGenerator类就可以知道需要传递IInterceptor类型作为实参。这个IInterceptor负责定义拦截器的行为,也就是要怎么拦截方法,和拦截方法后的动作等。

IInterceptor是一个接口,该接口只有一个方法,负责定义拦截行为:

public interface IInterceptor
{
    void Intercept(IInvocation invocation);
}

可以看到在接口的Intercept()方法中有一个IInvocation的接口对象作为形参,这个接口包含了被拦截(被代理对象)对象和方法的信息,我们可以看下这个接口的定义:

public interface IInvocation
{
    object[] Arguments { get; }
    Type[] GenericArguments { get; }
    MethodInfo Method { get; }
    MethodInfo MethodInvocationTarget { get; }
    object Proxy { get; }
    object ReturnValue { get; set; }
    Type TargetType { get; }

    IInvocationProceedInfo CaptureProceedInfo();
    object GetArgumentValue(int index);
    MethodInfo GetConcreteMethod();
    MethodInfo GetConcreteMethodInvocationTarget();
    void Proceed();
    void SetArgumentValue(int index, object value);
}

其中包含了大量关于被代理对象的信息。

如果我们要创建拦截器就要创建一个类并且继承IInterceptor接口,如下:

public class SomeInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        //Do before...
        try
        {
            invocation.Proceed();
        }catch
        {
            //...                
        }
        //Do after...
    }
}

invocation.Proceed()是执行下一层的拦截器,当该拦截器是拦截器链的最后一个的时候则会直接调用被代理对象的方法。所以当拦截器链中只有一个拦截器时调用该方法也相当于直接调用被代理对象的方法。

到这里我们就可以在把我们创建的拦截器当作实参传递进ProxyGenerator对象进而创建出拥有特定拦截行为的代理对象。

我们就可以在代理对象中对原对象进行异常捕获,日志记录,事务等的处理。

带参数构造函数的被代理对象


在我们上面所示的例子中ImpClass就是一个带有默认无参构造函数的被代理对象,ProxyGenerator对象在调用CreateClassProxy<TClass>(IInterceptor[] interceptors)方法创建代理对象的时候会尝试调用被代理对象的默认构造函数,如果不存在默认构造函数则会抛出异常。

但是更多时候我们的被代理对象都是需要用带参数的构造函数来创建,当我们对这些带有参数的构造函数进行代理的时候应该怎么做?

这时候我们一般是直接查看ProxyGenerator对象中是否有对于有参构造函数的调用。我们可以看到在ProxyGenerator中有一个方法可以传递参数到被代理对象的构造函数中,该方法定义如下:

public object CreateClassProxy(Type classToProxy, object[] constructorArguments, params IInterceptor[] interceptors);

可以注意到该方法并不是泛型方法,所以方法的返回值直接就是一个object,我们需要把它强制转换为我们需要的被代理对象的接口。该方法的第一个参数是被代理对象,第二个参数是一个object数组,这个就是传递进被代理对象的有参构造函数的参数数组,创建代理对象的时候会寻找被代理对象的构造函数形参,直到找到与这个参数数组匹配的构造函数为止,否则会抛出异常。第三个参数就是传递拦截器类型。

假如我们为ImpClass增加一个有参构造函数,如下:

public class ImpClass : SomeInterface
{
    private string _s;

    public ImpClass(string s)
    {
        this._s = s;
    }

    public void DoSome()
    {
        Console.WriteLine(_s);
    }
}

那么我们对该类创建代理对象的代码也需要作如下更改:

ProxyGenerator proxyGenerator = new ProxyGenerator();
object[] args = {"Hello World"};
SomeInterface proxyClass = (SomeInterface)proxyGenerator.CreateClassProxy(ImpClass,args,new SomeInterceptor());

proxyClass.DoSome();

拦截器链


就像上面提到的,invocation.Proceed()是进入下一个拦截器,当在下一个拦截器中也调用了invocation.Proceed()的话就会再进入下一个拦截器,像这样的一个从上到下,然后在从下到上冒泡的过程就构成了一个拦截器链。或者可以这么说创建一层拦截器负责捕获异常,一层拦截器负责日志记录等等。

创建拦截器链并没有什么新的知识,只是针对每个拦截器创建一个继承IInterceptor的对象,然后定义行为。

可以看到CreateClassProxy中的参数是一个IInterceptor的数组,前面的params关键字说明可以以可变参数列表的方式来调用函数传递参数。

public class ProxyGenerator : IProxyGenerator
{
    //...
    public TClass CreateClassProxy<TClass>(params IInterceptor[] interceptors) where TClass : class;
    //...
}

例如,假设我们有两个拦截器Interceptor1Interceptor2那么我们可以这样来创建代理对象:

ProxyGenerator proxyGenerator = new ProxyGenerator();
SomeInterface proxyClass = proxyGenerator.CreateClassProxy<ImpClass>(new Interceptor1(),new Interceptor2());

把这些拦截器都当作实参传递给ProxyGenerator.CreateClassProxy()对象就可以了,拦截器执行的顺序就是按照参数列表里面拦截器的顺序从上往下一层一层调用。

总结


文章介绍了Castle DynamicProxy库的基本使用,和对带有有参构造函数的被代理对象的构造函数实参传递,和拦截器链的一些介绍。总的来说对于Castle DynamicProxy库的使用都是比较简单的。

需要注意的是以上的动态代理都是针对同步方法,异步方法的动态代理要更为复杂一些。

本人的公众号,有空就写写文章这样,谢谢关注。
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐