在上一篇文章中介绍Java注解的时候,多次提到了Java的反射API。与javax.lang.model不同的是,通过反射API可以获取程序在运行时刻的内部结构。反射API中提供的动态代理也是非常强大的功能,可以原生实现AOP中 的方法拦截功能。正如英文单词reflection的含义一样,使用反射API的时候就好像在看一个Java类在水中的倒影一样。知道了Java类的内部 结构之后,就可以与它进行交互,包括创建新的对象和调用对象中的方法等。这种交互方式与直接在源代码中使用的效果是相同的,但是又额外提供了运行时刻的灵活性。使用反射的一个最大的弊端是性能比较差。相同的操作,用反射API所需的时间大概比直接的使用要慢一两个数量级。不过现在的JVM实现中,反射操作的性能已经有了很大的提升。在灵活性与性能之间,总是需要进行权衡的。应用可以在适当的时机来使用反射API。

基本用法

Java 反射API的第一个主要作用是获取程序在运行时刻的内部结构。这对于程序的检查工具和调试器来说,是非常实用的功能。只需要短短的十几行代码,就可以遍历出来一个Java类的内部结构,包括其中的构造方法、声明的域和定义的方法等。这不得不说是一个很强大的能力。只要有了java.lang.Class类 的对象,就可以通过其中的方法来获取到该类中的构造方法、域和方法。对应的方法分别是getConstructor、getField和getMethod。这三个方法还有相应的getDeclaredXXX版本,区别在于getDeclaredXXX版本的方法只会获取该类自身所声明的元素,而不会考虑继承下来的。Constructor、Field和Method这三个类分别表示类中的构造方法、域和方法。这些类中的方法可以获取到所对应结构的元数据。

反射API的另外一个作用是在运行时刻对一个Java对象进行操作。 这些操作包括动态创建一个Java类的对象,获取某个域的值以及调用某个方法。在Java源代码中编写的对类和对象的操作,都可以在运行时刻通过反射API来实现。考虑下面一个简单的Java类。

1 class MyClass {
2     public int count;
3     public MyClass(int start) {
4         count = start;
5     }
6     public void increase(int step) {
7         count = count + step;
8     }
9 } 

使用一般做法和反射API都非常简单。

 1 MyClass myClass = new MyClass(0); //一般做法
 2 myClass.increase(2);
 3 System.out.println("Normal -> " + myClass.count);
 4 try {
 5     Constructor constructor = MyClass.class.getConstructor(int.class); //获取构造方法
 6     MyClass myClassReflect = constructor.newInstance(10); //创建对象
 7     Method method = MyClass.class.getMethod("increase", int.class);  //获取方法
 8     method.invoke(myClassReflect, 5); //调用方法
 9     Field field = MyClass.class.getField("count"); //获取域
10     System.out.println("Reflect -> " + field.getInt(myClassReflect)); //获取域的值
11 } catch (Exception e) {
12     e.printStackTrace();
13 } 

由于数组的特殊性,Array类提供了一系列的静态方法用来创建数组和对数组中的元素进行访问和操作。

1 Object array = Array.newInstance(String.class, 10); //等价于 new String[10]
2 Array.set(array, 0, "Hello");  //等价于array[0] = "Hello"
3 Array.set(array, 1, "World");  //等价于array[1] = "World"
4 System.out.println(Array.get(array, 0));  //等价于array[0]

使用Java反射API的时候可以绕过Java默认的访问控制检查,比如可以直接获取到对象的私有域的值或是调用私有方法。只需要在获取到Constructor、Field和Method类的对象之后,调用setAccessible方法并设为true即可。有了这种机制,就可以很方便的在运行时刻获取到程序的内部状态。

处理泛型

Java 5中引入了泛型的概念之后,Java反射API也做了相应的修改,以提供对泛型的支持。由于类型擦除机制的存在,泛型类中的类型参数等信息,在运行时刻是不存在的。JVM看到的都是原始类型。对此,Java 5对Java类文件的格式做了修订,添加了Signature属性,用来包含不在JVM类型系统中的类型信息。比如以java.util.List接口为例,在其类文件中的Signature属性的声明是<E:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Collection<TE;>;; ,这就说明List接口有一个类型参数E。在运行时刻,JVM会读取Signature属性的内容并提供给反射API来使用。

比如在代码中声明了一个域是List<String>类型的,虽然在运行时刻其类型会变成原始类型List,但是仍然可以通过反射来获取到所用的实际的类型参数。

 1 Field field = Pair.class.getDeclaredField("myList"); //myList的类型是List
 2 Type type = field.getGenericType();
 3 if (type instanceof ParameterizedType) {
 4     ParameterizedType paramType = (ParameterizedType) type;
 5     Type[] actualTypes = paramType.getActualTypeArguments();
 6     for (Type aType : actualTypes) {
 7         if (aType instanceof Class) {
 8             Class clz = (Class) aType;
 9             System.out.println(clz.getName()); //输出java.lang.String
10         }
11     }
12 }  

动态代理

熟悉设计模式的人对于代理模式可 能都不陌生。 代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。代理的存在对于调用者来说是透明的,调用者看到的只是接口。代理对象则可以封装一些内部的处理逻辑,如访问控制、远程通信、日志、缓存等。比如一个对象访问代理就可以在普通的访问机制之上添加缓存的支持。这种模式在RMI和EJB中都得到了广泛的使用。传统的代理模式的实现,需要在源代码中添加一些附加的类。这些类一般是手写或是通过工具来自动生成。JDK 5引入的动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接 口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者。这种做法实际上相 当于对方法调用进行了拦截。熟悉AOP的人对这种使用模式应该不陌生。但是这种方式不需要依赖AspectJ等AOP框架。

下面的代码用来代理一个实现了List接口的对象。所实现的功能也非常简单,那就是禁止使用List接口中的add方法。如果在getList中传入一个实现List接口的对象,那么返回的实际就是一个代理对象,尝试在该对象上调用add方法就会抛出来异常。

 1 public List getList(final List list) {
 2     return (List) Proxy.newProxyInstance(DummyProxy.class.getClassLoader(), new Class[] { List.class },
 3         new InvocationHandler() {
 4             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 5                 if ("add".equals(method.getName())) {
 6                     throw new UnsupportedOperationException();
 7                 }
 8                 else {
 9                     return method.invoke(list, args);
10                 }
11             }
12         });
13  } 

这里的实际流程是,当代理对象的add方法被调用的时候,InvocationHandler中的invoke方法会被调用。参数method就包含了调用的基本信息。因为方法名称是add,所以会抛出相关的异常。如果调用的是其它方法的话,则执行原来的逻辑。

使用案例

Java 反射API的存在,为Java语言添加了一定程度上的动态性,可以实现某些动态语言中的功能。比如在JavaScript的代码中,可以通过 obj["set" + propName]()来根据变量propName的值找到对应的方法进行调用。虽然在Java源代码中不能这么写,但是通过反射API同样可以实现类似 的功能。这对于处理某些遗留代码来说是有帮助的。比如所需要使用的类有多个版本,每个版本所提供的方法名称和参数不尽相同。而调用代码又必须与这些不同的版本都能协同工作,就可以通过反射API来依次检查实际的类中是否包含某个方法来选择性的调用。

Java 反射API实际上定义了一种相对于编译时刻而言更加松散的契约。如果被调用的Java对象中并不包含某个方法,而在调用者代码中进行引用的话,在编译时刻就会出现错误。而反射API则可以把这样的检查推迟到运行时刻来完成。通过把Java中的字节代码增强、类加载器和反射API结合起来,可以处理一些对灵 活性要求很高的场景。

在 有些情况下,可能会需要从远端加载一个Java类来执行。比如一个客户端Java程序可以通过网络从服务器端下载Java类来执行,从而可以实现自动更新 的机制。当代码逻辑需要更新的时候,只需要部署一个新的Java类到服务器端即可。一般的做法是通过自定义类加载器下载了类字节代码之后,定义出 Class类的对象,再通过newInstance方法就可以创建出实例了。不过这种做法要求客户端和服务器端都具有某个接口的定义,从服务器端下载的是 这个接口的实现。这样的话才能在客户端进行所需的类型转换,并通过接口来使用这个对象实例。如果希望客户端和服务器端采用更加松散的契约的话,使用反射API就可以了。两者之间的契约只需要在方法的名称和参数这个级别就足够了。服务器端Java类并不需要实现特定的接口,可以是一般的Java类。

动态代理的使用场景就更加广泛了。需要使用AOP中的方法拦截功能的地方都可以用到动态代理。Spring框架的AOP实现默认也使用动态代理。不过JDK中的动态代理只支持对接口的代理,不能对一个普通的Java类提供代理。不过这种实现在大部分的时候已经够用了。

参考资料

  • Classworking toolkit: Reflecting  generics
  • D?ecorating with dynamic proxies

转载于:https://www.cnblogs.com/zoucaitou/p/4161622.html

java 反射 动态代理相关推荐

  1. 深入理解Java反射+动态代理,java开发面试笔试题

    我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家. 扫描二维码加VX好友,拉你进[程序员面试学习交流群]免费领取.也欢迎各位一起在群里探讨技术. 答: ...

  2. 【java】深入分析Java反射-动态代理 proxy

    1.概述 转载:深入分析Java反射(四)-动态代理 [Java]Java 反射机制浅析 [Java]java代理 静态代理 动态代理 proxy [java]静态代理 proxy 2.动态代理的简介 ...

  3. java反射-动态代理

    转自:https://www.cnblogs.com/throwable/p/12272269.html 动态代理的简介# Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单 ...

  4. 设计模式总结——代理模式以及java的动态代理

    定义 给目标对象一个代理对象,并由代理对象控制对目标对象的引用.联想到生活中就像是海外代购 既然是代理,就说明他要做的事情要比你直接去做要做的多,这就联系到了方法的增强,也就联系到了AOP,面向切面. ...

  5. 代理模式及Java实现动态代理

    代理模式 定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象. 代理模式UML图 在上图中: RealSubject 是原对象 ...

  6. java CGLIB动态代理

    CGLIB动态代理 一:CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成.CGLIB通过继承方式实现代理. 二: ...

  7. Java - JDK动态代理原理

    Java - JDK动态代理原理 前言 一. JDK动态代理源码分析 1.1 生成目标代理类 getProxyClass0 1.1.1 KeyFactory 生成接口的虚引用 1.1.2 ProxyC ...

  8. [Java|面试] 面试被问Java的动态代理机制,能说说吗

    Java的动态代理机制 文章目录 Java的动态代理机制 0. 什么是代理 1.动态代理和静态代理的区别 2. 使用代理的情况 3. 动态代理的构成 4. JDK中的动态代理 5. 手写一个JDK动态 ...

  9. 多态Class对象注册工厂反射动态代理

    运行时类型信息 类型信息(多态&Class对象&注册工厂&反射&动态代理) 一.为什么需要RTTI 二.Class对象 2.1 类字面常量 2.2 泛化的Class引用 ...

最新文章

  1. DropFileName = “svchost.exe“ 问题解决方案
  2. 【css】如何实现响应式布局
  3. Python模块之间的相互引用问题
  4. 点云处理软件Pointscene
  5. Spring的核心机制依赖注入,Junit测试与Java基础Getter和Setter两种方法意义——2017.07.26...
  6. java实现人字拼,人字拼地板拼法大全
  7. 新闻更新延时引发的学习,CACHE的利用。
  8. [2017-08-25]100行CSharp代码利用dynamic写个DSL(特定领域语言)
  9. python找零钱英镑_[求教!!]怎么用python编写一个找零钱的程序??
  10. 在线Excel转CSV工具
  11. discuz自动添加兼容html5标签的音乐播放器
  12. java servlet深入理解_深入理解 Java Servlet
  13. 谷歌浏览器html5插件怎么设置,怎么在谷歌浏览器上安装react插件
  14. 大数据可视化工具在中国的发展历程
  15. 阿里巴巴实习生初面面经
  16. excel表格斜线_老中医坐诊,专治Word表格各种不服
  17. 腾讯课堂老师可以看到学生本人吗?
  18. STM32 CAN模块问题:1、CAN收发器断电后重新上电,CAN模块死机(离线)现象;2、过滤器配置完之后没有过滤效果
  19. 简单理解操作系统之存储器管理
  20. 异常java.lang.IllegalArgumentException: Validation failed for query for method public abstract

热门文章

  1. SLAM笔记(五)光束平差法(Bundle Adjustment)
  2. eclipse 和 google拼音输入法冲突问题
  3. 移植 thttpd Web服务器
  4. 使用.NET自带的类实现DataGrid报表的打印。
  5. 一个请求从 URL 字符串到 HTML 代码的“漫长曲折”之路
  6. 使用WinHttp接口实现HTTP协议Get、Post和文件上传功能
  7. linux进程状态d状态,Linux下进程的状态
  8. 中计算散度的函数_理解 Kullback–Leibler 散度的近似
  9. input反应慢 vue_Vue v-model实时更新带来的输入框卡顿问题
  10. python对象模型 ruby_使用JRuby/Jython实现Ruby/Python的互操作性?