我对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修饰的,即生成的代理类是不能重写。

猜你喜欢

转载自www.cnblogs.com/yxlaisj/p/11481580.html