在代理模式中我们介绍的例子其实是一种静态代理,静态代理通常只代理一个类,且需要实现这个类的接口,也就是需要知道它到底代理了什么,很显然这种方式在某些特定的小场景下可以适用,但是要是到了大的层面,例如我希望给所有的控制层加一层日志打印,那么如果一个个实现代理或者实现相同的接口显然是不可能,这时候就需要了动态代理,它利用反射机制,能够事先不需要知道代理的是什么,在运行时再确定。现在大家会很经常听到一个名词叫面向横切面编程,也就是AOP(Aspect Oriented Programming)。

JDK提供了java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类,借助这两个类我们可以完成我们的动态代理,它的入口是Proxy类。

打开Proxy源码,我们可以发现这个类暴露的公共方法其实就4个getProxyClass(), getInvocationHandler() ,isProxyClass() ,newProxyInstance()而且都是静态方法,我们一般只关心newProxyInstance方法

Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):传入类加载器,一组接口再加上动态代理的Handler就能自动为你生成一个经过代理的Bean!接下去我们再聊聊InvocationHandler

我们简单介绍一下原理:

动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,也就是在运行中生成了一个类似被代理类的拷贝类,拥有所有的类结构信息,但是默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务,所以invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用!

例子

定义一个接口:

public interface Foo {

   void doSomething();
}

定义一个实现类:

public class FooImpl implements Foo{
   @Override
   public void doSomething() {
      System.out.println("这是一个实现类");
   }
}

为了实现一个代理的前置通知,我们包装了一下Proxy类的接口:

public class DynamicProxy<T> {
   public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
      //寻找JoinPoint连接点,AOP框架使用元数据定义
      if (true) {
         //执行一个前置通知
         (new BeforeAdvice()).exec();
      }
      //执行目标,并返回结果
      return (T) Proxy.newProxyInstance(loader, interfaces, h);
   }
}

通知类

public interface IAdvice {
   void exec();
}
public class BeforeAdvice implements IAdvice {
    @Override
    public void exec() {
        System.out.println("我是前置通知,我被执行了!");
    }
}

动态代理的Handler类:

public class MyInvocationHandler implements InvocationHandler {

   private Object target=null;

   public MyInvocationHandler(Object target) {
      this.target = target;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("这里可以打印一些必要日志");
      return method.invoke(target, args);
   }
}

最后就是客户端类:

public class Client {
   public static void main(String[] args) {
      Foo foo = new FooImpl();

      InvocationHandler invocationHandler = new MyInvocationHandler(foo);
      Foo foo1 = DynamicProxy.newProxyInstance(foo.getClass().getClassLoader(), foo.getClass().getInterfaces(), invocationHandler);
      foo1.doSomething();
   }
}

输出:

我是前置通知,我被执行了!
这里可以打印一些必要日志
这是一个实现类

小结

要实现动态代理的首要条件是:被代理类必须实现一个接口!当然动态代理不仅仅只有JDK一家,CGLIB可以实现不需要接口也可以实现动态代理的方式。调试时,只要看到类似$Proxy0这样的结构,大家就可以判断这是一个动态代理!

辨析:CGLib与JDK动态代理

JDK动态代理

利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类;

在调用具体方法前调用InvokeHandler来处理。

CGLib动态代理

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

关于两者之间的性能:JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:

1、CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

2、但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

更多详情参见:https://blog.csdn.net/yhl_jxy/article/details/80635012

参考

《设计模式之禅》

https://www.zhihu.com/question/20794107