java中静态代理,动态代理知识的补充

时间:2023-12-24 10:59:19

文章转载自:http://blog.csdn.net/jialinqiang/article/details/8950989

一、Java动态代理

相对于静态代理的代理类在编译时生成(.class文件),动态代理与其的区别是:动态代理类在运行时在JVM中生成。Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类(实现了InvocationHandler接口)对象,便能动态地获得代理类,避免了静态代理中代理类的急剧膨胀问题。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。

二、所涉及到的API中的类

Java动态代理的相关类位于java.lang.reflect包下,一般主要涉及到以下两个:

(1)InvocationHandler:调用处理器接口,该接口中仅定义了一个方法如下:

——public Object invoke(Object proxy, Method method, Object[] args)

在实际使用时,第一个参数proxy一般是指代理类,method是指被代理的方法的Method对象,args为该方法的参数数组。这个抽象方法在代理类中动态实现。

我们在使用动态代理时要自定义调用处理器InvocationHandlerImpl实现该接口,通过对invoke方法的实现处理对被代理对象的方法访问的控制。InvocationHandlerImpl中包含被代理的对象的引用。

(2)Proxy:辅助生成动态代理类(实际上也是是动态代理类的父类),主要方法有

——protected Proxy(InvocationHandler h)

构造函数,用于给内部的InvocationHandler类型的属性h赋值。

  • 参数h即我们自定义的调用处理器(实现了InvocationHandler接口)的对象。

——public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

获得一个代理类(类对应的Class对象)。

  • loader指定类的加载器。
  • interfaces是代理类要实现的接口,一般是被代理类所拥有的全部接口的数组。

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

返回代理类的一个实例。返回后的代理类可以当作被代理类使用。

  • 参数java.lang.ClassLoader:这是类加载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类加载器来进行加载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类加载器对象。
  • interfaces即代理类所要实现的接口,一般是被代理类所实现的全部接口的数组。
  • h即我们传入的调用处理器对象(实现了InvocationHandler接口)。

该方法封装了前几个方法,简化了我们使用动态代理的过程。

——public static boolean isProxyClass(Class<?> cl)

判断指定的类是否是一个动态代理类。

  • 参数cl即类对应的Class对象。

——public static InvocationHandler getInvocationHandler(Object proxy)

获取指定代理对象所关联的调用处理器。

  • 参数proxy即代理类实例

三、动态代理的过程

1.类之间的关系

java中静态代理,动态代理知识的补充

2.使用动态代理的步骤(细化):

(1)通过实现 InvocationHandler 接口创建自己的调用处理器(持有被代理类实例的引用);

(2)通过为 Proxy 类的静态方法getProxyClass根据指定的 ClassLoader 对象和一组 interface 来创建动态代理类;

(3)通过反射机制获得动态代理类的构造函数的Constructor对象,其唯一参数类型是调用处理器接口类型;

(4)通过反射机制由构造函数的Constructor对象创建动态代理类的实例,构造时调用处理器对象作为参数被传入(在这之前需创建被代理类及调用处理器的实例)。

(5)通过代理对象调用方法。

代码:

  1. //步骤1: InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
  2. // 其内部通常包含指向委托类(即被代理类)实例的引用,用于真正执行分派转发过来的方法调用
  3. InvocationHandler handler = new InvocationHandlerImpl(..); //可以将被代理类对象作为参数传入
  4. // 步骤2:通过 Proxy 为包括 InterfaceX 接口在内的一组接口动态创建动态代理类
  5. Class clazz = Proxy.getProxyClass(classLoader, new Class[] { InterfaceX.class, ... });
  6. //步骤3: 通过反射由生成的动态代理类的Class对象获得其构造函数的Constructor对象
  7. Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
  8. //步骤4: 通过构造函数对象创建动态代理类对象
  9. InterfaceX proxy = (InterfaceX)constructor.newInstance(new Object[] { handler });
  10. //步骤5:通过代理调用方法,此方法即代理方法(假设为request())
  11. proxy.request();

3.简化的步骤

Proxy的静态方法    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)将步骤2、3、4封装起来了,我们可以更方便地使用动态代理:

(1)通过实现 InvocationHandler 接口创建自己的调用处理器(持有被代理类实例的引用);

(2)通过Proxy类的静态方法newProxyInstance(...)创建代理类的实例。

(3)通过代理类对象调用方法。

代码:

  1. //步骤1: 自定义调用处理器类InvocationHandlerImpl(实现 InvocationHandler接口,且持有被代理类的引用)
  2. InvocationHandler handler = new InvocationHandlerImpl(..);
  3. //步骤2: 通过 Proxy 的静态方法newProxyInstance(..)直接创建动态代理类实例
  4. InterfaceX proxy = (InterfaceX)Proxy.newProxyInstance( classLoader,interfaces, handler );
  5. //步骤3:通过代理调用方法,此方法即代理方法(假设为request())
  6. proxy.request();

说明:上述步骤中省略了一些步骤:抽象接口Subject、被代理类RealSubject类及对象的创建,在动态代理还是静态代理的使用中都必须这样做,这里假设这些类已经写好了。还有就是一些参数如接口数组、加载器对象等的取值是根据一些常用的方法得到的(见下面的例子)。

四、动态代理示例

1.抽象角色Subject

  1. public interface Subject
  2. {
  3. public void request();
  4. }

2.真实角色,即被代理的类RealSubject

  1. public class RealSubject  implements Subject
  2. {
  3. @Override
  4. public void request()
  5. {
  6. System.out.println("from real subject");
  7. }
  8. }

3.调用处理器类MyInvocationHandler

  1. public class MyInvocationHandler implements InvocationHandler
  2. {
  3. //持有被代理类的引用
  4. private Subject real;
  5. public MyInvocationHandler(Subject real)
  6. {
  7. this.real=real;
  8. }
  9. public Object invoke(Object proxy, java.lang.reflect.Method method,
  10. Object[] args) throws Throwable
  11. {
  12. System.out.println("before");//可以附加操作控制对真实对象方法的访问
  13. //System.out.println(proxy.getClass().getName()+"---"+proxy.getClass().getSuperclass().getName()+"---"+method.getName());
  14. Object obj=method.invoke(real, args);//执行被代理对象的方法
  15. System.out.println("after");
  16. return obj;//obj是method方法返回的数据
  17. }
  18. }

4.动态代理

  1. public class DynamicTest
  2. {
  3. public static void main(String[] args)
  4. {
  5. Subject real=new RealSubject();
  6. InvocationHandler h=new MyInvocationHandler(real);
  7. //获得被代理类所实现的所有接口的数组,在这里数组中只有Subject.class一个元素
  8. Class[] interfaces= real.getClass().getInterfaces();
  9. //获得类加载器
  10. ClassLoader loader=h.getClass().getClassLoader();
  11. //获得动态代理类的实例
  12. Object s=java.lang.reflect.Proxy.newProxyInstance(loader,interfaces, h);
  13. //通过代理类对象调用方法
  14. Subject sub=(Subject)s;
  15. sub.request();
  16. }
  17. }

五、关于动态代理类

1.包:

如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.pattern.proxy 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.pattern.proxy),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;

验证方式:将Subject的public修饰符去掉,在调用处理器MyInvocationHandler的invoke()方法中使用proxy.getClass().getPackage()获得动态代理类的包进行验证。

2.类修饰符:

该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;

验证方式:在调用处理器MyInvocationHandler的invoke()方法中使用proxy.getClass().getModifiers()获得类的修饰符字段值,然后与帮助文档中的常量字段表对比(或使用Modifier类的相关方法判断)。

3.类名:

格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。

查看Proxy类的源代码中关于类名格式的信息,可以通过相同方式生成两个动态代理类,打印两代理类对象的hashCode值判断是否重复生成代理类。

4.动态代理类的继承关系:

java中静态代理,动态代理知识的补充

(1)与Proxy:

可以看出,Proxy是动态代理类的父类,动态代理类可以调用Proxy中的方法。每个动态代理实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。

(2)接口组的限制:

  • 动态代理类最多实现65535个接口,这是由JVM限制的,在Proxy的源代码中可以很容易发现,当接口数超过65535时,就会throw new IllegalArgumentException("interface limit exceeded");
  • 要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。(见源代码)
  • 这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。(使用Class.forName()进行可见性判断,见源代码)
  • 需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。(在上面的包的说明中已经能证实到,因为一个类只能位于一个包下)

5.代理的方法:

因为动态代理类$ProxyN是Proxy的子类,Proxy又继承于Object,所以$ProxyN可以调用Proxy、Object、它所实现的所有接口中的方法。

(1)使用动态代理类的实例调用Proxy中的方法时,不会对这些方法(Proxy的这些方法都是static的)进行代理(即不会委托到调用处理器的invoke上处理)。

(2)使用动态代理类的实例调用从Object类继承下来的方法时,会对toString()、equals()以及hashCode()这三个方法进行代理(委托到invoke()方法上反射执行),对于其他方法则不代理。

(3)使用动态代理类的实例调用从接口(创建代理实例时传入的接口数组)中实现的方法时,这些方法都会被代理,动态代理有意义的地方就在于此。但是需要注意的是当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

例如:接口数组      Class[] interfaces= new Class[]{Jia.class,Subject.class,};,并且这两个接口中都声明了同样的方法request(),那么当 Subject sub=(Subject)s;sub.request();时并不是调用的Subject中声明的方法,而是Jia中声明的方法(虽然当前的引用类型是Subject,但是Jia的顺序在Subject的前面)。

6.异常处理:

我们必须遵守一个继承原则是:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。同样在此处,动态代理类抛出的异常也要在其父类或父接口方法支持的异常列表中。但是如果invoke()方法抛出的异常不在代理类的父类或父接口方法支持的异常列表中,那么将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

验证方式:在上例中的invoke()方法中抛出异常:throw new ClassNotFoundException();

六、不足

Proxy仅支持interface的代理,这是由java单继承的特点所造成的,但是Proxy的设计已经非常完美了,不完美并不等于不伟大。

《Java 动态代理机制分析及扩展,第 2 部分》这篇文章对动态代理进行了扩展,实现了类的代理。

另外,cglib也可以实现类的代理。