java代理的深入浅出(一)-Proxy

1.什么是代理

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

按照代理的创建时期,代理类可以分为两种。

静态代理:

由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

实现方式:

1、为每个代理类,写对应的代理类

  1. 优点:简单方便
  2. 缺点:当业务系统中需要大量的代理类时, 定义繁多的代理类

2、使用Aspectj

  1. 使用Aspectj工具,将目标类class类,织入横切逻辑
  2. 优点:因在JVM加载类前,已经将横切的业务逻辑加载到目标类中, 所以在执行效率上非常高
  3. 缺点:不易维护, 修改的话, 还需要重新生成,编译

动态代理:在程序运行时,运用反射机制动态创建而成。

优点:易修改,易维护

缺点:需要动态生成代理类, 在效率上比静态代理相对低

实现的两种方式

1、JDK动态代理

目标对象必须有对应接口定义

2、CGLIB动态代理

目标对象不用有对应接口, 会生成目标类的子类,所以目标类的方法不能是final

Spring AOP实现机制是动态代理, 主要是上面两种方式JDK、CGLIB

2.基本原理

目前Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。 其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。 Proxy类主要用来获取动态代理对象,InvocationHandler接口用来约束调用者实现。

Proxy类:

Porxy类也是在java.lang.reflect,Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

代理类具用以下属性:

  • 代理类是公共的、最终的,而不是抽象的。

  • 未指定代理类的非限定名称。但是,以字符串 "$Proxy" 开头的类名空间应该为代理类保留。

  • 代理类扩展 java.lang.reflect.Proxy。

  • 代理类会按同一顺序准确地实现其创建时指定的接口。

  • 如果代理类实现了非公共接口,那么它将在与该接口相同的包中定义。否则,代理类的包也是未指定的。注意,包密封将不阻止代理类在运行时在特定包中的成功定义,也不会阻止相同类加载器和带有特定签名的包所定义的类。

  • 由于代理类将实现所有在其创建时指定的接口,所以对其 Class 对象调用 getInterfaces 将返回一个包含相同接口列表的数组(按其创建时指定的顺序),对其 Class 对象调用 getMethods 将返回一个包括这些接口中所有方法的 Method 对象的数组,并且调用 getMethod 将会在代理接口中找到期望的一些方法。

  • 如果 Proxy.isProxyClass 方法传递代理类(由 Proxy.getProxyClass 返回的类,或由 Proxy.newProxyInstance 返回的对象的类),则该方法返回 true,否则返回 false。

  • 代理类的 java.security.ProtectionDomain 与由引导类加载器(如 java.lang.Object)加载的系统类相同,原因是代理类的代码由受信任的系统代码生成。此保护域通常被授予 java.security.AllPermission。

  • 每个代理类都有一个可以带一个参数(接口 InvocationHandler 的实现)的公共构造方法,用于设置代理实例的调用处理程序。并非必须使用反射 API 才能访问公共构造方法,通过调用 Proxy.newInstance 方法(将调用 Proxy.getProxyClass 的操作和调用带有调用处理程序的构造方法结合在一起)也可以创建代理实例。

    1. protected Proxy(InvocationHandler h) //使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。
    2. static InvocationHandler getInvocationHandler(Object proxy) //返回指定代理实例的调用处理程序。
    3. static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) //返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
    4. static boolean isProxyClass(Class<?> cl) //当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
    5. static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) //返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

InvocationHandler接口:

InvocationHandler接口也是在java.lang.reflect,唯一的一个方法是invoke如下:

  1. Object invoke(Object proxy, Method method, Object[] args)

这个方法有三个参数,其中第二和第三个参数都比较好理解,一个是被拦截的方法,一个是该方法的参数列表。关键是第一个参数。按照doc文档的解析,proxy - the proxy instance that the method was invoked on也就是说,proxy应该是一个代理实例(动态代理类)。

3.示例

接口与实现类:

  1. public interface UserService {
  2. void addUser(long cardId);
  3. }
  4. public class UserServiceImpl implements UserService {
  5. public void addUser(long cardId) {
  6. System.out.println("cardId>>>>>"+cardId);
  7. }
  8. }

代理类的创建和拦截处理类

  1. public class UserInvacationHandler implements InvocationHandler {
  2. private Object target;
  3. public Object getProxyInstance(Object target){
  4. this.target =target;
  5. return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
  6. }
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. Object result = null;
  9. System.out.println("before target method.....");
  10. result = method.invoke(target,args);
  11. System.out.println("after target method.....");
  12. return result;
  13. }
  14. }

代理测试类

  1. public class ProxyTest {
  2. public static void main(String[] args){
  3. UserInvacationHandler userInvacationHandler = new UserInvacationHandler();
  4. UserService userService = (UserService) userInvacationHandler.getProxyInstance(new UserServiceImpl());
  5. userService.addUser(1L);
  6. }
  7. }

代理类反编译

生成代理类的方法Proxy.newProxyInstance(), 其源码核心代码是ProxyGenerator.generateProxyClass(String paramString, Class[] paramArrayOfClass)

  1. public class ProxyClassFile {
  2. public static void main(String[] args){
  3. String proxyName = "UserServiceProxy";
  4. UserService a = new UserServiceImpl();
  5. Class[] interfaces = a.getClass().getInterfaces();
  6. byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, interfaces);
  7. File f = new File("D:/work/code/middleware/study/proxy/UserServiceProxy.class");
  8. try{
  9. FileOutputStream fos = new FileOutputStream(f);
  10. fos.write(bytes);
  11. fos.flush();
  12. fos.close();
  13. }catch(FileNotFoundException e){
  14. e.printStackTrace();
  15. }catch(IOException e1){
  16. e1.printStackTrace();
  17. }
  18. }
  19. }

反编译

  1. import com.longchao.proxy.UserService;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. import java.lang.reflect.UndeclaredThrowableException;
  6. public final class UserServiceProxy extends Proxy
  7. implements UserService
  8. {
  9. private static Method m1;
  10. private static Method m3;
  11. private static Method m0;
  12. private static Method m2;
  13. public UserServiceProxy(InvocationHandler paramInvocationHandler)
  14. throws
  15. {
  16. super(paramInvocationHandler);
  17. }
  18. public final boolean equals(Object paramObject)
  19. throws
  20. {
  21. try
  22. {
  23. return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
  24. }
  25. catch (RuntimeException localRuntimeException)
  26. {
  27. throw localRuntimeException;
  28. }
  29. catch (Throwable localThrowable)
  30. {
  31. }
  32. throw new UndeclaredThrowableException(localThrowable);
  33. }
  34. public final void addUser(long paramLong)
  35. throws
  36. {
  37. try
  38. {
  39. this.h.invoke(this, m3, new Object[] { Long.valueOf(paramLong) });
  40. return;
  41. }
  42. catch (RuntimeException localRuntimeException)
  43. {
  44. throw localRuntimeException;
  45. }
  46. catch (Throwable localThrowable)
  47. {
  48. }
  49. throw new UndeclaredThrowableException(localThrowable);
  50. }
  51. public final int hashCode()
  52. throws
  53. {
  54. try
  55. {
  56. return ((Integer)this.h.invoke(this, m0, null)).intValue();
  57. }
  58. catch (RuntimeException localRuntimeException)
  59. {
  60. throw localRuntimeException;
  61. }
  62. catch (Throwable localThrowable)
  63. {
  64. }
  65. throw new UndeclaredThrowableException(localThrowable);
  66. }
  67. public final String toString()
  68. throws
  69. {
  70. try
  71. {
  72. return (String)this.h.invoke(this, m2, null);
  73. }
  74. catch (RuntimeException localRuntimeException)
  75. {
  76. throw localRuntimeException;
  77. }
  78. catch (Throwable localThrowable)
  79. {
  80. }
  81. throw new UndeclaredThrowableException(localThrowable);
  82. }
  83. static
  84. {
  85. try
  86. {
  87. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
  88. m3 = Class.forName("com.longchao.proxy.UserService").getMethod("addUser", new Class[] { Long.TYPE });
  89. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  90. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  91. return;
  92. }
  93. catch (NoSuchMethodException localNoSuchMethodException)
  94. {
  95. throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
  96. }
  97. catch (ClassNotFoundException localClassNotFoundException)
  98. {
  99. }
  100. throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  101. }
  102. }

从源码发现, addUser方法(或其他方法)都会调用this.h.invoke(this, m3, null);

this.h 是其父类Proxy的protected InvocationHandler h; 即我们自定义的UserInvocationHandler,

m3:

m3 = Class.forName("com.user.UserService").getMethod("addUser", new Class[0]);

4.总结

一个典型的动态代理创建对象过程可分为以下四个步骤:

  • 1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);

  • 2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类

    Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

  • 3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型

    Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

  • 4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入

    Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

    为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。

    生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))

5美中不足

诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

java代理的深入浅出(一)-Proxy的更多相关文章

  1. java代理的深入浅出(二)-CGLIB

    java代理的深入浅出(二)-CGLIB 1.基本原理 CGLIB的原理就是生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法.在子类中拦截所有父类方法的调用,拦截下来交给设置的Me ...

  2. java代理的深入浅出(三)-JavaAssist,ASM

    简介 类似字节码操作方法还有ASM.几种动态编程方法相比较,在性能上Javassist高于反射,但低于ASM,因为Javassist增加了一层抽象.在实现成本上Javassist和反射都很低,而ASM ...

  3. java 动态代理范例 InvocationHandler与Proxy

    java 动态代理范例 InvocationHandler与Proxy,拦截与代理 java.lang.reflect.Proxy,Proxy 提供用于创建动态代理类和实例的静态方法.newProxy ...

  4. 关于利用动态代理手写数据库连接池的异常 java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to java.sql.Connection

    代码如下: final Connection conn=pool.remove(0); //利用动态代理改造close方法 Connection proxy= (Connection) Proxy.n ...

  5. 浅谈Java代理一:JDK动态代理-Proxy.newProxyInstance

    浅谈Java代理一:JDK动态代理-Proxy.newProxyInstance java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口.目标接口的类加载器以及Inv ...

  6. 使用browsermob代理出现错误java.lang.NoClassDefFoundError: org/littleshoot/proxy/HttpFiltersSource

    使用browsermob代理做埋点数据,maven配置的包如下 <dependency> <groupId>net.lightbody.bmp</groupId> ...

  7. Java-Spring:java.lang.ClassCastException: com.sun.proxy.$Proxy* cannot be cast to***问题解决方案

    java.lang.ClassCastException: com.sun.proxy.$Proxy* cannot be cast to***问题解决方案 临床表现: 病例: 定义代理类: @Tra ...

  8. Java代理模式/静态代理/动态代理

    代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...

  9. Java代理(jdk静态代理、动态代理和cglib动态代理)

    一.代理是Java常用的设计模式,代理类通过调用被代理类的相关方法,并对相关方法进行增强.加入一些非业务性代码,比如事务.日志.报警发邮件等操作. 二.jdk静态代理 1.业务接口 /** * 业务接 ...

随机推荐

  1. redhat 安装配置samba实现win共享linux主机目录

    [转]http://blog.chinaunix.net/uid-26642180-id-3135941.html redhat 安装配置samba实现win共享linux主机目录 2012-03-1 ...

  2. 用sql实现汉字转拼音

    有时我们会需要将汉字转为拼音,例如需要将省市转为拼音后当做编码存储(尽管国家有统一的标识码,但有时候我们还是会用到),网络上也有工具提供汉字转拼音的功能,但各有优劣,一般转拼音后还会存在带声调的字母, ...

  3. TcpListener 示例

    using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; class ...

  4. adb shell dumpsys

    adb shell dumpsys activity activities -- class/packagename adb shell dumpsys batterystate --reset   ...

  5. 写Java程序的三十个基本规则【新手必读】

    (1) 类名首字母应该大写.字段.方法以及对象(句柄)的首字母应小写.对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母.例如:  ThisIsAClassName  this ...

  6. 在Eclipse中设置文件的默认打开方式

    在Eclipse中,我们可以设置jsp.xml.js.sql等文件默认打开方式: ①.打开配置选项 ②.找到文件设置 ③.选中我们要设置的文件,默认即可:

  7. oracle 字段类型详解

    CHAR 固定长度字符串 最大长度2000 bytes VARCHAR2 可变长度的字符串 最大长度4000 bytes 可做索引的最大长度749 NCHAR 根据字符集而定的固定长度字符串 最大长度 ...

  8. 《JS权威指南学习总结--6.4检测属性》

    内容要点: js对象可以看做属性的集合,我们经常会检测集合中成员的所属关系-----判断某个属性是否存在于某个对象中,可以通过in运算符,hasOwnPreperty()和propertyIsEnum ...

  9. PAT 团体程序设计天梯赛-练习集 L1-019. 谁先倒

    给出甲.乙两人的酒量(最多能喝多少杯不倒)和划拳记录,请你判断两个人谁先倒. 输入格式: 输入第一行先后给出甲.乙两人的酒量(不超过100的非负整数),以空格分隔.下一行给出一个正整数N(<=1 ...

  10. YII使用PHPExcel导入Excel文件的方法

    1.下载phpexcel,将压缩包中的classes复制到protected/extensions下并修改为PHPExcel. 2.修改YII配置文件config/main.php 'import'= ...