• 理解java动态代理


          java动态代理是java语言的一项高级特性。在平时的项目开发中,可能很难遇到动态代理的案例。但是动态代理在很多框架中起着不可替代的作用,例如Spring的AOP。今天我们就聊一聊java动态代理的实现原理。

         jdk对于动态代理的支持主要依赖于两个类:Proxy和InvocationHandler。我们先看一下类图。

     

         

      Subject类是主题类,定义了我要做什么。我们需要代理的类即实现Subject接口的RealSubject。

      1.InvocationHandler

      InvocationHandler接口是jdk提供的接口,这个接口只有一个方法

     public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;

          我们先了解下InvocationHandler这个类是做什么。以下是java doc

    * <p>Each proxy instance has an associated invocation handler.
    * When a method is invoked on a proxy instance, the method
    * invocation is encoded and dispatched to the {@code invoke}
    * method of its invocation handler.

      每一个代理实例都会和一个invocation handler关联,准确的说,每一个proxy类都会持有一个InvocationHandler实例,并且将目标函数交给InvcationHandler实例去执行。InvocationHandler只有invoke()这个方法,这个方法即实际被调用的方法。不管代理调用的是何种方法,处理器被调用的一定是invoke()方法。下面我们看看这个方法的参数。

      1. Object proxy。传入的Subject引用,即我们想要真正执行的目标对象。

      2. Method method。Method是java reflection API的一部分。这里传入的method对象,是实际被调用的method方法。

      3. Object[] args。这是方法调用时传入的参数数组。

      了解invoke()方法后,读者一定想知道,Subject的目标方法是怎么被调用的呢?接下来我们继续了解Proxy类。

      2. Proxy

      接下来我们了解下Proxy类是如何与InvocationHandler一共工作的。java doc中对Proxy的介绍如下:

    * provides static methods for creating dynamic proxy
    * classes and instances, and it is also the superclass of all
    * dynamic proxy classes created by those methods.

      Proxy提供了一个静态方法去创建动态代理类,最常用的就是下面这个方法了。

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

      利用newProxyInstance可以动态的创建所需要的代理对象,并且和与它关联的InvocationHandler绑定。参数如下

      1. ClassLoader loader, 加载代理类的类加载器。

      2. Class<?>[] interfaces, 代理类实现的接口。创建的代理类对象,只能强转为该interfaces的子类。

      3. InvocationHandler h, 代理类所关联的InvocationHandler。所有被代理的方法都会通过该InvocationHandler的invoke()方法执行。

      newProxyInstance方法是生成代理类的关键方法,代理类在程序运行的过程中生成,因而叫做动态代理。

      3. 案例

      了解了这两个最重要的类之后,我们需要通过一个实例来帮助我们更好的理解动态代理的运行机制。

      首先我们创建一个Subject接口以及其实现类。

      步骤1. 定义Subject

     1 public interface Subject {
     2 
     3     public void say(String str);
     4 }
     5 
     6 public class SubjectBean implements Subject {
     7 
     8     @Override
     9     public void say(String str) {
    10         System.out.println(str);
    11     }
    12 }

       Subject的say方法是我们需要代理的方法。在该方法的前后我们不妨做一些额外的操作。接下来我们定义我们的InvocationHandler。

      步骤2. 定义InvocationHandler

     1 public class MyInvocationHandle implements InvocationHandler {
     2 
     3     Subject subject;
     4 
     5     public MyInvocationHandle(Subject subject) {
     6         this.subject = subject;
     7     }
     8 
     9     @Override
    10     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    11         System.out.println("pre");
    12         method.invoke(subject, args);
    13         System.out.println("post");
    14         return null;
    15     }
    16 
    17 }

      通过构造器,把被代理的对象传入。

      步骤3. 定义生成代理类方法实现

    public class Main {
    
        public static Subject getProxy(Subject subject){
            return (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
                    subject.getClass().getInterfaces(),
                    new MyInvocationHandle(subject));
        }
    
        public static void main(String[] args) {
            Subject subject = new SubjectBean();
            Subject proxy =  getProxy(subject);
            proxy.say("hello");
        }
    }

      执行main函数,最后输出的结果为:

        

     可见,say()函数真正method.invoke(subject, args)这里完成的。在执行前后可以加入任意代码片段,完成对say()方法的增强操作。

     4. debug

      我们对main方法debug看看,proxy类到底是什么。如下图

      

      com.sun.proxy.$Proxy()类,是Proxy.newProxyInstance被调用后在jvm运行时动态生成的一个对象,命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。至于它为什么可以转为Subject,是因为我们在传入的第二个参数中,规定了它的类型信息。

      这篇文章主要简述了java动态代理的实现机制。如有错误之处,还望读者多多指教。

     

      参考文献:

     《Head First设计模式》

    作者:mayday芋头
    本博客中未标明转载的文章归作者mayday芋头和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    python网络编程,requests模块
    python操作excel
    python加密模块hashlib
    python操作redis
    python操作mysql
    python常用模块3(os和sys模块)
    python打开网站
    python常用模块2
    python模块简介
    mac下开发——环境心得(杂项,持续性更新)
  • 原文地址:https://www.cnblogs.com/maypattis/p/5495801.html
Copyright © 2020-2023  润新知