:notebook: 本文已归档到:「blog」html
:keyboard: 本文中的示例代码已归档到:「javacore」java
反射(Reflection)是 Java 程序开发语言的特征之一,它容许运行中的 Java 程序获取自身的信息,而且能够操做类或对象的内部属性。git
经过反射机制,能够在运行时访问 Java 对象的属性,方法,构造方法等。github
反射的主要应用场景有:数据库
类加载的完整过程以下:编程
(1)在编译时,Java 编译器编译好 .java
文件以后,在磁盘中产生 .class
文件。.class
文件是二进制文件,内容是只有 JVM 可以识别的机器码。设计模式
(2)JVM 中的类加载器读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息。类加载器会根据类的全限定名来获取此类的二进制字节流;而后,将字节流所表明的静态存储结构转化为方法区的运行时数据结构;接着,在内存中生成表明这个类的 java.lang.Class
对象。数组
(3)加载结束后,JVM 开始进行链接阶段(包含验证、准备、初始化)。通过这一系列操做,类的变量会被初始化。安全
要想使用反射,首先须要得到待操做的类所对应的 Class 对象。Java 中,不管生成某个类的多少个对象,这些对象都会对应于同一个 Class 对象。这个 Class 对象是由 JVM 生成的,经过它可以获悉整个类的结构。因此,java.lang.Class
能够视为全部反射 API 的入口点。bash
反射的本质就是:在运行时,把 Java 类中的各类成分映射成一个个的 Java 对象。
举例来讲,假如定义了如下代码:
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
类 - 该类提供动态地生成和访问 JAVA 数组的方法。Modifier
类 - 提供了 static 方法和常量,对类和成员访问修饰符进行解码。Proxy
类 - 提供动态地生成代理类和类实例的静态方法。得到 Class 的三种方法:
(1)使用 Class 类的 forName
静态方法
示例:
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 开发中经常使用此方法加载数据库驱动。
(2)直接获取某一个对象的 class
示例:
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
- 根据名称获取公有的(public)类成员。getDeclaredField
- 根据名称获取已声明的类成员。但不能获得其父类的类成员。getFields
- 获取全部公有的(public)类成员。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
- 返回类或接口的特定方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。getDeclaredMethod
- 返回类或接口的特定声明方法。其中第一个参数为方法名称,后面的参数为方法参数对应 Class 的对象。getMethods
- 返回类或接口的全部 public 方法,包括其父类的 public 方法。getDeclaredMethods
- 返回类或接口声明的全部方法,包括 public、protected、默认(包)访问和 private 方法,但不包括继承的方法。获取一个 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
- 返回类的特定 public 构造方法。参数为方法参数对应 Class 的对象。getDeclaredConstructor
- 返回类的特定构造方法。参数为方法参数对应 Class 的对象。getConstructors
- 返回类的全部 public 构造方法。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 类型了。
同时咱们必定要记住,经过 Proxy.newProxyInstance
建立的代理对象是在 jvm 运行时动态生成的一个对象,它并非咱们的 InvocationHandler 类型,也不是咱们定义的那组接口的类型,而是在运行是动态生成的一个对象,而且命名方式都是这样的形式,以$开头,proxy 为中,最后一个数字表示对象的标号。
接着咱们来看看这两句
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 方法中来调用,并非本身来真实调用,而是经过代理的方式来调用的。
反射应用