Java反射是Java语言一个很重要的特征,简单剖析下反射的定义、原理、使用、性能及应用场景。
(一)定义
程序运行时,允许改动程序结构或变量类型,这种语言称为动态语言。java不属于动态语言,但提供了RTTI(Run-time Type Identification)运行时类别识别。RTTI分为两种方式,一种是编译运行时已知悉类型,一种是反射机制。
(二)原理
在《深入java虚拟机》中提到,java文件被编译成class文件,JVM类加载器加载class字节码到方法区,然后在堆中生成Class类,Class类可以访问到类的基本信息,如类简单名、类包含路径全名、访问修饰符、字段、方法等信息。
反射中需要使用到的类:
Field类:提供有关类或接口的属性的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)属性或实例属性,简单的理解可以把它看成一个封装反射类的属性的类。
Constructor类:提供关于类的单个构造方法的信息以及对它的访问权限。这个类和Field类不同,Field类封装了反射类的属性,而Constructor类则封装了反射类的构造方法。
Method类:提供关于类或接口上单独某个方法的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
Class类:类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
(三)使用
(1)获取Class
方法一:Class c=Class.forName("java.lang.String")
方法二:对于基本数据类型可以用形如Class c=int.class或Class c=Integer.TYPE的语句
(tips:int.class = Integer.TYPE !=Integer.class)
方法三:Class c=MyClass.class
(2)调用Class中的方法得到你想得到的信息集合,如调用getDeclaredFields()方法得到类所有的属性
Field field = classInstance.getDeclaredField("TEST_TIMES");
int times = (Integer) field.get(classInstance);
System.out.println(times);
反射的性能是低于直接调用的,下次通过测试验证这个结果,测试中尽量避免对象创建等干扰因素。
我们将测试直接访问的耗时、直接反射的耗时、缓存需要查找的函数反射的耗时、使用ReflectAsm的反射耗时。
/**
* 测试反射性能
*
* @author peter_wang
* @create-time 2014-6-13 下午12:54:52
*/
public class ReflectPerformanceDemo {
private static final int TEST_TIMES = 1000000;
private long mNum;
private long mSum;
/**
* @param args
*/
public static void main(String[] args) {
normalInvoke();
normalReflectInvoke();
cacheReflectInvoke();
asmReflectInvoke();
}
/**
* 正常调用方法
*/
private static void normalInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
long time1 = System.currentTimeMillis();
for (long i = 0; i < TEST_TIMES; i++) {
demo.setmNum(i);
demo.mSum += demo.getmNum();
}
long time2 = System.currentTimeMillis();
System.out.println("normal invoke time:" + (time2 - time1));
}
/**
* 常规反射调用方法
*/
private static void normalReflectInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
long time1 = System.currentTimeMillis();
try {
for (long i = 0; i < TEST_TIMES; i++) {
Class<?> c = Class.forName("com.peter.demo.process.reflect.ReflectPerformanceDemo");
Method method = c.getMethod("setmNum", Long.TYPE);
method.invoke(demo, i);
demo.mSum += demo.getmNum();
}
}
catch (Exception e) {
e.printStackTrace();
}
long time2 = System.currentTimeMillis();
System.out.println("normal reflect invoke time:" + (time2 - time1));
}
/**
* 缓存反射调用方法
*/
private static void cacheReflectInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
try {
Class<?> c = Class.forName("com.peter.demo.process.reflect.ReflectPerformanceDemo");
Method method = c.getMethod("setmNum", Long.TYPE);
long time1 = System.currentTimeMillis();
for (long i = 0; i < TEST_TIMES; i++) {
method.invoke(demo, i);
demo.mSum += demo.getmNum();
}
long time2 = System.currentTimeMillis();
System.out.println("cache invoke time:" + (time2 - time1));
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* asm反射调用方法
*/
private static void asmReflectInvoke() {
ReflectPerformanceDemo demo = new ReflectPerformanceDemo();
try {
MethodAccess ma = MethodAccess.get(ReflectPerformanceDemo.class);
int index = ma.getIndex("setmNum");
long time1 = System.currentTimeMillis();
for (long i = 0; i < TEST_TIMES; i++) {
ma.invoke(demo, index, i);
demo.mSum += demo.getmNum();
}
long time2 = System.currentTimeMillis();
System.out.println("asm invoke time:" + (time2 - time1));
}
catch (Exception e) {
e.printStackTrace();
}
}
public long getmNum() {
return mNum;
}
public void setmNum(long mNum) {
this.mNum = mNum;
}
}
测试结果:
normal invoke time:7
normal reflect invoke time:1499
cache invoke time:32
asm invoke time:20
带缓存的反射调用方法速度明显慢于直接调用,采用asm第三方反射库,速度有少量提升。
反射慢的原因:Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
(五)应用场景
(1)“基于构件的编程”,在这种编程方式中,将使用某种基于快速应用开发(RAD)的应用构建工具来构建项目。这是现在最常见的可视化编程方法,通过代表不同组件的图标拖动到图板上来创建程序,然后设置构件的属性值来配置它们。这种配置要求构件都是可实例化的,并且要暴露其部分信息,使得程序员可以读取和设置构件的值。
(2)能够提供在跨网络的远程平台上创建和运行对象的能力,实现java语言的网络可移动性。这被成为远程调用(RMI),它允许一个Java程序将对象分步在多台机器上,这种分步能力将帮助开发人员执行一些需要进行大量计算的任务,充分利用计算机资源,提高运行速度。
(六)范例
破解最可靠的单例模式,在这个例子中,最可靠的又可以lazy loading的是第五种单例模式创建,但是可以通过反射机制破除安全性。
/**
* 安全的单例模式
*
* @author peter_wang
* @create-time 2014-6-10 下午4:45:20
*/
public class SingletonSafe {
private SingletonSafe() {
System.out.println("create singleton");
}
private static class StaticSingleton {
private static SingletonSafe instance = new SingletonSafe();
}
public static SingletonSafe getInstance() {
return StaticSingleton.instance;
}
}
/**
* 测试反射
*
* @author peter_wang
* @create-time 2014-6-10 下午5:08:58
*/
public class ReflectDemo {
/**
* @param args
*/
public static void main(String[] args) {
try {
Class classInstance = Class.forName("com.peter.demo.process.reflect.SingletonSafe");
System.out.println(classInstance.getName());
Constructor cons = classInstance.getDeclaredConstructor(null);
cons.setAccessible(true);
SingletonSafe singletonSafe1 = (SingletonSafe) cons.newInstance(null);
System.out.println("singleton1:" + singletonSafe1.toString());
SingletonSafe singletonSafe2 = (SingletonSafe) cons.newInstance(null);
System.out.println("singleton2:" + singletonSafe2.toString());
Method method1 = classInstance.getDeclaredMethod("getInstance", null);
SingletonSafe singletonSafe3 = (SingletonSafe) method1.invoke(classInstance, null);
System.out.println("singleton3:" + singletonSafe3.toString());
Method method2 = classInstance.getDeclaredMethod("getInstance", null);
SingletonSafe singletonSafe4 = (SingletonSafe) method2.invoke(classInstance, null);
System.out.println("singleton4:" + singletonSafe4.toString());
}
catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果:
com.peter.demo.process.reflect.SingletonSafe
create singleton
singleton1:com.peter.demo.process.reflect.SingletonSafe@2a9931f5
create singleton
singleton2:com.peter.demo.process.reflect.SingletonSafe@2f9ee1ac
create singleton
singleton3:com.peter.demo.process.reflect.SingletonSafe@5f186fab
singleton4:com.peter.demo.process.reflect.SingletonSafe@5f186fab
由测试结果可知,
单例模式可以用反射创建多个对象。
(七)总结
目前的计算机系统的速度,应用开发已不在过于在意性能,而更为注重系统的可维护性和扩展性以及快速开发效率上。上述的测试结果是在大量操作基础上产生的。而在通常的一次业务请求中,反射使用的次数应该是非常少的,只在框架级基础上被使用,在一个高负载的系统中,业务处理的性能将是关键点,而不在于使用的这些反射所带来的性能影响上。而使用反射所带来的开发便利与可维护性可扩展性的提升所带来的价值,是远远高于其所损耗的性能的。