------- android培训、java培训、期待与您交流! ----------
Java反射机制
1. Java反射机制
1.1 Java反射的概念
在Java中反射指的是,我们可以在运行时加载、探知、使用编译期间完全未知的类。换句话说,Java程序可以加载一个运行时才得知名称的类,获悉其完整构造,并生成其对象实体、或对其变量设值、或调用其方法。这种“看透类”的能力被称为Introspection(内省、内观、反省)。Reflection和Introspection是常并提的两个术语。
在Java中,反射是一种强大的工具。它使你能够创建灵活的代码,这些代码可以在运行时装载。反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需要注意的是,如果使用不当,反射的成本很高。
Reflection是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查,或者说“自审”,并能直接操作程序内部的属性。例如,使用它能获得Java类中各成员的名称并显示出来。
考虑下面这个简单的例子,看看Reflection是如何工作的。
try {
Class<?> c = Class.forName("java.lang.Object");//加载类
Method[] ms = c.getDeclaredMethods();//获得类的方法列表
for (Method method : ms) {
System.out.println(method.toString());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
程序输出结果:
protected void java.lang.Object.finalize() throws java.lang.Throwable
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 final void java.lang.Object.wait(long,int) 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()
protected native java.lang.Object java.lang.Object.clone() throws java.lang.CloneNotSupportedException
private static native void java.lang.Object.registerNatives()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
这样就列出了java.lang.Object类的各方法及它们的限制符和返回类型。这个程序使用Class.forName载入指定的类,然后调用getDeclaredMethods()来获取这个类中定义的方法。Java.lang.reflect.Method是用来描述某个类中的单个方法的一个类。
1.2 反射API
Java提供了一套独特的反射API来描述类,使得Java程序在运行时可以获得任何一个类的字节码信息,包括类的修饰符(public,static等)、基类(超类、父类)、实现的接口、字段和方法等信息,并可以根据字节码信息来创建该类的实例对象,改变对象的字段内容和调用对象的方法。
Class是Java反射中的一个核心类,位于java.lang包中。它代表了内存中的一个Java类。通过它可以取得类的各种操作属性,这些属性是通过java.lang.reflect包中反射API来描述的。
public final classConstructor<T>
extendsAccessibleObject
implementsGenericDeclaration, Member
提供关于类的单个构造方法的信息以及对它的访问权限。
public final classMethod
extendsAccessibleObject
implementsGenericDeclaration, Member
提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
public final classField
extendsAccessibleObject
implementsMember
提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
public final classArray
extendsObject
Array 类提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作期间进行扩展转换,但如果发生收缩转换,则抛出 IllegalArgumentException。
public classModifier
extendsObject
Modifier 类提供了 static 方法和常量,对类和成员访问修饰符进行解码。
public class AccessibleObject
extendsObject
implementsAnnotatedElement
AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获取字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
在反射对象中设置 accessible 标志允许具有足够特权的复杂应用程序(比如 Java ObjectSerialization 或其他持久性机制)以某种通常禁止使用的方式来操作对象。
public final classClass<T>
extendsObject
implementsSerializable, GenericDeclaration, Type, AnnotatedElement
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
对于任何一个类来说,都可以通过Class提供的反射调用以不同的方式来获得类的信息。除了可以使用newInstence()创建该类的实例外,可以使用Class取得类的包名和类名:
PackagegetPackage()
获取此类的包。
StringgetName()
以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。如果此类对象表示的是非数组类型的引用类型,则返回该类的二进制名称,Java Language Specification, Second Edition 对此作了详细说明。
如果此类对象表示一个基本类型或 void,则返回的名字是一个与该基本类型或 void 所对应的 Java 语言关键字相同的String。如果此类对象表示一个数组类,则名字的内部形式为:表示该数组嵌套深度的一个或多个 '[' 字符加元素类型名。元素类型名的编码如下: Element Type | Encoding |
boolean | Z |
byte | B |
char | C |
class or interface | Lclassname; |
double | D |
float | F |
int | I |
long | J |
short | S |
类或接口名 classname 是上面指定类的二进制名称。
示例:
String.class.getName()
returns "java.lang.String"
byte.class.getName()
returns "byte"
(new Object[3]).getClass().getName()
returns "[Ljava.lang.Object;"
(new int[3][4][5][6][7][8][9]).getClass().getName()
returns "[[[[[[[I"
Class最重要的功能是提供了一组反射调用,用以取得该类的构造方法、变量和方法。
1). 取得构造方法——返回类型Constructor
根据类的构造方法的不同,可以使用如下3类方法来取得类的构造函数。
i. 获取类的所有公共构造方法列表:
Constructor<?>[] getConstructors()
Constructor<?>[]getDeclaredConstructors()
ii. 获得使用特殊参数类型的公共构造方法:
Constructor<T>getConstructor(Class<?>... parameterTypes)
Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)
iii. 获得本地或匿名类的构造方法:
Constructor<?>getEnclosingConstructor()
Constructor类还提供了取得参数类型和创建实例的方法:
Class<?>[]getParameterTypes()
TnewInstance(Object... initargs)
2). 获得变量——返回类型Field
获得字段信息的Class反射调用不同于那些接入构造方法的调用,在参数类型数组中可以使用字段名。
i. 取得指定名称的字段:
类或接口的指定公共成员字段。
FieldgetDeclaredField(String name)
类或接口的指定已声明字段
ii. 获得类或接口所声明的所有变量:
Field[]getFields()
类或接口的所有可访问公共字段。
Field[]getDeclaredFields()
类或接口所声明的所有字段。
Field类为变量提供了获得或修改值的方法:
boolean getBoolean(Objectobj) 获取一个静态或实例 boolean 字段的值。
char getChar(Objectobj) 获取 char 类型或另一个通过扩展转换可以转换为 char 类型的基本类型的静态或实例字段的值。
double getDouble(Objectobj) 获取 double 类型或另一个通过扩展转换可以转换为 double 类型的基本类型的静态或实例字段的值。
float getFloat(Objectobj) 获取 float 类型或另一个通过扩展转换可以转换为 float 类型的基本类型的静态或实例字段的值。
int getInt(Objectobj) 获取 int 类型或另一个通过扩展转换可以转换为 int 类型的基本类型的静态或实例字段的值。
long getLong(Objectobj) 获取 long 类型或另一个通过扩展转换可以转换为 long 类型的基本类型的静态或实例字段的值。
short getShort(Object obj) 获取 short 类型或另一个通过扩展转换可以转换为 short 类型的基本类型的静态或实例字段的值。
void setBoolean(Objectobj, boolean z) 将字段的值设置为指定对象上的一个 boolean 值。
void setByte(Objectobj, byte b) 将字段的值设置为指定对象上的一个 byte 值。
void setChar(Objectobj, char c) 将字段的值设置为指定对象上的一个 char 值。
void setDouble(Objectobj, double d) 将字段的值设置为指定对象上的一个 double 值。
void setFloat(Objectobj, float f) 将字段的值设置为指定对象上的一个 float 值。
void setInt(Objectobj, int i) 将字段的值设置为指定对象上的一个 int 值。
void setLong(Objectobj, long l) 将字段的值设置为指定对象上的一个 long 值。
void setShort(Objectobj, short s) 将字段的值设置为指定对象上的一个 short 值。
3).获取方法——返回类型Method
i.使用特定的参数类型获得命名的方法:
Method getMethod(Stringname, Class<?>... parameterTypes)
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method getDeclaredMethod(Stringname, Class<?>... parameterTypes)
返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
ii.获得所有方法列表:
Method[] getMethods()
返回一个包含某些Method 对象的数组,这些对象反映此Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。
Method[] getDeclaredMethods()
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
iii. 获取本地或匿名类的方法
Method getEnclosingMethod()
如果此 Class 对象表示某一方法中的一个本地或匿名类,则返回 Method 对象,它表示底层类的立即封闭方法。
Method为方法提供了取得参数和返回值类型及调用的方法:
Class<?>[] getParameterTypes()
按照声明顺序返回 Class对象的数组,这些对象描述了此Method 对象所表示的方法的形参类型。
Class<?> getReturnType()
返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型。
Object invoke(Objectobj, Object... args)
对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
4). Array类
Array类提供了动态创建和访问Java数组的方法,这些方法对应了8个Java的基本数据类型。
static boolean getBoolean(Object array, int index)
以boolean形式返回指定数组对象中索引组件的值。
static void setBoolean(Object array, intindex, boolean z)
将指定数组对象中索引组件的值设置为指定的 boolean 值。
5).Modifier类
提供了static方法和常量,对类和成员访问修饰符进行解码。修饰符集被表示为整数,用不同的位置表示不同的修饰符。
static int | ABSTRACT |
static int | FINAL |
static int | INTERFACE |
static int | NATIVE |
static int | PRIVATE |
static int | PROTECTED |
static int | PUBLIC |
static int | STATIC |
static int | STRICT |
static int | SYNCHRONIZED |
static int | TRANSIENT |
static int | VOLATILE |
2. 检测类
从上面的API可以看出,Java反射功能能够实现类的查找、创建与调用。如下示例。
/**
* @desc 测试类:获得类名称、方法、构造方法和字段
*/
public class MyObject {
int a;
int b;
public MyObject(int a, int b) {
super();
this.a = a;
this.b = b;
}
public int sum() {
return a + b;
}
public int minus(){
return a - b;
}
public int multiply(){
return a* b;
}
public int divide(){
return a /b;
}
}
测试:
Class<?> c = null;
try {
c = Class.forName("com.test.MyObject");//加载类
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//方法
Method[] methods = c.getDeclaredMethods();//获得类的方法列表
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
System.out.println("方法名:"+method.getName());
System.out.println("修饰符:"+method.getModifiers());// 1 --> PUBLIC
System.out.println("返回值:"+method.getReturnType());
//参数列表
Class<?>[] params = method.getParameterTypes();
for (int j = 0; j < params.length; j++) {
System.out.println("参数:"+j+params[j]);
}
}
//获得构造方法
Constructor<?>[] constructors = c.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
Constructor constructor = constructors[i];
System.out.println("构造方法名:"+constructor.getName());
System.out.println("修饰符:"+constructor.getModifiers());
//参数列表
Class[] params = constructor.getParameterTypes();
for (int j = 0; j < params.length; j++) {
System.out.println("参数:"+j+ " " +params[j]);
}
}
//获得属性
Field[] fields = c.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
System.out.println("变量名:" +field.getName());
System.out.println("修饰符:" +field.getModifiers());//常量字段
System.out.println("变量类型:"+field.getType());
}
3. 处理对象
在前面演示了获取类信息的方法,此外它还可动态创建类对象,并执行类中的方法。
3.1 创建类的对象
可以根据构造方法来创建一个对象。由于类MyObject的构造方法有两个int类型的参数,因此需要先创建一个类型数组,在根据类型数组取得构造方法:
// 类型数组
Class[] paramtypes = new Class[2];
paramtypes[0] = int.class;
paramtypes[1] = int.class;
//获得带参数列表的构造方法
Constructor<?> constructor = c.getConstructor(paramtypes);
由于构造方法含有两个整型参数,因此需要传递两个参数取得实例:
//实例创建对象
Object object = constructor.newInstance(20,50);
除了使用指定的参数类型来创建对象实例,还可以调用无参的构造方法:
Constructor<?> constructor = c.getConstructor();
//实例化对象
Object object = constructor.newInstance();
3.2 改变属性的值
Reflection还有一个用处就是改变对象数据字段的值。Reflection可以从正在运行的程序中根据名称找到对象的字段并修改它,示例如下:
Field fieldB = c.getDeclaredField("b");
System.out.println(fieldB.get(object));
fieldB.setInt(object, 0);
System.out.println(fieldB.get(object));
3.3 执行类的方法
通过Reflection可以执行一个指定名称的方法。与构造方法的调用过程相似,需要首先根据需要调用方法的参数类型创建一个参数类型数组,然后再创建一个输入值数组,执行方法的调用。如果函数没有参数,则直接调用即可。
public class InvokeTester {
public int add(int param1, int param2) {
return param1 + param2;
}
public String echo(String message) {
return "Hello " + message;
}
private void output(String name){
System.out.println(this.echo(name));
}
}
public class Test3 {
public static void main(String[] args) throws Exception {
/*
* 1. 普通方式
*/
InvokeTester tester = new InvokeTester();
tester.add(100, 111);
tester.echo("zhangsan");
/*
* 2.反射
*/
Class<?> clazz = Class.forName("com.test.InvokeTester");
Object invokerTester = clazz.newInstance();
Method addMethod = clazz.getDeclaredMethod("add", int.class,int.class);
Object result = addMethod.invoke(invokerTester,100,111);
System.out.println(result);
Method echoMethod = clazz.getDeclaredMethod("echo", String.class);
Object message = echoMethod.invoke(invokerTester, "lisi");
System.out.println(message);
//私有方法
Method output = clazz.getDeclaredMethod("output", String.class);
output.setAccessible(true);//压制Java的访问控制检查
output.invoke(invokerTester, "wangwu");
}
}
3.4 使用数组
反射可以创建并操作数组。数组在Java中是一种特殊的类型,一个数组的引用可以赋给Object引用。看看下面的数组是怎么工作的:
public class ArrayTester1 {
public static void main(String[] args) throws Exception
{
Class<?> classType = Class.forName("java.lang.String");
//Creates a new array with the specified component type and length
Object array = Array.newInstance(classType, 10);
//把array数组索引为5的元素值设置为"Hello"
Array.set(array, 5, "Hello");
//取得array数组索引为5的值
String str = (String)Array.get(array, 5);
System.out.println(str);
}
}
在程序中创建了一个长度为10的String数组,并索引为5的字符串赋值,最后将这个字符串数组打印输出。还可以动态的指定数组的维度来创建更复杂的数组。
public class ArrayTester2 {
public static void main(String[] args) {
int[] dims = new int[]{5,10,20};
//三维数组
// Object array = Array.newInstance(Integer.TYPE, 5,10,15);//以dims维度创建一个三维数组
Object array = Array.newInstance(Integer.TYPE, dims);//以dims维度创建一个三维数组
System.out.println(array instanceof int[][][]);//true
//二维数组
Object arrayObj = Array.get(array, 3);//取三维数组的3得到二维数组
//Returns the Class representing the component type of an array
// Class<?> classType = arrayObj.getClass().getComponentType();
// System.out.println(classType.getName());
System.out.println(arrayObj instanceof int[][]);//true
arrayObj = Array.get(arrayObj, 5);//取二维数组的5得到一维数组
//一维数组
Array.setInt(arrayObj, 10, 38);//设值
int[][][] arrayCast = (int[][][])array;
System.out.println(arrayCast[3][5][10]);//下标为[3][5][10]的值就是38
// System.out.println(Integer.TYPE);//int
// System.out.println(Integer.class.getName());//classType对象,java.lang.Integer
}
}
程序中创建了一个5×10×15的整型数组,并为处于[3][5][10]的元素赋了值为38.注意,多维数组实际上就是数组的数组,例如,第一个Array.get()之后,arrobj是一个10×15的数组,进而取得其中的一个元素,即长度为15的数组,并使用Array.setInt()为它的第10个元素赋值。
4. 动态代理
要知道什么是动态代理,首先我们来看一下静态代理的做法。无论是那种代理方式,都存在代理对象和目标对象两个模型,所谓目标对象就是我们要生成的代理对象所代理的那个对象。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。
在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式一般涉及到的角色有:
– 抽象角色:声明真实对象和代理对象的共同接口
– 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
– 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
4.1 静态代理
/*
* 抽象角色 : 声明真实对象和代理对象的共同接口
*/
public interface Subject {
public void request();
}
/*
* 真实对象
*/
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("from real subject.我要出租房子");
}
}
/**
* 代理角色
*/
public class ProxySubject implements Subject {
private RealSubject realSubject;//代理对象角色内部包含有对真实对象的引用
@Override
public void request() {
if (null == realSubject) {
realSubject = new RealSubject();
}
this.frequest();
realSubject.request();//真实角色所完成的操作
this.prequest();
}
/*
* 代理角色附加的操作
*/
private void frequest(){
System.out.println("完成之前打电话。");
}
private void prequest(){
System.out.println("完成之后收中介费。");
}
}
/**
* @desc 调用端
*/
public class Client {
public static void main(String[] args) {
Subject subject = new ProxySubject();
subject.request();
}
}
由以上代码可以看出,客户实际需要调用的是RealSubject类的request()方法,现在用ProxySubject来代理 RealSubject类,同样达到目的,同时还封装了其他方法(frequest (),prequest ()),可以处理一些其他问题。
另外,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。
4.2 动态代理
所谓的动态代理,即通过代理类Proxy的代理,接口和实现类之间可以不直接发生关系,而可以在运行期实现动态关联。
Java动态代理类位于java.lang.reflect包下主要涉及到一个接口和一个类。
1).接口InvocationHandler:该接口中仅定义一个方法。
Objectinvoke(Object proxy,Method method,Object[] args)throwsThrowable
在实际使用时,第一个参数proxy是指代理类,method是被代理的方法,args为该方法的参数数组。
2). Proxy:该类就是动态代理类,作用类实现类InvocationHandler接口的代理类,主要包含以下方法:
protected Proxy(InvocationHandler h)
使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。
public static Class<?>getProxyClass(ClassLoader loader,Class<?>... interfaces)
获得一个代理类,其中loader是类加载器,interfaces是真实类所拥有的全部接口的数组。
ObjectnewProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
动态代理(Dynamic Proxy)是这样一种类:它是在运行时生成的class,在生成它时必须提供一组interface给它,然后该class就宣称它实现类这些interface。当然可以把该class的实例当做这些interface中的任何一个来用。Dynamic Proxy其实就是一个Proxy,它不会替你做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
/**
* @desc 被代理的接口
*/
public interface Foo {
public String output(String name);
public void doAction();
}
/*
* 被代理的类
*/
public class FooImpl implements Foo {
@Override
public String output(String name) {
return name;
}
@Override
public void doAction() {
}
}
/*
* 被代理的类
*/
public class FooImpl2 implements Foo {
@Override
public String output(String name) {
return name;
}
@Override
public void doAction() {
}
}
/**
* @desc InvocationHandler 是代理实例的调用处理程序 实现的接口
*/
public class MyInvocationHandler implements InvocationHandler {
private Object foo;
public MyInvocationHandler(Object foo) {
this.foo = foo;
}
public void setFoo(Object foo) {
this.foo = foo;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.invoke(foo, args);
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @desc 测试
*/
public class Main {
public static void main(String[] args) throws Exception {
FooImpl foo = new FooImpl();
//FooImpl2 foo2 = new FooImpl2();
//1.创建某一接口Foo的代理
MyInvocationHandler handler = new MyInvocationHandler(foo);
//handler.setFoo(foo2);
//2.动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[]{Foo.class});
//3.代理实例是代理类的一个实例。每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。
Foo f1 = (Foo) proxyClass.getConstructor(new Class[]{InvocationHandler.class}).newInstance(new Object[]{handler});
Foo f2 = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),foo.getClass().getInterfaces(),handler);
//4.代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,并传递代理实例、识别调用方法的 Method 对象以及包含参数的 Object 类型的数组
String value = f1.output("张三");
System.out.println(value);
f2.doAction();
//当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
System.out.println(Proxy.isProxyClass(proxyClass));//$Proxy0
System.out.println(f1.getClass());//$Proxy0动态代理类的名称,实现了接口:Foo
//返回指定代理实例的调用处理程序。
System.out.println(Proxy.getInvocationHandler(f1));
}
}
我们看到目标对象的方法调用被Proxy 拦截,在InvocationHandler 中的回调方法中通过反射调用。这种动态代理的方式实现了对类的方法的运行时修改。JDK 的动态代理有个缺点,那就是不能对类进行代理,只能对接口进行代理,想象一下我们的 FooImpl如果没有实现任何接口, 那么将无法使用这种方式进行动态代理(实际上是因为$Proxy0 这个类继承了 Proxy,JAVA 的继承不允许出现多个父类)。但准确的说这个问题不应该是缺点,因为良好的系统,每一个类都是应该有一个接口的。
从上面知道$Proxy0 是动态代理对象的所属类型,但由于这个类型根本不存在,我们如何鉴别一个对象是一个普通的对象还是动态代理对象呢?Proxy 类中提供了isProxyClass(Classc)方法鉴别与此。
下面我们介绍一下 InvocationHandler 这个接口,它只有一个方法 invoke()需要实现,这个方法会在目标对象的方法调用的时候被激活, 你可以在这里控制目标对象的方法的调用,在调用前后插入一些其他操作(譬如:鉴权、日志、务管理等)。 invoke()方法的后两个参数很好理解,一个是调用的方法的 Method 对象,另一个是方法的参数,第一个参数有些需要注意的地方,这个 proxy 参数就是我们使用 Proxy 的静态方法创建的动态代理对象,也就是$Proxy0 的实例 (这点你可以在 Eclipse 的断点调试中看到 proxy 的所属类型确实是$Proxy0)。由于$Proxy0 在 JDK 中不是静态存在的, 因此你不可以把第一个参数 Object proxy 强制转换为$Proxy0 类型,因为你根本就无法从 Classpath 中导入$Proxy0。那么我们可以把 proxy 转为目标对象的接口吗?因为$Proxy0 是实现了目标对象的所有的接口的,答案是可以的。但实际上这样做的意义不大, 因为你会发现转换为目标对象的接口之后,你调用接口中的任何一个方法,都会导致 invoke()的调用陷入死循环而导致堆栈溢出。如下所示:@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Foo f = (Foo) proxy;
f.doAction();
f.output("zhangsan");
return method.invoke(f, args);
}
这是因为目标对象的大部分的方法都被代理了,你在 invoke()通过代理对象转换之后的接口调用目标对象的方法,依然是走的代理对象,也就是说当 f.doAction()方法被激活时会立即导致 invoke()的调用,然后再次调用 f.doAction()方法,… …从而使方法调用进入死循环,就像无尽的递归调用。那么 invoke()方法的第一个参数到底干什么用的呢?其实一般情况下这个参数都用不到,除非你想获得代理对象的类信息描述,因为它的 getClass()方法的调用不会陷入死循环。如下所示:
Class<?> clazz = proxy.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method2 : methods) {
System.out.println("方法名:"+method2.getName());
}
这里我们可以获得代理对象的所有的方法的名字,你会看到控制台输出如下信息:
方法名:doAction
方法名:equals
方法名:toString
方法名:hashCode
方法名:output
我们看到 proxy 确实动态的把目标对象的所有的接口中的方法都集中到了自己的身上。这里还要注意一个问题,那就是从Object身上继承的方法hashCode()等的调用也会导致陷入死循环,为什么getClass()不会呢?因为getClass()方法是final的,不可以被覆盖,所以也就不会被Proxy代理。但不要认为Proxy不可以对final的方法进行动态代理,因为Proxy 面向的是FooImpl的接口,而不是FooImpl本身,所以即便是FooImpl在实现Foo接口的时候,把方法都变为 final 的,也不会影响到 Proxy 的动态代理。
------- android培训、java培训、期待与您交流! ----------