Java 静态代理和动态代理

1. 前言

  代理是Java语言很重要的一种设计模式。其应用非常的广泛,尤其是在如Spring、MyBatis等各种常用框架中更是经常用到。想要明白这些框架某些底层原理,理解动态代理是一个必不可少的前提。

2. 代理是什么

  代理的官方解释就不必多说了,可自行百度。简单地可理解为:A代替B去做一些事。但A它有自己的想法:A在帮B做事前后可能会去做一些其他的事。

3. 静态代理

  静态代理的特点:代理类和被代理类在编译期间就已经确定。

  静态代理关键的地方有两点:一是代理类和被代理类需要实现同一个接口;二是将被代理类的实例对象传入到代理类中

 1 interface ClothFactory {
 2     void produceCloth();
 3 }
 4 
 5 class ClothFactoryProxy implements ClothFactory{
 6 
 7     ClothFactory clothFactory;  //用被代理类的对象实例化
 8 
 9     public ClothFactoryProxy(ClothFactory clothFactory){
10         this.clothFactory = clothFactory;
11     }
12 
13     @Override
14     public void produceCloth() {
15         //前置通知
16         System.out.println("prepare to do something before producing clothes");
17         clothFactory.produceCloth();
18         //后置通知
19         System.out.println("clear something after producing clothes");
20     }
21 }
22 
23 class NikeClothFactory implements ClothFactory{
24 
25     @Override
26     public void produceCloth() {
27         System.out.println("Nike's factory is producing clothes.....");
28     }
29 }
30 
31 public class StaticProxyTest{
32     public static void main(String[] args) {
33         ClothFactoryProxy proxy = new ClothFactoryProxy(new NikeClothFactory());
34         proxy.produceCloth();
35     }
36 }

  静态代理不足的地方:每一个需要被代理的类都要创建一个与之对应的代理类。假如有五十个需要被代理的类那就需要手动写五十个几乎一模一样的代理类,这可一点都不程序猿。真正的程序猿就该将偷懒做到极致,只写一个通用的代理类:无论是哪个类,要代理都来找这个通用的代理类。这就是动态代理所干的活啦

4. 动态代理

  动态代理的关键点本质上和静态代理没有什么区别,还是解决两个关键点:一是代理类和被代理类需要实现同一个接口;二是将被代理类的实例对象传入到代理类中。但作为动态代理那肯定不能和静态代理一样那么直接,针对这两个必须要有上流的解决办法。

  在Java创建代理对象可以直接调用相关的API:

1  public static Object newProxyInstance(ClassLoader loader,
2                                           Class<?>[] interfaces,
3                                           InvocationHandler h)

  第一个参数,类加载器,后面再来说;

  第二个参数,interfaces 是被代理类需要实现的接口。聪明的你应该已经发现这个参数就是用于解决上面所说的第一个关键点,将被代理类需要实现的接口传入就是为了保证创建的代理类和被代理类实现同样的接口。——看到这里,你或许有疑问,没有疑问的话看这里:上文不是提到说只创建一个通用的代理类吗,怎么又要创建和被代理类实现同样接口的代理类,这和静态代理不就一样一样的吗?先留着这个疑问,后面再来说。

  第三个参数,那毫无疑问就是为了解决第二个关键点的。我们先来看一个 InvocationHandler 的实现类例子:

 1 class MyInvocationHandler implements InvocationHandler{
 2     private Object object;  
 3 
 4     public void bind(Object object){
 5         this.object = object;
 6     }
 7 
 8     @Override
 9     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
10         //method 即为被代理类要调用的方法
11         System.out.println("before");
12         Object returnVal = method.invoke(object, args);
13         System.out.println("after");
14         return returnVal;
15     }
16 }

  其中 object 对象就是被代理类的实例对象,bind() 方法是将被代理的实例对象和 InvocationHandler 的实例对象(handler)绑定在一起。回首看 handler 对象是作为第三个参数传入newProxyInstance( , , ) 方法中。因为 object 对象和 handler 绑定在了一起,所以被代理类的对象已经被我们给传入了,不是直接传入的,是通过上流的方法搭 handler 的车进去的,第二个关键点解决啦。

  好!两个关键点都已经解决了。相信你还是一脸懵逼着呢吧。

  其实不难想到既然需要代理对象,那就一定需要一个对应的代理类存在。我们没有手动写代理类却获得了一个代理对象,那这个代理类就只能是 Proxy.newProxyInstance( , , )  方法来帮我们写的啦。那该方法到底帮我创建了一个什么样的代理类呢?且不说代理类具体是什么样的,至少它得解决上文提到得关键点:一是代理类和被代理类需要实现同一个接口;二是将被代理类的实例对象传入到代理类中。所以可以猜测生成的代理类(假如叫:$Proxy123)一定实现了第二个参数所指的接口。一定有一个InvocationHandler 类型的成员变量,其实也不难想到该成员变量一定会用第三个参数来初始化。再进一步推测,它还一定有一个方法会调用 handler.invoke() 方法,因为从上面的例子中可以看到:在该方法中需要帮被代理的对象完成它自己的工作,还需要做一些其他的事情。实际上第三个参数 handler 是传给了 Proxy 类的成员变量,newInstance( , , ) 方法会创建一个代理类来继承 Proxy 并实现第二个参数所指的接口。被创建出来的代理类在实现接口中的方法中调用 handler.invoke() 方法,然后 handler.invoke() 再调用 method.invoke() 来执行真正的被代理的方法。对于该代理类原本的模样,读者有兴趣的话可自行百度了解。最后来看看第一个参数 classloader,被创建出来的代理类需要被加载到虚拟机中才能发挥其作用,所以该参数就是指定一个类加载器将该类加载到Java虚拟机中去发挥作用,一般和被代理类使用同一个类加载器就可以了。

  总结:其实动态代理和静态代理在本质上没啥区别,只不过手动创建代理类的工作由已经有的API来帮我们做了,根据被代理对象的不同动态的生成不同的代理类。因此我们可以省去这些步骤而直接得到一个代理对象。这就是 Proxy.newInstance( , , ) 方法中的秘密噢。

  不足之处,欢迎指正!

posted @ 2020-08-24 11:04  你看月亮的脸在改变呢  阅读(127)  评论(0)    收藏  举报