zoukankan      html  css  js  c++  java
  • 我对Java动态代理的理解

    虽然面向应用开发的程序员很少直接使用动态代理技术,但是诸如AOP,事务控制,Spring容器注入等等,实际上都是基于动态代理实现的,可见,动态代理是多么重要。这篇随笔记录了我对动态代理技术原理的一两点理解。

    1. 什么是代理

    1.1 什么是代理

           下图所示为一般地代理模式类图,实际上,代理proxy代替具体对象向客户暴露接口,让客户直接通过代理对象调用真实对象的方法。形象地来说,比如买飞机票的时候,你不必非得去机场售票处买票,可以在市区酒店乃至街道上各个代理售票点购买机票,而代售点,就是机场售票中心的代理。乘客调用代售点的“售卖机票”的功能,由于代售点和机场票务中心是联网的,所以你获得了机场官方承认的有效机票。

     

    图1.1 代理模式类图

    1.2 静态代理与动态代理的区别

            上面用了现实生活中的例子解释了一下代理的基本意义,那么在程序中,有静态代理和动态代理之分,他们的区别是什么?首先,我们用最简单的静态代理的例子来说明什么是静态代理。代码如下。

     1 import java.util.Random;
     2 
     3 public class StaticProxyDemo {
     4     public static void main(String[] args) {
     5         TicketStation ticketStation = new ProxyTicketStation(new OfficalTicketStation());
     6         ticketStation.buyTicket();
     7     }
     8 
     9 }
    10 
    11 /**
    12  * 机票对象,每张机票有一个id编号
    13  */
    14 class Ticket {
    15     private final int id;
    16     public Ticket(int id) {
    17         this.id = id;
    18     }
    19 
    20     public String toString() {
    21         return "Ticket:" + id;
    22     }
    23 }
    24 
    25 
    26 /**
    27  * 机票销售接口
    28  */
    29 interface TicketStation {
    30     Ticket buyTicket();
    31 }
    32 
    33 
    34 /**
    35  * 机场官方售票大厅, 实现机场销售接口
    36  */
    37 class OfficalTicketStation implements TicketStation {
    38     private static Random rand = new Random(47);
    39 
    40     public Ticket buyTicket() {
    41         return new Ticket(rand.nextInt(100));
    42     }
    43 }
    44 
    45 
    46 /**
    47  * 代理售票点,持有一个官方售票大厅的引用
    48  * 表示两者之间是联网的
    49  */
    50 class ProxyTicketStation implements TicketStation {
    51 
    52     private final TicketStation ticketStation;
    53     public ProxyTicketStation(TicketStation station) {
    54         ticketStation = station;
    55     }
    56 
    57     /**
    58      * 代理售票点实际调用官方售票大厅的方法获得机票
    59      */
    60     public Ticket buyTicket() {
    61         System.out.println("连接机场票务中心....");
    62         Ticket ticket = ticketStation.buyTicket();
    63         if (ticket != null) {
    64             System.out.println("您购买的机票编号为: " + ticket);
    65         }
    66         return ticket;
    67     }
    68 }

            我们直接从main方法中看到创建代理对象的过程:

           TicketStation ticketStation = new ProxyTicketStation(new OfficalTicketStation());

    1) 创建真实对象:new OfficalTicketStation()

    2) 创建代理对象,将真实对象作为构造函数传入

    以上两部就是创建静态代理对象的一般步骤,很简单,也很容易理解。之所以能够这样new出一个代理对象,是因为ProxyTicketStation的字节码文件在编译期就已经编译好了,于是,第一次访问该类的静态成员时(构造器也是静态成员),JVM的类加载器就将ProxyTicketStation.class文件加载之后生成了Class对象存入堆内存中,这样就可以通过该类的Class对象创建具体实例了。这里之所以叫静态代理,原因就是ProxyTicketStation.class这个字节码文件,是编译器读取你的源码(ProxyTicketStation.java)生成的并且存放在了磁盘上,你的程序跑起来之后,她只是安静地等待运行过程中需要时被加载。

            那么相对的,动态代理就是程序运行时, 由特定程序而不是编译器,动态地写出来的类的字节码(或者通过远程传入的),然后加载实例产生的代理对象。通过Javassist等开源框架,程序员们可以方便的在程序中生成字节码,当然,Java底层天然就可以写,只不过相对复杂,而动态代理对象的字节码是Java底层写的。

    2. 动态代理的实现原理 

            这里涉及到的JVM虚拟机运行的原理,我就通过这张图简要地说明一下。注意,这里省略了验证、解释等过程。

     

     图2.1 Java编译加载字节码文件

     上节说过,动态代理中,代理对象的字节码文件,是通过Java底层代码写的,那么具体是怎么写的呢?首先,就需要了解一下动态代理使用的三个步骤。

    2.1 动态代理实现三个步骤

            第一步: 实现InvocationHandler接口

            第二步:创建动态代理对象

            第三步:通过代理对象调用方法

            简单地代码实现如下:

            实现InvocationHandler,该类的作用是,对于稍后动态代理类的所有方法的调用,都会重定向到InvocationHandler的invoke方法。在invoke方法中,可以统一处理非业务逻辑,比如记录对象调用的日志等。就是说,把非业务逻辑和业务逻辑分离了。而且自己实现的InvocationHandler可以复用,供不同proxy对象重定向调用invoke,就减少了需要写的类,是整体代码更加紧凑,易于维护。相反,静态代理类如果数量过多,就会使整体代码显得臃肿和零散,难以维护。

     1 import java.lang.reflect.InvocationHandler;
     2 import java.lang.reflect.Method;
     3 import java.util.stream.Stream;
     4 
     5 public class MyInvocationHandler implements InvocationHandler {
     6     private Object proxied;
     7 
     8     public MyInvocationHandler(Object proxied) {
     9         this.proxied = proxied;
    10     }
    11 
    12 
    13     public Object invoke(Object proxy, Method method, Object[] args) {
    14         System.out.println("Invoke method: " + method.getName() + ", args: " + args);
    15         if (args != null)
    16             Stream.of(args).forEach(System.out::println);
    17         try {
    18             if (method.getName().equals("buyTicket")) {
    19                 System.out.println("Buy ticket....");
    20                 Ticket result = (Ticket) method.invoke(proxied, args);
    21                 System.out.println(result);
    22             }
    23         } catch (Throwable t) {
    24             System.err.println(t);
    25         }
    26 
    27         return null;
    28     }
    29 }

            创建代理对象以及调用代理对象方法。创建代理对象proxy时,需要传入三个参数。第一个参数是类加载器,类加载器用于加载代理类字节码文件,一般地,使用当前环境的类加载器即可,如果字节码来自网络或者磁盘,就需要自己实现类加载器了,为什么?因为默认的三个类加载器(启动类加载器,扩展类加载器和系统类加载器)只能加载位于特定目录和classpath下的字节码文件.class。第二个参数是动态创建的代理类,需要实现哪些接口,这里仅需要使用TicketStation接口即可。第三个参数是InvocationHandler对象,这里传入我们实现的handler,这是最重要的参数,因为Java底层在生成代理类的时候,在其每个方法调用中,都会将调用重定向到handler的invoke方法。

     1 import java.lang.reflect.Proxy;
     2 
     3 public class DynamicTicketProxyDemo {
     4     public static void main(String[] args) {
     5         // 创建代理对象
     6         TicketStation official = new OfficalTicketStation();
     7         TicketStation proxy = (TicketStation) Proxy.newProxyInstance(
     8                 TicketStation.class.getClassLoader(),      // 类加载器
     9                 new Class[]{TicketStation.class},          // 代理类需要实现的接口
    10                 new MyInvocationHandler(official));        // InvocationHandler,对代理所有方法的调用都会重定向到该类invoke方法
    11 
    12         // 调用代理对象方法
    13         proxy.buyTicket();
    14     }
    15 }

    2.2 代理对象是怎样创建的

            通过2.1节,我们已经知道了怎样在代码中写动态代理,毫无疑问,最重要的一步是 Proxy.newProxyInstance()方法的调用,理解了这个方法的实现,也就能对动态代理有更深入的理解,那么本节就从源码来了解一下Java动态代理的实现。

     1 public static Object newProxyInstance(ClassLoader loader,
     2                                           Class<?>[] interfaces,
     3                                           InvocationHandler h)
     4         throws IllegalArgumentException
     5     {
     6         Objects.requireNonNull(h);          // invocationHandler必须传
     7 
     8         final Class<?>[] intfs = interfaces.clone();
     9         final SecurityManager sm = System.getSecurityManager();
    10         if (sm != null) {
    11             checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    12         }
    13 
    14         /*
    15          * Look up or generate the designated proxy class.
    16          */
    17         Class<?> cl = getProxyClass0(loader, intfs);      // 1.核心方法,获取动态代理的类对象
    18 
    19         /*
    20          * Invoke its constructor with the designated invocation handler.
    21          */
    22         try {
    23             if (sm != null) {
    24                 checkNewProxyPermission(Reflection.getCallerClass(), cl);
    25             }
    26 
    27             final Constructor<?> cons = cl.getConstructor(constructorParams);    // 2.获取带参数的构造器
    28             final InvocationHandler ih = h;
    29             if (!Modifier.isPublic(cl.getModifiers())) {
    30                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
    31                     public Void run() {
    32                         cons.setAccessible(true);
    33                         return null;
    34                     }
    35                 });
    36             }
    37             return cons.newInstance(new Object[]{h});     // 2.创建代理对象并返回
    38         } catch (IllegalAccessException|InstantiationException e) {
    39             throw new InternalError(e.toString(), e);
    40         } catch (InvocationTargetException e) {
    41             Throwable t = e.getCause();
    42             if (t instanceof RuntimeException) {
    43                 throw (RuntimeException) t;
    44             } else {
    45                 throw new InternalError(t.toString(), t);
    46             }
    47         } catch (NoSuchMethodException e) {
    48             throw new InternalError(e.toString(), e);
    49         }
    50     }

    上述方法做的事情就是:1.必要校验; 2. 创建代理对象的Class对象; 3. 调用Constructor.newInstance来创建代理对象。

            最重要的一步就是Class<?> cl = getProxyClass0(loader, intfs),该方法源代码如下: 1 private static Class<?> getProxyClass0(ClassLoader loader,

     1 private static Class<?> getProxyClass0(ClassLoader loader,
     2                                            Class<?>... interfaces) {
     3         if (interfaces.length > 65535) {
     4             throw new IllegalArgumentException("interface limit exceeded");
     5         }
     6 
     7         // 尝试从缓存中获取代理对象的类对象,如果没有, 
     8         // 就调用ProxyClassFactory工厂方法去创建代理对象的Class对象
     9         return proxyClassCache.get(loader, interfaces);
    10     }

    getProxyClass0()方法中,proxyClassCache缓存在Proxy类加载时就初始化了,作为Proxy类的常量定义:

    1 public class Proxy implements java.io.Serializable {
    2     private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    3         proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    4 
    5 }

    可以看到,proxyClassCache是一个WeakCache缓存对象, 实例化时就传入了ProxyClassFactory代理对象类工厂实例,这样的话,当调用proxyClassCache.get(loader, interfaces),如果缓存中不存在满足当前interfaces的类对象,就会调用ProxyClassFactory工厂来创建代理对象的类对象,ProxyClassFactory嵌入在Proxy的类,它实现了参数化函数接口BiFunction<ClassLoader, Class<?>[], Class<?>>,即通过传入两个参数-----这里是loader和interfaces-----来获得代理类的Class对象,源代码如下,这里省略了部分代码,值保留最核心的部分:

     1 private static final class ProxyClassFactory
     2         implements BiFunction<ClassLoader, Class<?>[], Class<?>>
     3     {
     4         // 代理对象的名称前缀
     5         private static final String proxyClassNamePrefix = "$Proxy";  
     6 
     7         // 原子操作,每创建一个代理对象,就会在前缀后加上一个数字以区分
     8         private static final AtomicLong nextUniqueNumber = new AtomicLong();
     9 
    10         @Override
    11         public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    12 
    13             String proxyPkg = null;     // 要创建代理类的包
    14             int accessFlags = Modifier.PUBLIC | Modifier.FINAL;    //这里都是public final的
    15 // 这里是组装代理类完整的包名 16 for (Class<?> intf : interfaces) { 17 int flags = intf.getModifiers(); 18 if (!Modifier.isPublic(flags)) { 19 accessFlags = Modifier.FINAL; 20 String name = intf.getName(); 21 int n = name.lastIndexOf('.'); 22 String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); 23 if (proxyPkg == null) { 24 proxyPkg = pkg; 25 } else if (!pkg.equals(proxyPkg)) { 26 throw new IllegalArgumentException( 27 "non-public interfaces from different packages"); 28 } 29 } 30 } 31 32 if (proxyPkg == null) { 33 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; 34 } 35 36 /* 37 * 组装出代理类的完整限定名 38 */ 39 long num = nextUniqueNumber.getAndIncrement(); 40 String proxyName = proxyPkg + proxyClassNamePrefix + num; 41 42 /* 43 * 最终要的代码,生成代理类的字节码 44 */ 45 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 46 proxyName, interfaces, accessFlags); 47 try { 48 return defineClass0(loader, proxyName, 49 proxyClassFile, 0, proxyClassFile.length); 50 } catch (ClassFormatError e) { 51 /* 52 * A ClassFormatError here means that (barring bugs in the 53 * proxy class generation code) there was some other 54 * invalid aspect of the arguments supplied to the proxy 55 * class creation (such as virtual machine limitations 56 * exceeded). 57 */ 58 throw new IllegalArgumentException(e.toString()); 59 } 60 } 61 }

    上述代码中,最重要的一步就是byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 这一步是调用ProxyGenerator来生成代理类字节码文件,如同Javaassist框架可以方便地生成类字节码一样,ProxyGenerator封装了底层代码来生成代理类字节码。回忆一下JVM加载过程,通常情况下,JVM类记载器运行时加载的是编译期生成并保存在磁盘特定路径中的.class文件,既然编译器能生成.class字节码文件,Java当然能够支持程序员自己写程序在程序运行时生成.class字节码文件喽,这里ProxyGenerator.generateProxyClass就是Java实现的这样一种过程,只不过封装了复杂的底层逻辑。我们姑且只需要知道ProxyGenerator.generateProxyClass生成了代理类的.class文件即可。

            既然生成了.class字节码文件,那么接下来呢?自然是加载并初始化这个类,这一过程就是调用类加载器将.class文件读入Java虚拟机内存中, 经过一系列的验证,内存分配,解析之后,初始化该类,即执行静态代码块或者初始化静态成员。这一过程,就是Proxy类中这个方法执行的:

    private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);

    我们看到这个方法时native的,表示加载验证初始化代理类的过程是更底层的实现,这里就不必继续深究下去了。总之,该方法会返回给我们一个代理类的Class对象。跳转到Proxy.newProxyInstance方法源码中去,接下来就是获取构造器对象,然后通过构造器的newInstance方法,生成代理对象实例,最终返回给我们。值得注意的是,这里获取的构造器 final Constructor<?> cons = cl.getConstructor(constructorParams)是带参数constructorParams的构造器,该包括该构造器代理类是由 ProxyGenerator.generateProxyClass()方法写的,constructorParams参数在Proxy类中有定义:

    1 public class Proxy implements java.io.Serializable {
    2 
    3     /** parameter types of a proxy class constructor */
    4     private static final Class<?>[] constructorParams =
    5         { InvocationHandler.class };
    6 }

    即,代理对象的构造器参数是需要传入InvocationHandler实例,这也是为什么Proxy.newProxyInstance()方法必须传入InvocationHandler对象的原因,最后调用return cons.newInstance(new Object[]{h});方法,其中h就是我们传入的InvocationHandler对象, 通过这个构造器传入的InvocationHandler对象,生成的代理对象,被调用任何方法时,都会重定向到InvocatoinHandler.invoke()方法上去,这都是底层写的字节码来实现的。

            还有一点,就是在生成代理类的字节码时候,所有方法都是public final修饰的,即生成的代理类是不能重写。

  • 相关阅读:
    设计模式-设计原则(Design Principle)
    设计模式-装饰者模式(Decorator Pattern)
    C++-copy constructor、copy-assignment operator、destructor
    cocos2dx3.0-tinyxml在Android环境下解析xml失败的问题
    XACML-条件评估(Condition evaluation),规则评估(Rule evaluation),策略评估(Policy evaluation),策略集评估(PolicySet evaluation)
    XACML-<Target> 元素的结构与相关的评估
    XACML-PolicySet与request结构简介
    从接口自动化测试框架设计到开发(八)--python操作数据库
    从接口自动化测试框架设计到开发(七)--cookie处理
    从接口自动化测试框架设计到开发(六)--持续集成jenkins
  • 原文地址:https://www.cnblogs.com/yxlaisj/p/11481580.html
Copyright © 2011-2022 走看看