JDK动态代理


一、基本概念

1.什么是代理?

在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念。代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念。这里引用维基百科上的一句话对代理进行定义:

A proxy is an agent or substitute authorized to act for another person or a document which authorizes the agent so to act.

意思是说:代理指的是一个代理人(或替代品),它被授权代表另外一个人(或文档)。

从这个简明扼要的定义中,可以看出代理的一些特性:1.代理存在的意义就是代表另一个事物。2.代理至少需要完成(或实现)它所代表的事物的功能。

JDK动态代理

2.什么是JAVA静态代理?

JAVA静态代理是指由程序员创建或工具生成的代理类,这个类在编译期就已经是确定了的,存在的。

典型的静态代理模式一般包含三类角色:

1.抽象角色:它的作用是定义一组行为规范。抽象角色一般呈现为接口(或抽象类),这些接口(或抽象类)中定义的方法就是待实现的。

2.真实角色:实现了抽象角色所定义的行为。真实角色就是个普通的类,它需要实现抽象角色定义的那些接口。

3.代理角色:代表真实角色的角色。根据上面代理的定义,我们可以知道代理角色需要至少完成(或实现)真实角色的功能。为了完成这一使命,那么代理角色也需要实现抽象角色所定义的行为(即代理类需要实现抽象角色所定义的接口),并且在实现接口方法的时候需要调用真实角色的相应方法。 

JDK动态代理

上图使用UML类图解释了静态代理的数据模型。

1.接口IFunc代表了抽象角色,定义了一个行为,即方法doSomething()。

2.类RealFunc代表了真实角色,它实现了IFunc接口中定义的方法doSomething()。

3.类ProxyFunc代表了代理角色,它实现了IFunc接口中定义的方法doSomething()。它的实现方式是依赖RealFunc类的,通过持有RealFunc类对象的引用realObj,在ProxyFunc.doSomething()方法中调用了realObj.doSomething()。当然,代理类也可以做一些其他的事情,如图中的doOtherthing()。

通过上面的介绍,可以看出静态代理存在以下问题:

1.代理类依赖于真实类,因为代理类最根本的业务功能是需要通过调用真实类来实现的。那么如果事先不知道真实类,该如何使用代理模式呢?

2.一个真实类必须对应一个代理类,即当有多个真实类RealA、RealB、RealC...的时候,就需要多个代理类ProxyA、ProxyB、ProxyC...。这样的话如果大量使用静态代理,容易导致类的急剧膨胀。该如何解决?

要想解决上述问题,就需要使用下面讲解的JAVA动态代理。

3.什么是JAVA动态代理?

JAVA动态代理与静态代理相对,静态代理是在编译期就已经确定代理类和真实类的关系,并且生成代理类的。而动态代理是在运行期利用JVM的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入JAVA虚拟机执行。现在主流的JAVA动态代理技术的实现有两种:一种是JDK自带的,就是我们所说的JDK动态代理,另一种是开源社区的一个开源项目CGLIB。本文主要对JDK动态代理做讨论。

4.什么是JDK动态代理?

JDK动态代理的实现是在运行时,根据一组接口定义,使用Proxy、InvocationHandler等工具类去生成一个代理类和代理类实例。

JDK动态代理的类关系模型和静态代理看起来差不多。也是需要一个或一组接口来定义行为规范。需要一个代理类来实现接口。区别是没有真实类,因为动态代理就是要解决在不知道真实类的情况下依然能够使用代理模式的问题。

JDK动态代理

图中高亮显示的$Proxy0即为JDK动态代理技术生成的代理类,类名的生成规则是前缀"$Proxy"加上一个序列数。这个类继承Proxy,实现一系列的接口Intf1,Intf2...IntfN。

既然要实现接口,那么就要实现接口的各个方法,即图中的doSomething1(),doSomething2()...doSomethingN()。我们上面介绍静态代理的时候,知道静态代理类本质上是调用真实类去实现接口定义的方法的。但是JDK动态代理中是没有真实类这样的概念的。那么JDK动态代理类是如何实现这些接口方法的具体逻辑呢?答案就在图中的InvocationHandler上。$Proxy0对外只提供一个构造函数,这个构造函数接受一个InvocationHandler实例h,这个构造函数的逻辑非常简单,就是调用父类的构造函数,将参数h赋值给对象字段h。最终就是把所有的方法实现都分派到InvocationHandler实例h的invoke方法上。所以JDK动态代理的接口方法实现逻辑是完全由InvocationHandler实例的invoke方法决定的。

二、样例分析

了解了JDK动态代理的概念后,现在我们动手写个JDK动态代理的代码样例。直观的认识下JDK动态代理技术为我们的做了什么。上面说到了,JDK动态代理主要依靠Proxy和InvocationHandler这两个类来生成动态代理类和类的实例。这两个类都在jdk的反射包java.lang.reflect下面。Proxy是个工具类,有了它就可以为接口生成动态代理类了。如果需要进一步生成代理类实例,需要注入InvocationHandler实例。这点我们上面解释过,因为代理类最终逻辑的实现是分派给InvocationHandler实例的invoke方法的。

闲话休絮,先开始我们的第一步,定义一个接口。这个接口里面定义一个方法helloWorld()。

1 public interface MyIntf {
2     void helloWorld();
3 }

第二步,编写一个我们自己的调用处理类,这个类需要实现InvocationHandler接口

1 public class MyInvocationHandler implements InvocationHandler {
2     @Override
3     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
4         System.out.println(method);
5         return null;
6     }
7 }

InvocationHandler接口只有一个待实现的invoke方法。这个方法有三个参数,proxy表示动态代理类实例,method表示调用的方法,args表示调用方法的参数。在实际应用中,invoke方法就是我们实现业务逻辑的入口。这里我们的实现逻辑就一行代码,打印当前调用的方法(实际应用中不会这么做),虽然实现简单,但是不影响我们解释JDK动态代理的技术。

第三步,直接使用Proxy提供的方法创建一个动态代理类实例。并调用代理类实例的helloWorld方法,检测运行结果。

1 public class ProxyTest {
2     public static void main(String[] args) {
3         System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
4         MyIntf proxyObj = (MyIntf)Proxy.newProxyInstance(MyIntf.class.getClassLoader(),new Class[]{MyIntf.class},new MyInvocationHandler());
5         proxyObj.helloWorld();
6     }
7 }

第三行代码是设置系统属性,把生成的代理类写入到文件。这里再强调一下,JDK动态代理技术是在运行时直接生成类的字节码,并载入到虚拟机执行的。这里不存在class文件的,所以我们通过设置系统属性,把生成的字节码保存到文件,用于后面进一步分析。

第四行代码就是调用Proxy.newProxyInstance方法创建一个动态代理类实例,这个方法需要传入三个参数,第一个参数是类加载器,用于加载这个代理类。第二个参数是Class数组,里面存放的是待实现的接口信息。第三个参数是InvocationHandler实例。

第五行调用代理类的helloWorld方法,运行结果:

public abstract void com.tuniu.distribute.openapi.common.annotation.MyIntf.helloWorld()

分析运行结果,就可以发现,方法的最终调用是分派到了MyInvocationHandler.invoke方法,打印出了调用的方法信息。

到这里,对于JDK动态代理的基本使用就算讲完了。我们做的事情很少,只是编写了接口MyIntf和调用处理类MyInvocationHandler。其他大部分的工作都是Proxy工具类帮我们完成的。Proxy帮我们创建了动态代理类和代理类实例。上面的代码我们设置了系统属性,把生成的字节码保存到class文件。下面我们通过反编译软件(如jd-gui),看下Proxy类为我们生成的代理类是什么样子的。

 1 package com.sun.proxy;
 2 import com.tuniu.distribute.openapi.common.annotation.MyIntf;
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 import java.lang.reflect.UndeclaredThrowableException;
 7 
 8 public final class $Proxy0 extends Proxy implements MyIntf {
 9     private static Method m0;
10     private static Method m1;
11     private static Method m2;
12     private static Method m3;
13 
14     static {
15         try {
16             m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
17             m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
18             m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
19             m3 = Class.forName("com.tuniu.distribute.openapi.common.annotation.MyIntf").getMethod("helloWorld", new Class[0]);
20             return;
21         } catch (NoSuchMethodException localNoSuchMethodException) {
22             throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
23         } catch (ClassNotFoundException localClassNotFoundException) {
24             throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
25         }
26     }
27 
28     public $Proxy0(InvocationHandler paramInvocationHandler) {
29         super(paramInvocationHandler);
30     }
31 
32     public final void helloWorld() {
33         try {
34             this.h.invoke(this, m3, null);
35             return;
36         } catch (Error | RuntimeException localError) {
37             throw localError;
38         } catch (Throwable localThrowable) {
39             throw new UndeclaredThrowableException(localThrowable);
40         }
41     }
42 
43     // 后面省略equals(),hashCode(),toString()三个方法的代码,因为这三个方法和helloWorld()方法非常相似
44 }

这里Proxy为我们生成的代理类叫$Proxy0,继承了Proxy,实现了我们定义的接口MyIntf。每一个JDK动态代理技术生成的代理类的名称都是由$Proxy前缀加上一个序列数0,1,2...。并且都需要继承Proxy类。

$Proxy0类中9-26行代码定义了4个Method字段m0,m1,m2,m3,我们先来看下m3,它描述了我们定义的接口MyIntf中的方法helloWorld。

紧接着下面的32-41行代码就是对helloWorld方法的实现,它的实现非常简单就一句话this.h.invoke(this, m3, null);这行代码就是调用当前对象的h实例的invoke方法,也就是把方法的实现逻辑分派给了h.invoke。这里的h是继承父类Proxy中的InvocationHandler字段(读者可以结合上面的动态代理类图模型或者Proxy源码进一步理解)。

同时$Proxy0提供了一个构造函数(代码28-30行),调用父类的构造函数来注入这个InvocationHandler实例。

$Proxy0中的另外3个Method对象m0,m1,m2分别代表了Object类的hashCode(),equals(),toString()方法,我们知道java中的所有类都是Object的子类(Object类本身除外),这里$Proxy0重写了Object中的这三个方法。这三个方法的实现和helloWorld方法很类似,所以笔者这里就把这段代码省略了,用一行注释(43行代码)解释了下。

行文至此,我们已经感官的认识了运行时生成的代理类结构。揭开了这层面纱,其实JDK动态代理也没什么了。简单的来说就是,JDK动态代理技术可以为一组接口生成代理类,这个代理类也就是一层壳,简单的实现了接口中定义的方法。通过提供一个构造函数传入InvocationHandler实例,然后将方法的具体实现交给它。

 

更多详情见请继续阅读下一页的精彩内容

 

  • 1
  • 2
  • 下一页

相关内容

    暂无相关文章