深入理解JDK动态代理机制

蔚落 2022-06-15 07:08 375阅读 0赞

一、现实生活中的代理?

  1. 在现实生活中,我们常见的有服务器代理商、联想PC代理商、百事可乐、火车票、机票等代理商,为什么会有这些个代理商呢?设想以买火车票为场景,如果我要买一张从广州去长沙的火车票,就必须去火车站排队购票,如果排队的人比较多的话,非常的耽误时间。但有了火车票代理商之后,我就可以直接去找个离我最近的代理商买票,因为这样的代理商不止一个二个,遍布全市各地。 所以代理商的出现不但减轻了火车站售票员的工作压力,同时也为市民购票提供了许多方便。只是代理商会收5块的手续费。从这个示例中可以发现和买票有关的一些名词:火车站、售票、代理商?解释这些名词在程序中代理的含义:火车站:称为目标,售票:目标的最终行为,代理商:和火车站具有同样售票行为的代理商,不过代理商在售票前和售票后会做一些操作,比如查询余票、售票后收取手续费等操作。

二、JDK中的代理

1、特征:

  1. 1)、jdk中代理的一个很重要的特征:**代理类和目标类都拥有相同的接口,所以它们都拥有相同的行为。**代理类的对象本身并不真正提供服务,而是调用目标类对象的相关方法,来提供特定的服务。如:火车票代理商自己并不提供火车票销售的服务,而是调用火车站的售票服务。为顾客提供查询余票信息、火车的运营时间、火车票的销售等服务,这些服务都是来自火车站(目标类)。代理类主要负责为目标类预处理消息(如:身份证信息是否正确)、过滤消息(如:该顾客是否为通缉犯,是否公安监视的人),并把这些消息转发给目标类,以及事后处理消息(收取顾客的手续费)等。
  2. 2)、**必须实现一个或多个接口**

2、代理种类:

1)、静态代理,示例:以销售火车票为例

a、定义一个火车站售票的接口

  1. package proxy;
  2. public interface Ticket {
  3. public void ticket();
  4. }

b、售票接口的实现类

  1. package proxy;
  2. public class TicketImpl implements Ticket {
  3. @Override
  4. public void ticket() {
  5. System.out.println("成功售出一张火车票!");
  6. }
  7. }

c、火车票销售的代理类,与目标类实现了相同的接口

  1. package proxy;
  2. /**
  3. * 火车票销售代理类
  4. */
  5. public class TicketImplProxy implements Ticket {
  6. private TicketImpl ticketImpl; //目标类
  7. public TicketImplProxy(TicketImpl ticketImpl) {
  8. this.ticketImpl = ticketImpl;
  9. }
  10. @Override
  11. public void ticket() {
  12. System.out.println("售票前验证顾客的身份信息…………");
  13. ticketImpl.ticket();
  14. System.out.println("售票后收取顾客的手续费…………");
  15. }
  16. }

d、测试类:

  1. package proxy;
  2. public class StaticProxyTest {
  3. public static void main(String[] args) throws Exception {
  4. TicketImpl ticketImpl = new TicketImpl(); //要代理的目标
  5. TicketImplProxy staticProxy = new TicketImplProxy(ticketImpl);
  6. staticProxy.ticket();
  7. }
  8. }

运行结果:

0_1329746065lu6i.gif

静态代理的缺陷:

  1. 一个代理类只能代理一个目标类,如果在一个系统中想代理多个目标类的话,就得写多个代理类,这将是一件很繁琐的工作。所以在JDK1.3之后,引出了动态代理的概念,动态代理可以在程序运行的时候,通过反射机制动态的生成一个类的字节码文件,并实现目标类相同的接口。

2)、动态代理

1)、介绍:

  1. jdk中的动态代理,主要用到了java.lang.reflect包中的两个类:**Proxy类**和**InvocationHandler接口**,Proxy类用于生成代理类,InvocationHandler接口用于调用目标类的方法之前或之后,做一些处理。比如:记录日志、事务处理、效率测试等,均在该接口的invoke方法中实现。传说中AOP思想的原理就是从这里开始扩展的。

0_1329740229aK0M.gif

2)、动态代理机制深入分析(以ArrayList为代理目标类为例):

1、使用Proxy类的getProxyClass方法,获得ArrayList代理类的字节码文件

  1. Class clazzProxy = Proxy.getProxyClass(ArrayList.class.getClassLoader(), ArrayList.class.getInterfaces());

2、打印代理类的结构信息

获得这个Class对象的字节码文件之后,我们打印出来看看这个类的名字叫什么?

  1. System.out.println("代理类名称:" + clazzProxy.getName());

代理类名称:$Proxy0

通过JDK反射机制,获代理类的结构,比如:它的父类是谁?实现了哪些接口?有哪些个构造方法?有哪些个成员方法?等。。。

下面将通反射机制,获取这个代理类的组织结构信息:

  1. System.out.println("代理类" + clazzProxy.getName() + "的父类:" + clazzProxy.getSuperclass().getName());

代理类$Proxy0的父类:java.lang.reflect.Proxy

  1. Class[] clazzInterfaces = clazzProxy.getInterfaces();
  2. StringBuilder sbInterfaces = new StringBuilder();
  3. for (Class clazzInterface : clazzInterfaces) {
  4. sbInterfaces.append(clazzInterface.getName()).append(",");
  5. }
  6. sbInterfaces.deleteCharAt(sbInterfaces.length()-1);
  7. System.out.println("代理类" + clazzProxy.getName() + "所实现的接口:" + sbInterfaces);

代理类$Proxy0所实现的接口:java.util.List,java.util.RandomAccess,java.lang.Cloneable,java.io.Serializable

  1. System.out.println("代理类" + clazzProxy.getName() + "的访问修饰符:" + clazzProxy.getModifiers());

代理类$Proxy0的访问修饰符:17

由访问修饰符的值可得知代理类的修饰符为:代理类的访问修饰符为:public final,由clazzProxy.getModifiers();得知为17,其中public代表1,final代表16,相加得17。参考java.lang.reflect.Modifier

  1. System.out.println("\n-------------打印代理类" + clazzProxy.getName() + "的构造方法列表-------------");
  2. Constructor[] constructors = clazzProxy.getConstructors();
  3. for (Constructor constructor : constructors) {
  4. System.out.println("访问修饰符:" + constructor.getModifiers());
  5. String name = constructor.getName();
  6. int modifiers = constructor.getModifiers();
  7. StringBuilder sb = new StringBuilder(name);
  8. sb.append('(');
  9. Class[] clazzParameters = constructor.getParameterTypes();
  10. for (Class clazzParameter : clazzParameters) {
  11. sb.append(clazzParameter.getName()).append(",");
  12. }
  13. if (clazzParameters != null && clazzParameters.length != 0) {
  14. sb.deleteCharAt(sb.length()-1);
  15. }
  16. sb.append(')');
  17. System.out.println(sb.toString());
  18. }

-——————打印代理类$Proxy0的构造方法列表——————-

访问修饰符: 1
$Proxy0(java.lang.reflect.InvocationHandler)

  1. System.out.println("\n-------------打印代理类" + clazzProxy.getName() + "的方法列表-------------");
  2. Method[] methods = clazzProxy.getMethods();
  3. for (Method method : methods) {
  4. String name = method.getName();
  5. StringBuilder sb = new StringBuilder(name);
  6. sb.append('(');
  7. Class[] clazzParameters = method.getParameterTypes();
  8. for (Class clazzParameter : clazzParameters) {
  9. sb.append(clazzParameter.getName()).append(",");
  10. }
  11. if (clazzParameters != null && clazzParameters.length != 0) {
  12. sb.deleteCharAt(sb.length()-1);
  13. }
  14. sb.append(')');
  15. System.out.println(sb.toString());
  16. }

打印结果:

  1. -------------打印代理类$Proxy0的方法列表-------------
  2. add(java.lang.Object)
  3. add(int,java.lang.Object)
  4. get(int)
  5. equals(java.lang.Object)
  6. toString()
  7. hashCode()
  8. indexOf(java.lang.Object)
  9. clear()
  10. contains(java.lang.Object)
  11. isEmpty()
  12. lastIndexOf(java.lang.Object)
  13. addAll(java.util.Collection)
  14. addAll(int,java.util.Collection)
  15. iterator()
  16. size()
  17. toArray()
  18. toArray([Ljava.lang.Object;)
  19. remove(java.lang.Object)
  20. remove(int)
  21. set(int,java.lang.Object)
  22. containsAll(java.util.Collection)
  23. removeAll(java.util.Collection)
  24. retainAll(java.util.Collection)
  25. subList(int,int)
  26. listIterator()
  27. listIterator(int)
  28. isProxyClass(java.lang.Class)
  29. getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
  30. getInvocationHandler(java.lang.Object)
  31. newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
  32. wait(long)
  33. wait()
  34. wait(long,int)
  35. getClass()
  36. notify()
  37. notifyAll()

这些方法分别来自List接口、Proxy和Object类,因为代理类会自动实现目标类相同的接口。

  1. //由上述信息可推断出代理类的文件结构(程序中只实现打印代理类的声明部份)
  2. StringBuilder sbProxyClassStruct = new StringBuilder("public final class ");
  3. sbProxyClassStruct.append(clazzProxy.getName())
  4. .append(" extends ").append(clazzProxy.getSuperclass().getName())
  5. .append(" implements ").append(sbInterfaces).append(" { } ");
  6. System.out.println("代理类的结构:" + sbProxyClassStruct);

程序中只打印了代理类的声明部份,结果:

代理类声明部份的结构:public final class $Proxy0 extends java.lang.reflect.Proxy implements java.util.List,java.util.RandomAccess,java.lang.Cloneable,java.io.Serializable { }

由上述信息可推断出代理类的文件结构:

  1. package proxy;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.util.Collection;
  4. import java.util.Iterator;
  5. import java.util.List;
  6. import java.util.ListIterator;
  7. public final class $Proxy0 extends java.lang.reflect.Proxy implements java.util.List,
  8. java.util.RandomAccess, java.lang.Cloneable, java.io.Serializable {
  9. public $Proxy0(InvocationHandler h) {
  10. super(h);
  11. // TODO Auto-generated constructor stub
  12. }
  13. @Override
  14. public boolean add(Object e) {
  15. // TODO Auto-generated method stub
  16. return false;
  17. }
  18. @Override
  19. public void add(int index, Object element) {
  20. // TODO Auto-generated method stub
  21. }
  22. @Override
  23. public boolean addAll(Collection c) {
  24. // TODO Auto-generated method stub
  25. return false;
  26. }
  27. @Override
  28. public boolean addAll(int index, Collection c) {
  29. // TODO Auto-generated method stub
  30. return false;
  31. }
  32. @Override
  33. public void clear() {
  34. // TODO Auto-generated method stub
  35. }
  36. @Override
  37. public boolean contains(Object o) {
  38. // TODO Auto-generated method stub
  39. return false;
  40. }
  41. @Override
  42. public boolean containsAll(Collection c) {
  43. // TODO Auto-generated method stub
  44. return false;
  45. }
  46. @Override
  47. public Object get(int index) {
  48. // TODO Auto-generated method stub
  49. return null;
  50. }
  51. @Override
  52. public int indexOf(Object o) {
  53. // TODO Auto-generated method stub
  54. return 0;
  55. }
  56. @Override
  57. public boolean isEmpty() {
  58. // TODO Auto-generated method stub
  59. return false;
  60. }
  61. @Override
  62. public Iterator iterator() {
  63. // TODO Auto-generated method stub
  64. return null;
  65. }
  66. @Override
  67. public int lastIndexOf(Object o) {
  68. // TODO Auto-generated method stub
  69. return 0;
  70. }
  71. @Override
  72. public ListIterator listIterator() {
  73. // TODO Auto-generated method stub
  74. return null;
  75. }
  76. @Override
  77. public ListIterator listIterator(int index) {
  78. // TODO Auto-generated method stub
  79. return null;
  80. }
  81. @Override
  82. public boolean remove(Object o) {
  83. // TODO Auto-generated method stub
  84. return false;
  85. }
  86. @Override
  87. public Object remove(int index) {
  88. // TODO Auto-generated method stub
  89. return null;
  90. }
  91. @Override
  92. public boolean removeAll(Collection c) {
  93. // TODO Auto-generated method stub
  94. return false;
  95. }
  96. @Override
  97. public boolean retainAll(Collection c) {
  98. // TODO Auto-generated method stub
  99. return false;
  100. }
  101. @Override
  102. public Object set(int index, Object element) {
  103. // TODO Auto-generated method stub
  104. return null;
  105. }
  106. @Override
  107. public int size() {
  108. // TODO Auto-generated method stub
  109. return 0;
  110. }
  111. @Override
  112. public List subList(int fromIndex, int toIndex) {
  113. // TODO Auto-generated method stub
  114. return null;
  115. }
  116. @Override
  117. public Object[] toArray() {
  118. // TODO Auto-generated method stub
  119. return null;
  120. }
  121. @Override
  122. public Object[] toArray(Object[] a) {
  123. // TODO Auto-generated method stub
  124. return null;
  125. }
  126. }

由上述信息得知该代理类只有一个带InvocationHandler参数的构造方法,现在我们来生成这个代理类的实例对象:

  1. //方式1,创建一个内部类,并实现InvocationHandler接口
  2. Constructor proxy1 = clazzProxy.getConstructor(InvocationHandler.class);
  3. System.out.println("------------创建代理类实例,方式1--------------------");
  4. class MyInvocationHandler implements InvocationHandler {
  5. ArrayList target = new ArrayList();
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. String name = method.getName();
  9. System.out.println(name + "方法调用前...");
  10. Object retVal = method.invoke(target, args);
  11. System.out.println(name + "方法调用后的返回结果为:" + retVal);
  12. System.out.println(name + "方法调用后...\n");
  13. return retVal;
  14. }
  15. }
  16. List list1 = (List)proxy1.newInstance(new MyInvocationHandler());
  17. list1.add("zhangsan");
  18. list1.add("lisi");
  19. System.out.println(list1.size());

调用List接口的add方法和size方法后的输出结果:

-——————————创建代理类$Proxy0的实例对象———————————
-—————-创建代理类实例,方式1——————————
add方法调用前…
add方法调用后的返回结果为:true
add方法调用后…

add方法调用前…
add方法调用后的返回结果为:true
add方法调用后…

size方法调用前…
size方法调用后的返回结果为:2
size方法调用后…
2

  1. //方式2
  2. Constructor prox2 = clazzProxy.getConstructor(InvocationHandler.class);
  3. List list2 = (List)prox2.newInstance(new InvocationHandler() {
  4. List target = new ArrayList();
  5. @Override
  6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  7. Object retVal = method.invoke(target, args);
  8. return retVal;
  9. }}
  10. );
  11. //方式3(匿名类),将创建代理类和创建代理类实例对象的步聚合二为一
  12. System.out.println("------------创建代理类实例,方式3--------------------");
  13. List list3 = (List)Proxy.newProxyInstance(
  14. ArrayList.class.getClassLoader(),
  15. ArrayList.class.getInterfaces(),
  16. new InvocationHandler() {
  17. ArrayList target = new ArrayList();
  18. @Override
  19. public Object invoke(Object proxy, Method method,
  20. Object[] args) throws Throwable {
  21. //测试方法的运行效率
  22. long beginTime = System.currentTimeMillis();
  23. Object retVal = method.invoke(target, args);
  24. long endTime = System.currentTimeMillis();
  25. System.out.println(method.getName() + " 方法运行时长为:" + (endTime - beginTime));
  26. return retVal;
  27. }}
  28. );
  29. list3.add("hy");
  30. list3.add("yangxin");
  31. System.out.println(list3.size());

输出结果:

-—————-创建代理类实例,方式3——————————
add 方法运行时长为:0
add 方法运行时长为:0
size 方法运行时长为:0
2

*通过以上代码的分析,可将生成代理类的代码,抽取成一个生成代理的通用方法,并将功能代码用一个对象封装起来(如:测试方法的运行时间,方法运行前或运行后需要做的事情)

  1. /**
  2. * 获得代理对象
  3. * @param target 目标(被代理的对象)
  4. * @param advice 目标对象中的方法被调用前要执行的功能
  5. * @return 目标的代理对象
  6. */
  7. private static Object getProxy(final Object target,final Advice advice) {
  8. return Proxy.newProxyInstance(
  9. target.getClass().getClassLoader(),
  10. target.getClass().getInterfaces(),
  11. new InvocationHandler() {
  12. @Override
  13. public Object invoke(Object proxy, Method method,
  14. Object[] args) throws Throwable {
  15. Object retVal = null;
  16. try {
  17. advice.doBefore(target, method, args);//方法执行前
  18. retVal = method.invoke(target, args);
  19. advice.doAfter(target, method, args, retVal);//方法执行后
  20. } catch (Exception e) {
  21. advice.doThrow(target, method, args, e);//方法抛出异常
  22. } finally {
  23. advice.doFinally(target, method, args);//方法最终执行代码(用于释放数据资源、关闭IO流等操作)
  24. }
  25. return retVal;
  26. }}
  27. );
  28. }

封装功能的对象:

  1. package proxy;
  2. import java.lang.reflect.Method;
  3. /**
  4. * aop接口,提供方法运行前、方法运行后、方法运行中产生Exception、方法最终运行代码
  5. *
  6. */
  7. public interface Advice {
  8. /**
  9. * 方法运行前
  10. * @param target 被代理的目标对象
  11. * @param method 被调用的方法
  12. * @param args 方法的参数
  13. */
  14. public void doBefore(Object target, Method method, Object[] args);
  15. /**
  16. * 方法运行后
  17. * @param target 被代理的目标对象
  18. * @param method 被调用的方法对象
  19. * @param args 方法的参数
  20. * @param retVal 方法的返回值
  21. */
  22. public void doAfter(Object target, Method method, Object[] args, Object retVal);
  23. /**
  24. * 方法运行时产生的异常
  25. * @param target 被代理的目标对象
  26. * @param method 被调用的方法
  27. * @param args 方法参数
  28. * @param e 运行时的异常对象
  29. */
  30. public void doThrow(Object target, Method method, Object[] args, Exception e);
  31. /**
  32. * 最终要执行的功能(如释放数据库连接的资源、关闭IO流等)
  33. * @param target 被代理的目标对象
  34. * @param method 被调用的方法
  35. * @param args 方法参数
  36. */
  37. public void doFinally(Object target, Method method, Object[] args);
  38. }

再建一个日志功能的实现类LogAdvice,用于测试:

  1. package proxy;
  2. import java.lang.reflect.Method;
  3. import java.util.Arrays;
  4. /**
  5. * 日志功能切入类
  6. * @author 杨信
  7. *
  8. */
  9. public class LogAdvice implements Advice {
  10. long beginTime = System.currentTimeMillis();
  11. @Override
  12. public void doBefore(Object target, Method method, Object[] args) {
  13. System.out.println(target.getClass().getSimpleName() +
  14. "." + method.getName() + "方法被调用,参数值:" + Arrays.toString(args));
  15. }
  16. @Override
  17. public void doAfter(Object target, Method method, Object[] args, Object retVal) {
  18. long endTime = System.currentTimeMillis();
  19. System.out.println(target.getClass().getSimpleName() +
  20. "." + method.getName() + "方法运行结束,返回值:" + retVal + ",耗时" + (endTime - beginTime) + "毫秒。");
  21. }
  22. @Override
  23. public void doThrow(Object target, Method method, Object[] args,
  24. Exception e) {
  25. System.out.println("调用" + target.getClass().getSimpleName() +
  26. "." + method.getName() + "方法发生异常,异常消息:");
  27. e.printStackTrace();
  28. }
  29. @Override
  30. public void doFinally(Object target, Method method, Object[] args) {
  31. System.out.println("doFinally...");
  32. }
  33. }

测试生成代理的通用方法:

  1. ArrayList target = new ArrayList();
  2. List list4 = (List)getProxy(target,new LogAdvice());
  3. list4.add("张三");
  4. list4.add("李四");
  5. list4.add("王五");
  6. System.out.println(list4.size());
  7. list4.get(3); //演示异常advice

输出结果:

  1. ArrayList.add方法被调用,参数值:[张三]
  2. ArrayList.add方法运行结束,返回值:true,耗时0毫秒。
  3. doFinally...
  4. ArrayList.add方法被调用,参数值:[李四]
  5. ArrayList.add方法运行结束,返回值:true,耗时0毫秒。
  6. doFinally...
  7. ArrayList.add方法被调用,参数值:[王五]
  8. ArrayList.add方法运行结束,返回值:true,耗时0毫秒。
  9. doFinally...
  10. ArrayList.size方法被调用,参数值:null
  11. ArrayList.size方法运行结束,返回值:3,耗时0毫秒。
  12. doFinally...
  13. 3
  14. ArrayList.get方法被调用,参数值:[3]
  15. 调用ArrayList.get方法发生异常,异常消息:
  16. java.lang.reflect.InvocationTargetException
  17. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  18. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
  19. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
  20. at java.lang.reflect.Method.invoke(Method.java:597)
  21. at proxy.DynamicProxyTest$3.invoke(DynamicProxyTest.java:163)
  22. at $Proxy0.get(Unknown Source)
  23. at proxy.DynamicProxyTest.main(DynamicProxyTest.java:143)
  24. Caused by: java.lang.IndexOutOfBoundsException: Index: 3, Size: 3
  25. at java.util.ArrayList.RangeCheck(ArrayList.java:547)
  26. at java.util.ArrayList.get(ArrayList.java:322)
  27. ... 7 more
  28. doFinally...

完毕,通过动态代理机制,实现了传说中的AOP思想。

发表评论

表情:
评论列表 (有 0 条评论,375人围观)

还没有评论,来说两句吧...

相关阅读

    相关 深入理解JDK动态代理机制

    一、现实生活中的代理?         在现实生活中,我们常见的有服务器代理商、联想PC代理商、百事可乐、火车票、机票等代理商,为什么会有这些个代理商呢?设想以买火车票为

    相关 深入学习jdk动态代理

    何为代理 代理,即代替主角完成一些额外的事情,例如,经纪人作为明星的代理人和出资洽谈片酬,排期等,而正真参与拍戏的还是明星本人,明星拍完戏后,再有经纪人代理明星去清理片酬等