Java反射机制详细示例及动态代理

时间:2021-03-06 05:07:04

最近学习了Java中的反射.在此做以下总结:

反射可以在程序运行过程中动态获取类的相关信息,包括类由哪个类加载器进行加载,类中的成员变量,成员方法,访问修饰符,返回值类型,构造方法等等;

首先要获取类的Class对象.获取Class对象有三种方法,此处以Student类为例:

  1. Class cls = Class.forName("com.qcc.reflect.entity.Student");
  2. Class cls = Student.class;
  3. 有对象的时候,可以通过getClass()方法来获取Class对象 Student stu = new Student();Class cls = stu.getClass();

第一种方法在Java框架中使用最频繁,有了Class对象后,就可以调用Class对象的相关方法来获取类的所有的信息;

拿获取方法做示例如下:

现有实体类Student和Person:

package com.qcc.reflect.entity;

public class Person {

private String name;
private int age;

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;
}

public Person() {
super();
// TODO Auto-generated constructor stub
}

public void method1() {
System.out.println("person类: method1()");
}

private int method2() {
return 0;
}
}

Student类继承父类Person类

package com.qcc.reflect.entity;

public class Student extends Person {

private int stuno;

public int getStuno() {
return stuno;
}

public void setStuno(int stuno) {
this.stuno = stuno;
}

public Student() {
super();
// TODO Auto-generated constructor stub
}

public String method3(){
return "Student类的公有无参方法method3()";
}

private void method4(int age){
System.out.println("Student类的私有带参方法method4(int age),实际参数为:" + age);
}

}

类的Class对象的getMethods()方法

可以获取当前类以及父类中所有的公有方法;

@Test
public void testGetMethods() throws Exception {
Class cls = Class.forName("com.qcc.reflect.entity.Student");
Method[] methods = cls.getMethods();
for(Method method: methods) {
System.out.println(method);
}
}
运行结果:

public java.lang.String com.qcc.reflect.entity.Student.method3()
public void com.qcc.reflect.entity.Student.setStuno(int)
public int com.qcc.reflect.entity.Student.getStuno()
public java.lang.String com.qcc.reflect.entity.Person.getName()
public void com.qcc.reflect.entity.Person.setName(java.lang.String)
public void com.qcc.reflect.entity.Person.method1()
public int com.qcc.reflect.entity.Person.getAge()
public void com.qcc.reflect.entity.Person.setAge(int)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

类的Class对象的getDeclaredMethods()方法

可以获取当前类中声明的所有方法(包括私有方法);

/**
* 获取当前类中所有声明的方法,包括私有方法
* @throws Exception
*/
@Test
public void testDeclaredMethods() throws Exception{
Class cls = Class.forName("com.qcc.reflect.entity.Student");
Method[] methods = cls.getDeclaredMethods();
for(Method method: methods) {
System.out.println(method);
}
}


运行结果如下:

public java.lang.String com.qcc.reflect.entity.Student.method3()
private void com.qcc.reflect.entity.Student.method4(int)
public void com.qcc.reflect.entity.Student.setStuno(int)
public int com.qcc.reflect.entity.Student.getStuno()

类的Class对象的getMethod()方法

获取当前类及父类中指定的公有方法

@Test
public void testMethod() throws Exception{
Class cls = Class.forName("com.qcc.reflect.entity.Student");
Method method = cls.getMethod("method1");
method.invoke(cls.newInstance());
System.out.println(method);
}
运行结果如下:

person类: method1()
public void com.qcc.reflect.entity.Person.method1()

类的Class对象的getDeclaredMethod()方法

获取当前类中声明的任意方法,包括私有方法.当要执行私有方法时,需要先将方法的访问权限即method的accessible的属性设为true,
@Test
public void testDeclaredMethod() throws Exception{
Class cls = Class.forName("com.qcc.reflect.entity.Student");
Method method = cls.getDeclaredMethod("method4",int.class);
method.setAccessible(true);
method.invoke(cls.newInstance(),1);
System.out.println(method);
}
运行结果如下:

private void com.qcc.reflect.entity.Student.method4(int)
Student类的私有带参方法method4(int age),实际参数为:1

此时发现,获取私有方法与获取公有方法调用的方法不一样.那有没有一种可以获取任意方法,不管此方法是公有还是私有,是在当前类中还是在父类中,也或者是父类的父类呢?此时就需要我们手动编写一个工具方法.

获取任意类的任意方法实现思路如下:

  1. 先在当前类中声明的方法中获取指定方法
  2. 如果没找到,则到所有父类中公有的方法中找指定的方法
  3. 如果仍未找到,则先判断是否已到*类Object【因为Object.class.getSuperclass()为null】
  4. 如果Object.class.getSuperclass() != null ,则将当前Class对象的父类Class对象传进去.采用递归算法,调用当前getMethod本身,直到找到为止.
  5. 如果在Object类中仍未找到,则抛出异常提示信息
实现如下:

public Method getMethod(Class cls, String methodName, Class[] classes) throws Exception {
Method method = null;
try {
method = cls.getDeclaredMethod(methodName, classes);
} catch (NoSuchMethodException e) {
try {
method = cls.getMethod(methodName, classes);
} catch (NoSuchMethodException e1) {
if(cls.getSuperclass() != null) {
method = getMethod(cls.getSuperclass(), methodName, classes);
} else {
throw new Exception("所有的父类中都找不到" + methodName + "方法");
}
}
}
return method;
}
编写测试代码,测试getMethod()方法,因method1,method2,method3三个方法都没有参数列表,因此使用循环统一测试(纯属省事.):

@Test
public void testGetMethod() throws Exception{
Class cls = Class.forName("com.qcc.reflect.entity.Student");
String methodName = null;
for(int i=1; i<4; i++) {
methodName = "method" + i;
Method method = getMethod(cls, methodName, null);
System.out.println(method);
System.out.println("---------------------");
}
methodName = "method4";
Method method = getMethod(cls, methodName,new Class[]{int.class});
System.out.println(method);
}
运行结果:

public void com.qcc.reflect.entity.Person.method1()
---------------------
private int com.qcc.reflect.entity.Person.method2()
---------------------
public java.lang.String com.qcc.reflect.entity.Student.method3()
---------------------
private void com.qcc.reflect.entity.Student.method4(int)
Field的获取方式与Method相同,也分以上四类.略.

使用Java反射机制来实现动态代理

现在有一个CalcDao接口,提供加减乘除的四个简单方法,有一个CalcDaoImpl的实现类实现了CalcDao接口,现在要求在调用实现类中的每个方法之前提示方法开始执行,执行完成后提示执行完毕,并统计方法调用的时间.
如果在每个方法中分别添加以上需求的实现,代码重复量大,代码耦合度高,不利于维护,使用java的动态代理可以很轻松的实现以上需求.
使用Java的反射机制,创建动态代理对象.让代理对象在调用目标方法之前和之后分别做一些事情,然后动态代理对象决定是否调用以及何时来调用被代理对象的方法(可以加一些验证,验证不通过则不去调用目标方法,此处示例略)
CalcDao接口和CalcDaoImpl类的代码如下:

package com.qcc.reflect.test;

/**
* 简单计算器类
* 提供加.减.乘.除 运算
* @author QianChaoChen 00002336<br>
* @date 2016年9月30日 上午9:47:05s
*/
public interface CalcDao {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
package com.qcc.reflect.test;public class CalcDaoImpl implements CalcDao {@Overridepublic int add(int i, int j) {int r = i + j;return r;}@Overridepublic int sub(int i, int j) {int r = i - j;return r;}@Overridepublic int mul(int i, int j) {int r = i * j;return r;}@Overridepublic int div(int i, int j) {int r = i / j;return r;}}
实现动态代理的步骤如下:

1、创建被代理的对象(接口类型) 2、通过代理类Proxy的newProxyInstance()方法创建代理对象 3、newProxyInstance()方法的三个参数:       3.1 ClassLoader:类加载器,即代理对象使用哪个类加载器进行加载,一般和被代理对象使用相同的.       3.2 Class类型的数组,此数组中只能放接口的Class对象.若代理对象不需要实现被代理对象已经实现的接口以外的接口,则可以使用       3.3 InvocationHandler一般使用匿名内部类的方式,被代理对象的类型要求是final类型的.编译器会给出提示. 

动态代理的单元测试方法如下:

@Test
public void testProxy(){
//创建被代理的对象(接口类型)
final CalcDao target = new CalcDaoImpl();
//通过代理类Prxoy的newProxyInstance()方法创建代理对象
CalcDao proxy = (CalcDao) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("方法【" + method.getName() + "】开始执行, 参数为" + Arrays.asList(args) + "...");
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("方法【" + method.getName() + "】执行完成,运算结果为:" + result + ", 用时" + (end - start) + "毫秒!");
return result;
}
});
proxy.add(10, 2);
proxy.sub(10, 2);
proxy.mul(10, 2);
proxy.div(10, 2);
}

}
proxy.add(10,2)是调用代理对象的add方法,它会去调用InvocationHandler的匿名内部类中的invoke方法,invoke方法中method.invoke(target,args)其实是调用被代理对象target的method方法,(此处就是反射在动态代理中最核心的部分)传递的参数数组是args,代理对象会在method.invoke()方法之前和之后做一些事情.

InvocationHandler接口的实现类中重写的invoke()方法也有三个参数,
proxy:正在生成的代理对象本身;
method:正在被调用的那个方法
args:被调用的方法中实际传递的参数;

result是调用目标方法后的返回值,如果方法本身没有返回值,则result为null.
以上测试代理对象的运行结果:

方法【add】开始执行, 参数为[10, 2]...
方法【add】执行完成,运算结果为:12, 用时0毫秒!
方法【sub】开始执行, 参数为[10, 2]...
方法【sub】执行完成,运算结果为:8, 用时0毫秒!
方法【mul】开始执行, 参数为[10, 2]...
方法【mul】执行完成,运算结果为:20, 用时0毫秒!
方法【div】开始执行, 参数为[10, 2]...
方法【div】执行完成,运算结果为:5, 用时0毫秒!