:notebook: 本文已归档到:「 blog 」
:keyboard: 本文中的示例代码已归档到:「 javacore 」
反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
反射的主要应用场景有:
类加载的完整过程如下:
(1)在编译时,Java 编译器编译好 .java
文件之后,在磁盘中产生 .class
文件。 .class
文件是二进制文件,内容是只有 JVM 能够识别的机器码。
(2)JVM 中的类加载器读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息。类加载器会根据类的全限定名来获取此类的二进制字节流;然后,将字节流所代表的静态存储结构转化为方法区的运行时数据结构;接着,在内存中生成代表这个类的 java.lang.Class
对象。
(3)加载结束后,JVM 开始进行连接阶段(包含验证、准备、初始化)。经过这一系列操作,类的变量会被初始化。
要想使用反射,首先需要获得待操作的类所对应的 Class 对象。 Java 中,无论生成某个类的多少个对象,这些对象都会对应于同一个 Class 对象。这个 Class 对象是由 JVM 生成的,通过它能够获悉整个类的结构 。所以, java.lang.Class
可以视为所有反射 API 的入口点。
举例来说,假如定义了以下代码:
User user = new User(); 复制代码
步骤说明:
new User()
,JVM 会根据 User
的全限定名去加载 User.class
。 User.class
文件并加载 JVM 内存中。 Class
对象,并且存储在 JVM 的方法区。注意: 一个类有且只有一个 Class
对象 。 Java 中的 java.lang.reflect
包提供了反射功能。 java.lang.reflect
包中的类都没有 public
构造方法。
java.lang.reflect
包的核心接口和类如下:
Member Field Method Constructor Array Modifier Proxy
获得 Class 的三种方法:
示例:
package io.github.dunwu.javacore.reflect; public class ReflectClassDemo01 { public static void main(String[] args) throws ClassNotFoundException { Class c1 = Class.forName("io.github.dunwu.javacore.reflect.ReflectClassDemo01"); System.out.println(c1.getCanonicalName()); Class c2 = Class.forName("[D"); System.out.println(c2.getCanonicalName()); Class c3 = Class.forName("[[Ljava.lang.String;"); System.out.println(c3.getCanonicalName()); } } //Output: //io.github.dunwu.javacore.reflect.ReflectClassDemo01 //double[] //java.lang.String[][] 复制代码
使用类的完全限定名来反射对象的类。常见的应用场景为:在 JDBC 开发中常用此方法加载数据库驱动。
示例:
public class ReflectClassDemo02 { public static void main(String[] args) { boolean b; // Class c = b.getClass(); // 编译错误 Class c1 = boolean.class; System.out.println(c1.getCanonicalName()); Class c2 = java.io.PrintStream.class; System.out.println(c2.getCanonicalName()); Class c3 = int[][][].class; System.out.println(c3.getCanonicalName()); } } //Output: //boolean //java.io.PrintStream //int[][][] 复制代码
(3) 调用 Object 的 getClass
方法 ,示例:
Object 类中有 getClass 方法,因为所有类都继承 Object 类。从而调用 Object 类来获取
示例:
package io.github.dunwu.javacore.reflect; import java.util.HashSet; import java.util.Set; public class ReflectClassDemo03 { enum E {A, B} public static void main(String[] args) { Class c = "foo".getClass(); System.out.println(c.getCanonicalName()); Class c2 = ReflectClassDemo03.E.A.getClass(); System.out.println(c2.getCanonicalName()); byte[] bytes = new byte[1024]; Class c3 = bytes.getClass(); System.out.println(c3.getCanonicalName()); Set<String> set = new HashSet<>(); Class c4 = set.getClass(); System.out.println(c4.getCanonicalName()); } } //Output: //java.lang.String //io.github.dunwu.javacore.reflect.ReflectClassDemo.E //byte[] //java.util.HashSet 复制代码
判断是否为某个类的实例有两种方式:
instanceof
关键字 Class
对象的 isInstance
方法 (它是一个 Native 方法) 示例:
public class InstanceofDemo { public static void main(String[] args) { ArrayList arrayList = new ArrayList(); if (arrayList instanceof List) { System.out.println("ArrayList is List"); } if (List.class.isInstance(arrayList)) { System.out.println("ArrayList is List"); } } } //Output: //ArrayList is List //ArrayList is List 复制代码
通过反射来创建实例对象主要有两种方式:
Class
对象的 newInstance
方法。 Constructor
对象的 newInstance
方法。 示例:
public class NewInstanceDemo { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Class<?> c1 = StringBuilder.class; StringBuilder sb = (StringBuilder) c1.newInstance(); sb.append("aaa"); System.out.println(sb.toString()); //获取String所对应的Class对象 Class<?> c2 = String.class; //获取String类带一个String参数的构造器 Constructor constructor = c2.getConstructor(String.class); //根据构造器创建实例 String str2 = (String) constructor.newInstance("bbb"); System.out.println(str2); } } //Output: //aaa //bbb 复制代码
Class
对象提供以下方法获取对象的成员( Field
):
getFiled getDeclaredField getFields getDeclaredFields
示例如下:
public class ReflectFieldDemo { class FieldSpy<T> { public boolean[][] b = {{false, false}, {true, true}}; public String name = "Alice"; public List<Integer> list; public T val; } public static void main(String[] args) throws NoSuchFieldException { Field f1 = FieldSpy.class.getField("b"); System.out.format("Type: %s%n", f1.getType()); Field f2 = FieldSpy.class.getField("name"); System.out.format("Type: %s%n", f2.getType()); Field f3 = FieldSpy.class.getField("list"); System.out.format("Type: %s%n", f3.getType()); Field f4 = FieldSpy.class.getField("val"); System.out.format("Type: %s%n", f4.getType()); } } //Output: //Type: class [[Z //Type: class java.lang.String //Type: interface java.util.List //Type: class java.lang.Object 复制代码
Class
对象提供以下方法获取对象的方法( Method
):
getMethod getDeclaredMethod getMethods getDeclaredMethods
获取一个 Method
对象后,可以用 invoke
方法来调用这个方法。
invoke
方法的原型为:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 复制代码
示例:
public class ReflectMethodDemo { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // 返回所有方法 Method[] methods1 = System.class.getDeclaredMethods(); System.out.println("System getDeclaredMethods 清单(数量 = " + methods1.length + "):"); for (Method m : methods1) { System.out.println(m); } // 返回所有 public 方法 Method[] methods2 = System.class.getMethods(); System.out.println("System getMethods 清单(数量 = " + methods2.length + "):"); for (Method m : methods2) { System.out.println(m); } // 利用 Method 的 invoke 方法调用 System.currentTimeMillis() Method method = System.class.getMethod("currentTimeMillis"); System.out.println(method); System.out.println(method.invoke(null)); } } 复制代码
Class
对象提供以下方法获取对象的构造方法( Constructor
):
getConstructor getDeclaredConstructor getConstructors getDeclaredConstructors
获取一个 Constructor
对象后,可以用 newInstance
方法来创建类实例。
示例:
public class ReflectMethodConstructorDemo { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<?>[] constructors1 = String.class.getDeclaredConstructors(); System.out.println("String getDeclaredConstructors 清单(数量 = " + constructors1.length + "):"); for (Constructor c : constructors1) { System.out.println(c); } Constructor<?>[] constructors2 = String.class.getConstructors(); System.out.println("String getConstructors 清单(数量 = " + constructors2.length + "):"); for (Constructor c : constructors2) { System.out.println(c); } System.out.println("===================="); Constructor constructor = String.class.getConstructor(String.class); System.out.println(constructor); String str = (String) constructor.newInstance("bbb"); System.out.println(str); } } 复制代码
数组在 Java 里是比较特殊的一种类型,它可以赋值给一个对象引用。下面我们看一看利用反射创建数组的例子:
public class ReflectArrayDemo { public static void main(String[] args) throws ClassNotFoundException { Class<?> cls = Class.forName("java.lang.String"); Object array = Array.newInstance(cls, 25); //往数组里添加内容 Array.set(array, 0, "Scala"); Array.set(array, 1, "Java"); Array.set(array, 2, "Groovy"); Array.set(array, 3, "Scala"); Array.set(array, 4, "Clojure"); //获取某一项的内容 System.out.println(Array.get(array, 3)); } } //Output: //Scala 复制代码
其中的 Array 类为 java.lang.reflect.Array
类。我们通过 Array.newInstance
创建数组对象,它的原型是:
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException { return newArray(componentType, length); } 复制代码
动态代理是反射的一个非常重要的应用场景。动态代理常被用于一些 Java 框架中。例如 Spring 的 AOP ,Dubbo 的 SPI 接口,就是基于 Java 动态代理实现的。
静态代理其实就是指设计模式中的代理模式。
Subject定义了 RealSubject 和 Proxy 的公共接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy 。
abstract class Subject { public abstract void Request(); } 复制代码
RealSubject定义 Proxy 所代表的真实实体。
class RealSubject extends Subject { @Override public void Request() { System.out.println("真实的请求"); } } 复制代码
Proxy保存一个引用使得代理可以访问实体,并提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代实体。
class Proxy extends Subject { private RealSubject real; @Override public void Request() { if (null == real) { real = new RealSubject(); } real.Request(); } } 复制代码
说明:
静态代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于 Proxy 和 RealSubject 的功能本质上是相同的,Proxy 只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。
为了解决静态代理的问题,就有了创建动态代理的想法:
在运行状态中,需要代理的地方,根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,这样就可以避免了 Proxy 角色的 class 在系统中冗杂的问题了。
Java 动态代理基于经典代理模式,引入了一个 InvocationHandler,InvocationHandler 负责统一管理所有的方法调用。
动态代理步骤:
com.sun.proxy.$ProxyXXXX
; InvocationHandler
实例 handler,用来处理 Proxy
所有方法调用; 从上面可以看出,JDK 动态代理的实现是基于实现接口的方式,使得 Proxy 和 RealSubject 具有相同的功能。
但其实还有一种思路:通过继承。即:让 Proxy 继承 RealSubject,这样二者同样具有相同的功能,Proxy 还可以通过重写 RealSubject 中的方法,来实现多态。CGLIB 就是基于这种思路设计的。
在 Java 的动态代理机制中,有两个重要的类(接口),一个是 InvocationHandler
接口、另一个则是 Proxy
类,这一个类和一个接口是实现我们动态代理所必须用到的。
InvocationHandler
接口定义:
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } 复制代码
每一个动态代理类都必须要实现 InvocationHandler
这个接口,并且每个代理类的实例都关联到了一个 Handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler
这个接口的 invoke
方法来进行调用。
我们来看看 InvocationHandler 这个接口的唯一一个方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable 复制代码
参数说明:
Method
对象 如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。
Proxy
这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance
这个方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 复制代码
这个方法的作用就是得到一个动态的代理对象。
参数说明:
上面的内容介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:
首先我们定义了一个 Subject 类型的接口,为其声明了两个方法:
public interface Subject { void hello(String str); String bye(); } 复制代码
接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject 类:
public class RealSubject implements Subject { @Override public void hello(String str) { System.out.println("Hello " + str); } @Override public String bye() { System.out.println("Goodbye"); return "Over"; } } 复制代码
下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:
public class InvocationHandlerDemo implements InvocationHandler { // 这个就是我们要代理的真实对象 private Object subject; // 构造方法,给我们要代理的真实对象赋初值 public InvocationHandlerDemo(Object subject) { this.subject = subject; } @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { // 在代理真实对象前我们可以添加一些自己的操作 System.out.println("Before method"); System.out.println("Call Method: " + method); // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用 Object obj = method.invoke(subject, args); // 在代理真实对象后我们也可以添加一些自己的操作 System.out.println("After method"); System.out.println(); return obj; } } 复制代码
最后,来看看我们的 Client 类:
public class Client { public static void main(String[] args) { // 我们要代理的真实对象 Subject realSubject = new RealSubject(); // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 InvocationHandler handler = new InvocationHandlerDemo(realSubject); /* * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象 * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上 */ Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler); System.out.println(subject.getClass().getName()); subject.hello("World"); String result = subject.bye(); System.out.println("Result is: " + result); } } 复制代码
我们先来看看控制台的输出:
com.sun.proxy.$Proxy0 Before method Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String) Hello World After method Before method Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye() Goodbye After method Result is: Over 复制代码
我们首先来看看 com.sun.proxy.$Proxy0
这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName());
这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler); 复制代码
可能我以为返回的这个代理对象会是 Subject 类型的对象,或者是 InvocationHandler 的对象,结果却不是,首先我们解释一下 为什么我们这里可以将其转化为 Subject 类型的对象?
原因就是:在 newProxyInstance 这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是 Subject 类型,所以就可以将其转化为 Subject 类型了。
接着我们来看看这两句
subject.hello("World"); String result = subject.bye(); 复制代码
这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的 invoke 方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject 类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的 invoke 方法去执行。
我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:
public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String) public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye() 复制代码
正好就是我们的 Subject 接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的 invoke 方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。