在Java运行环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自Java语言的反射(Reflection)机制。Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
1、Java Reflection API简介
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中。
- Class类:代表一个类。
- Field类:代表类的成员变量(成员变量也称为类的属性)。
- Method类:代表类的方法。
- Constructor类:代表类的构造方法。·Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
下面的DumpMethods类演示了Reflection API的基本作用,它读取命令行参数指定的类名,然后打印这个类所具有的方法信息:
import java.lang.reflect.Method;/*** @title DumpMethods* @description 测试* @author: yangyongbing*/
public class DumpMethods {public static void main(String[] args) throws ClassNotFoundException {// 加载并初始化命令行参数指定的类Class<?> classType = Class.forName(args[0]);// 获得类的所有方法Method[] methods = classType.getDeclaredMethods();for (int i = 0; i < methods.length; i++) {System.out.println(methods[i].toString());}}
}
运行命令“java DumpMethods java.util.Stack”,就会显示java.util.Stack类所具有的方法,程序的打印结果如下:
下面的ReflectTester类进一步演示了Reflection API的基本使用方法。ReflectTester类有一个copy(Object object)方法,这个方法能够创建一个和参数object同样类型的对象,然后把object对象中的所有属性拷贝到新建的对象中,并将它返回。这个例子只能复制简单的JavaBean,假定JavaBean的每个属性都有public类型的getXXX()和setXXX()方法。
package com.yang.springframework.net;import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** @title ReflectTester* @description 测试* @author: yangyongbing*/
public class ReflectTester {public Object copy(Object object) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 获得对象的类型Class<?> classType = object.getClass();System.out.println("Class:" + classType.getName());// 通过默认构造方法创建一个新的对象Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object() {});// 获得对象的所有属性Field[] fields = classType.getDeclaredFields();for (int i = 0; i < fields.length; i++) {Field field = fields[i];String fieldName = field.getName();String firstLetter = fieldName.substring(0, 1).toUpperCase();// 获得和属性对应的getXXX()方法的名字String getMethodName = "get" + firstLetter + fieldName.substring(1);// 获得和属性对应的setXXX()方法的名字String setMethodName = "set" + firstLetter + fieldName.substring(1);// 获得和属性对应的getXXX()方法Method getMethod = classType.getMethod(getMethodName, new Class[]{});// 获得和属性对应的setXXX()方法Method setMethod = classType.getMethod(setMethodName, new Class[]{});// 调用原对象的getXXX()方法Object value = getMethod.invoke(object, new Object());System.out.println(fieldName + ":" + value);// 调用拷贝对象的setXXX()方法setMethod.invoke(objectCopy, new Object[]{value});}return objectCopy;}public static void main(String[] args) throws Exception {Customer customer = new Customer("Tom", 21);customer.setId(Long.valueOf(1));Customer customerCopy = (Customer) new ReflectTester().copy(customer);System.out.println("Copy information:" + customerCopy.getName() + " " + customerCopy.getAge());}}// Customer类是一个JavaBean
class Customer {private Long id;private String name;private int age;public Customer() {}public Customer(String name, int age) {this.name = name;this.age = age;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
ReflectTester类的copy(Object object)方法依次执行以下步骤:
(1)获得对象的类型:
Class<?> classType = object.getClass();System.out.println("Class:" + classType.getName());
在java.lang.Object类中定义了getClass()方法,因此对于任意一个Java对象,都可以通过此方法获得对象的类型。Class类是Reflection API中的核心类,它有以下方法:
- getName():获得类的完整名字。
- getFields():获得类的所有public类型的属性。
- getDeclaredFields():获得类的所有属性。
- getMethods():获得类的所有public类型的方法。
- getDeclaredMethods():获得类的所有方法。
- getMethod(String name,Class<?>…parameterTypes):获得类的public类型的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。
- getDeclaredMethod(String name,Class<?>…parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。
- getConstrutors():获得类的所有public类型的构造方法。
- getDeclaredConstrutors():获得类的所有构造方法。
- getConstrutor(Class<?>…parameterTypes):获得类的public类型的特定构造方法,parameterTypes参数指定构造方法的参数类型。
- getDeclaredConstructor(Class<?>…parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
(2)通过默认构造方法创建一个新的对象:
Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object() {});
以上代码先调用Class类的getConstructor()方法获得一个Constructor对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。
(3)获得对象的所有属性:
Field[] fields = classType.getDeclaredFields();
Class类的getDeclaredFields()方法返回类的所有属性,包括public、protected、默认和private访问级别的属性。
(4)获得每个属性相应的getXXX()和setXXX()方法,然后执行这些方法,把原来对象的属性拷贝到新的对象中:
for (int i = 0; i < fields.length; i++) {Field field = fields[i];String fieldName = field.getName();String firstLetter = fieldName.substring(0, 1).toUpperCase();// 获得和属性对应的getXXX()方法的名字String getMethodName = "get" + firstLetter + fieldName.substring(1);// 获得和属性对应的setXXX()方法的名字String setMethodName = "set" + firstLetter + fieldName.substring(1);// 获得和属性对应的getXXX()方法Method getMethod = classType.getMethod(getMethodName, new Class[]{});// 获得和属性对应的setXXX()方法Method setMethod = classType.getMethod(setMethodName, new Class[]{});// 调用原对象的getXXX()方法Object value = getMethod.invoke(object, new Object());System.out.println(fieldName + ":" + value);// 调用拷贝对象的setXXX()方法setMethod.invoke(objectCopy, new Object[]{value});}
以上代码假定每个属性都有相应的getXXX()和setXXX()方法,并且在方法名中,“get”和“set”的后面一个字母为大写。例如Customer类的name属性对应getName()和setName()方法。Method类的invoke(Object obj,Object args[])方法用于动态执行一个对象的特定方法,它的第1个obj参数指定具有该方法的对象,第2个args参数指定向该方法传递的参数。
下面的InvokeTester类的main()方法中,运用反射机制调用一个InvokeTester对象的add()和echo()方法:
import java.lang.reflect.Method;/*** @title InvokeTester* @description 测试* @author: yangyongbing*/
public class InvokeTester {public int add(int param1,int param2){return param1+param2;}public String echo(String msg){return "echo:"+msg;}public static void main(String[] args) throws Exception {Class<InvokeTester> classType = InvokeTester.class;InvokeTester invokeTester = classType.getConstructor().newInstance();// 调用InvokeTester对象的add()方法Method addMethod = classType.getMethod("add", new Class[]{int.class, int.class});Object result = addMethod.invoke(invokeTester, new Object[]{Integer.valueOf(100), Integer.valueOf(200)});System.out.println(result);// 调用InvokeTester对象的echo()方法Method echoMethod = classType.getMethod("echo", new Class[]{String.class});result = echoMethod.invoke(invokeTester, new Object[]{"Hello"});System.out.println(result);}
}
2、在远程方法调用中运用反射机制
假定在SimpleServer服务器端创建了一个HelloServiceImpl对象,它具有getTime()和echo()方法。HelloServiceImpl类实现了HelloService接口。下面分别是HelloService接口和HelloServiceImpl类的源程序。
SimpleClient客户端如何调用服务器端的HelloServiceImpl对象的getTime()和echo()方法呢?显然,SimpleClient客户端需要把调用的方法名、方法参数类型、方法参数值,以及方法所属的类名或接口名发送给SimpleServer,SimpleServer再调用相关对象的方法,然后把方法的返回值发送给SimpleClient。
为了便于按照面向对象的方式来处理客户端与服务器端的通信,可以把它们发送的信息用Call类来表示:
一个Call对象表示客户端发起的一个远程调用,它包括调用的类名或接口名、方法名、方法参数类型、方法参数值和方法执行结果。
SimpleClient调用SimpleServer端的HelloServiceImpl对象的echo()方法的流程如下:
- (1)SimpleClient创建一个Call对象,它包含了调用HelloService接口的echo()方法的信息。
- (2)SimpleClient通过对象输出流把Call对象发送给SimpleServer。
- (3)SimpleServer通过对象输入流读取Call对象,运用反射机制调用HelloServiceImpl对象的echo()方法,把echo()方法的执行结果保存到Call对象中。
- (4)SimpleServer通过对象输出流把包含了方法执行结果的Call对象发送给SimpleClient。
- (5)SimpleClient通过对象输入流读取Call对象,从中获得方法执行结果。
下面分别是SimpleServer和SimpleClient的源程序:
先运行命令“java remotecall.SimpleServer”,再运行命令“java remotecall.SimpleClient”,SimpleClient端将打印“echo:Hello”。该打印结果是服务器端执行HelloServiceImpl对象的echo()方法的返回值。下图显示了SimpleClient与SimpleServer的通信过程。
3、代理模式
在日常生活中,常常会遇到各种各样的代理。例如小王是一个公司的老板,他请秘书小张代他去房产公司寻找合适的办公楼,小张会把需要租用的办公楼的具体需求告诉房产公司。秘书小张是老板小王的代理,而房产公司是受委托的公司。
代理模式是常用的Java设计模式,它的特征是:代理类与委托类有同样的接口,如下图所示:
代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理类的创建时期,代理类可分为两种:
- 静态代理类:由开发人员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
- 动态代理类:在程序运行时,运用反射机制动态创建而成。
4、总结
Java反射机制是Java语言的一个重要特性。考虑实现一个newInstance(String className)方法,它的作用是根据参数className指定的类名,通过该类的不带参数的构造方法创建这个类的对象,并将其返回。如果不运用Java反射机制,那么必须在newInstance()方法中罗列参数className所有可能的取值,然后创建相应的对象。
以上程序代码很冗长,而且可维护性较差。如果在以后软件的升级版本中去除了一个HelloService4类,或者增加了一个HelloService1001类,就需要修改以上newInstance()方法。
如果运用反射机制,就可以简化程序代码,并且能提高软件系统的可维护性和可扩展性:
Java反射机制在服务器程序和中间件程序中得到了广泛的运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法。此外,有一种对象—关系映射(Object-Relation Mapping,ORM)中间件能够把任意一个JavaBean持久化到关系数据库中。在ORM中间件的实现中,运用Java反射机制来读取任意一个JavaBean的所有属性,或者给这些属性赋值。
在JDK类库中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
- Class类:代表一个类。·Field类:代表类的属性。
- Method类:代表类的方法。
- Constructor类:代表类的构造方法。
- Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
- Proxy类和InvocationHandler接口:提供了生成动态代理类及其实例的方法。