28-反射
1. 概述#
Reflection(反射)是被视为 [动态语言] 的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
- 动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang
- 静态语言:与动态语言相对应的,运行时结构不可变的语言就是静态语言;如 Java、C、C++。
Java 不是动态语言,但 Java 可以称之为“准动态语言”。即 Java 有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
Java 反射机制提供的功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射相关的主要 API:
java.lang.Class
代表一个类java.lang.reflect.Method
代表类的方法java.lang.reflect.Field
代表类的成员变量java.lang.reflect.Constructor
代表类的构造器
2. Class 类#
Class 类是"类的类"(class of classes)。如果说类是对象的抽象和集合的话,那么 Class 类就是对类的抽象和集合。
2.1 RTTI#
运行时类型识别(RTTI, Run-Time Type Identification)是 Java 中非常有用的机制,在 Java 运行时,RTTI 维护类的相关信息。多态(polymorphism) 是基于 RTTI 实现的。RTTI 的功能主要是由 Class类 实现的。
在 Object 类中定义了以下的方法,此方法将被所有子类继承:public final Class getClass()
以上的方法返回值的类型是一个 Class类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构 (class/interface/enum/annotation/primitive type/void/[]) 的有关信息。
- Class 本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个 Class 实例
- 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过 Class 可以完整地得到一个类中的所有被加载的结构
- Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象。
2.2 Class 对象#
要了解 RTTI 在 Java 中的工作原理,首先必须要知道类型信息在运行时是如何表示的。这项工作是由称为 [Class对象] 的特殊对象完成的,它包含了与类相关的信息。事实上,Class 对象就是用来创建类的所有的 "常规" 的对象的。 Java 使用 Class 对象来执行其 RTTI,即使你正在执行的是类似转型这样的操作。Class 类还拥有大量的使用 RTTI 的其他方式。
类是程序的一部分,每个类都有一个 Class 对象。换言之,每当编写并且编译了一个新类,就会产生一个 Class 对象(更恰当地说,是被保存在一个同名的 .class 文件中)。为了生成这个类的对象,运行这个程序的 JVM 将使用被称为 "类加载器" 的子系统。这个待会就说。
代码演示:每一个 Class 类的对象代表一个其他的类。就比如在下面的程序中,Class 类的对象 c1 代表了 Human 类,c2 代表了 Woman 类。
public class Demo {
public static void main(String[] args) {
Human aPerson = new Human();
Class c1 = aPerson.getClass();
System.out.println(c1.getName());
Human anotherPerson = new Woman();
Class c2 = anotherPerson.getClass();
System.out.println(c2.getName());
}
}
class Human {}
class Woman extends Human {}
当我们调用对象的 getClass() 时,就得到对应 Class 对象的引用。
在 c2 中,即使我们将 Women 对象的引用向上转换为 Human 对象的引用,对象所指向的 Class 类对象依然是 Woman。
Java 中每个对象都有相应的 Class 类对象,因此,我们随时能通过 Class 对象知道某个对象“真正”所属的类。无论我们对引用进行怎样的类型转换,对象本身所对应的 Class 对象都是同一个。当我们通过某个引用调用方法时,Java总能找到正确的 Class 类中所定义的方法,并执行该 Class 类中的代码。由于 Class 对象的存在,Java 不会因为类型的向上转换而迷失。这就是多态的原理。
btw:类可以造对象,但类本身也是一个类(Class类) 的对象。还记得静态可以直接通过 "类名." 的方式调用吗?其实这也是"对象." 式调用,不是吗。
哪些类型可以有 Class 对象?
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
public void test5() {
// Class实例可以是哪些结构?
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);
}
2.3 类的加载与类加载器#
2.3.1 类的加载#
当程序主动使用某个类时,如果该类还未被加载到内存中,则为了使用类而做的准备工作实际包含 3 个步骤。
- 加载。这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 Class 对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个 Class 对象。这个加载的过程需要类加载器参与
- 链接。在连接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必须的话,将解析这个类创建的对其它类的所有引用。是一个将Java类的二进制代码合并到 JVM 的运行状态之中的过程
- 验证:确保加载的类信息符合 JVM 规范,例如:以 cafe 开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
- 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块
- 执行类构造器
<clinit>()
的过程。类构造器<clinit>()
是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器) - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的
<clinit>()
在多线程环境中被正确加锁和同步
- 执行类构造器
public class Demo {
@Test
public void classLoaderTest() {
/*
Step2: 链接结束后 m = 0
Step3: 初始化后,m 的值由 <clinit>() 执行决定。这个 A 的类构造器<clinit>()
由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于
<clinit>() { m = 300; m = 100;}
*/
System.out.println(A.m); // 100
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
2.3.3 类加载器#
我们使用 java.exe 对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,此过程称为"类的加载"。加载到内存中的类,我们就称为"运行时类",此运行时类,就作为Class的一个实例。
为了生成这个实例,运行这个程序的 JVM 将使用被称为 "类加载器" 的子系统。
类加载器的作用:
- 将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口。
- 类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制 可以回收这些 Class 对象。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是 JVM 实现的一部分。原生类加载器加载的所谓的可信类,包括 Java API 类,它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持WebServer应用,或者在网络中下载类),那么你有一种方式可以挂接额外的类加载器。
所有的类都是在对其第一次使用时,动态加载到 JVM 中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用 static 关键字。因此,使用 new 操作符创建类的新对象也会被当作对类的静态成员的引用。因此,Java 程序在它开始运行之前并非被完全加载,其各个部分是在必须时才加载的。
类加载器首先检查这个类的 Class 对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找 .class 文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良 Java 代码(这是 Java 中用于安全防范目的地的措施之一)。
一旦某个类的 Class 对象被载入内存,它就被用来创建这个类的所有对象。
@Test
public void test() {
// 1. 获取一个 [系统类加载器]
ClassLoader classloader = ClassLoader.getSystemClassLoader();
System.out.println(classloader); // sun.misc.Launcher$AppClassLoader@58644d46
// 2. 取系统类加载器的父类加载器,即 [扩展类加载器]
classloader = classloader.getParent(); // sun.misc.Launcher$ExtClassLoader@4a574795
System.out.println(classloader);
// 3. 获取扩展类加载器的父类加载器,即 [引导类加载器]
classloader = classloader.getParent();
System.out.println(classloader); // null
// 4. 测试当前类由哪个类加载器进行加载
classloader = ClassLoaderDemo.class.getClassLoader();
System.out.println(classloader); // sun.misc.Launcher$AppClassLoader@58644d46
// 5. 测试 JDK 提供的 Object 类由哪个类加载器加载
classloader = Object.class.getClassLoader();
System.out.println(classloader); // null
}
使用类加载器加载配置文件:
@Test
public void test2() throws IOException {
Properties prop = new Properties();
// 此时的文件相对于当前 Module
// FileInputStream is = new FileInputStream("jdbc.properties");
// 此时的文件相对于 src
InputStream is = ClassLoaderDemo.class.getClassLoader()
.getResourceAsStream("jdbc1.properties");
// FileInputStream is = new FileInputStream("src\\jdbc1.properties");
prop.load(is);
String user = prop.getProperty("user");
String password = prop.getProperty("password");
System.out.println(user + ":" + password);
}
3. 获取 Class 类的实例#
3.1 调用运行时类的属性#
使用类字面常量来生成对 Class 对象的引用,如:Class clazz = Person.class;
。
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段 TYPE。TYPE 字段是一个引用,指向对应的基本数据类型的 Class 对象,如下所示:
boolean.class
等价于Boolean.TYPE
char.class
等价于Character.TYPE
byte.class
等价于Byte.TYPE
short.class
等价于Short.TYPE
int.class
等价于Integer.TYPE
long.class
等价于Long.TYPE
float.class
等价于Float.TYPE
double.class
等价于Double.TYPE
void.class
等价于Void.TYPE
注意,当使用 ".class" 来创建对 Class 对象的引用时,不会自动地初始化该 Class 对象。该方法下 Class 对象的初始化被延迟到了对静态方法(构造器隐式是静态的)或者非常数静态域进行首次引用时才执行。
3.2 实例调方法#
已知某个类的实例,调用该实例的 getClass()
获取 Class 对象。
Person p = new Person();
Class clazz2 = p.getClass();
3.3 Class 类静态方法#
Class clazz3 = Class.forName("cn.edu.nuist.java.person");
这个方法是 Class 类(所有Class对象都属于这个类)的一个 static 成员。Class 对象就和其它对象一样,我们可以获取并操作它的引用(这也就是类加载器的工作)。forName()
是取得 Class 对象的引用的一种方法。它是用一个类的全类名的String作为输入参数,返回的是一个 Class 对象的引用。
该方法产生的“副作用”:如果类还没有被加载就加载它。在加载的过程中,Person 的 static 子句被执行(属性和加载器方式就不会执行)。
如果 Class.forName()
找不到你要加载的类,它会抛出 ClassNotFoundException
。
3.4 使用类加载器#
ClassLoader classLoader = ReflectionDemo.class.getClassLoader();
Class clazz4 = classLoader.loadClass("cn.edu.nuist.Person");
4. 什么时候会发生类初始化#
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化
main()
所在的类 - new 一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用 java.lang.reflect 包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
- 当虚拟机启动,先初始化
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化
- 当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用静态常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
public class InitTest {
public static void main(String[] args) {
System.out.println("staticFinal 之前");
System.out.println(B.staticFinal);
System.out.println("staticFinal 之后");
// -----------------------
System.out.println("staticFinal2 之前");
System.out.println(B.staticFinal2);
System.out.println("staticFinal2 之后");
// -----------------------
System.out.println("staticNonFinal 之前");
System.out.println(C.staticNonFinal);
System.out.println("staticNonFinal 之后");
/*
staticFinal 之前
1101
staticFinal 之后
-----------------------
staticFinal2 之前
~~~ClassB对象初始化~~~
996
staticFinal2 之后
-----------------------
staticNonFinal 之前
~~~ClassC对象初始化~~~
67
staticNonFinal 之后
*/
}
}
class B {
static {
System.out.println("~~~ClassB对象初始化~~~");
}
static final int staticFinal = 1101;
static final int staticFinal2 = (int)(Math.random()*1101);
}
class C {
static int staticNonFinal = 67;
static {
System.out.println("~~~ClassC对象初始化~~~");
}
}
如果一个 static final
值是"编译期常量",就像 B.staticFinal
那样,那么这个值不需要对所属类进行初始化就可以被读到。但是,如果只是将一个域设置为 static 和 final 的,还不足以确保这种行为,例如,对 B.staticFinal2
的访问将强制进行类的初始化,因为它不是一个编译期常量。
如果一个 static 域不是 final 的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间),就像在对 C.staticNonFinal
的访问中所看到的那样。
5. 泛化的 Class 引用#
public final class Class<T> extends Object implements
Serializable, GenericDeclaration, Type, AnnotatedElement {...}
T
- 由此 Class 对象建模的类的类型。例如,String.class
的类型是 Class<String>
。如果将被建模的类未知,则使用 Class<?>
。
摘自《Java编程思想》
6. 创建运行时类的对象#
- 调用 Class 对象的
newInstance()
的前提:
- 类必须有一个无参数的构造器
- 类的构造器的访问权限要足够
- 没有无参的构造器也可以创建对象,只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。步骤如下:
7. 获取运行时类的完整结构#
Field、Method、Constructor、Superclass、Interface、Annotation
7.1 Person#
package cn.edu.nuist.java1;
import java.util.Date;
@MyAnnotation(value="修饰类")
public class Person extends Creature<String> implements Comparable<String>, MyInterface {
private String privateAttr;
int defaultAttr;
public int publicMethod;
static {
System.out.println("Person static block");
}
public Person() {}
@MyAnnotation("修饰构造器")
private Person(String privateAttr) {
this.privateAttr = privateAttr;
}
Person(String privateAttr, int defaultAttr) {
this.privateAttr = privateAttr;
this.defaultAttr = defaultAttr;
}
<T> T typeMethod(T t, int i, double d) {
return t;
}
@MyAnnotation("修饰方法")
private void privateMethod(String nation) {
System.out.println("我的国籍是" + nation);
}
public String publicMethod(String str, Date date) throws ArithmeticException, NullPointerException{
System.out.println("publicMethod\t" + date);
return str;
}
private static void staticMethod() {
System.out.println("static Method");
}
@Override
public void info() {
System.out.println("So, tell me, Who am I?");
}
@Override
public int compareTo(String o) {
return 0;
}
@Override
public String toString() {
return "Person{" +
"privateAttr='" + privateAttr + '\'' +
", defaultAttr=" + defaultAttr +
", publicMethod=" + publicMethod +
'}';
}
}
7.2 FieldTest#
package cn.edu.nuist.java2;
import cn.edu.nuist.java1.Person;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class FieldTest {
@Test
public void test1() {
Class clazz = Person.class;
// getFields(): 获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for(Field field : fields)
System.out.println(field);
System.out.println("-----------------------------");
// getDeclaredFields(): 获取当前运行时类当中声明的所有属性(不包含父类中声明的)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field : declaredFields)
System.out.println(field);
}
@Test
public void test2() {
Class clazz = Person.class;
// 权限修饰符 数据类型 变量名
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field : declaredFields) {
// 1. 权限修饰符
int modifier = field.getModifiers();
System.out.print(Modifier.toString(modifier) + " ");
// 2. 数据类型
Class type = field.getType();
System.out.print(type.getName() +" ");
// 3. 变量名
String fieldName = field.getName();
System.out.println(fieldName);
}
}
}
7.3 MethodTest#
package cn.edu.nuist.java2;
import cn.edu.nuist.java1.Person;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class MethodTest {
@Test
public void test1() {
Class clazz = Person.class;
// getMethods(): 获取当前运行时类及其所有父类当中声明为public权限的方法
Method[] methods = clazz.getMethods();
for(Method m : methods)
System.out.println(m);
System.out.println("---------------------------");
// 获取当前运行时类中声明的所有方法(不包含父类中声明的)
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods)
System.out.println(m);
}
// @Xxx
// 权限修饰符 返回值类型 方法名(参数类型1 形参名1, ...) throws XxxException {}
@Test
public void test2() {
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods) {
// 1. 获取方法声明的注解
Annotation[] annotations = m.getAnnotations();
for(Annotation a : annotations)
System.out.println(a);
// 2. 权限修饰符
System.out.print(Modifier.toString(m.getModifiers()) + " ");
// 3. 返回值类型
System.out.print(m.getReturnType() + " ");
// 4. 方法名
System.out.print(m.getName());
// 5. 形参
System.out.print("(");
Class[] paramsType = m.getParameterTypes();
if(!(paramsType == null || paramsType.length == 0)) {
int i;
for(i = 0; i < paramsType.length-1; i++)
System.out.print(paramsType[i].getName() + " args_" + i + ", ");
System.out.print(paramsType[i].getName() + " args_" + i);
}
System.out.print(") ");
// 6. 方法抛出的异常
Class<?>[] exceptionTypes = m.getExceptionTypes();
if(!((exceptionTypes == null || exceptionTypes.length == 0))) {
System.out.print("throws ");
for(int i = 0; i < exceptionTypes.length; i++) {
if(i == exceptionTypes.length-1) {
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ", ");
}
}
System.out.println();
}
}
}
7.4 OtherTest#
package cn.edu.nuist.java2;
import cn.edu.nuist.java1.Person;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class OtherTest {
@Test
public void constructors() {
// 获取构造器
Class clazz = Person.class;
// getConstructors(): 获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for(Constructor c : constructors) {
System.out.println(c);
}
System.out.println("---------------------------");
// getDeclaredConstructors(): 获取当前运行时类中所有的构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor c : declaredConstructors)
System.out.println(c);
}
@Test
public void father() {
// 父类
Class clazz = Person.class;
Class superClass = clazz.getSuperclass();
System.out.println(superClass);
// 带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
// 父类的泛型
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
// 获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class) actualTypeArguments[0]).getName());
}
@Test
public void interfaces() {
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for(Class c : interfaces)
System.out.println(c);
System.out.println("-----------------------");
// 父类实现的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c : interfaces1)
System.out.println(c);
}
@Test
public void pack() {
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
@Test
public void classAnnotations() {
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for(Annotation a : annotations)
System.out.println(a);
}
}
8. 调用运行时类的指定结构#
8.1 调用属性#
public class InvokeDemo {
@Test
public void invokeField() throws Exception {
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
// 获取指定的属性(要求必须是声明为public的),所以不咋用
Field publicAttr = clazz.getField("publicAttr");
// 设置当前属性的值
publicAttr.set(p, 1101);
// 获取当前对象的指定属性值
int pAttr = (int) publicAttr.get(p);
System.out.println(pAttr);
}
@Test
public void invokeField2() throws Exception {
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
Field privateAttr = clazz.getDeclaredField("privateAttr");
// 不加↓,报的不是NoSuchFieldException,而是IllegalAccessException
privateAttr.setAccessible(true); // 表示当前属性是可访问的
// 设置当前属性的值
privateAttr.set(p, "LJQ");
// 获取当前对象的指定属性值
String name = (String) privateAttr.get(p);
System.out.println(name);
}
}
8.2 方法和构造器#
public class InvokeDemo2 {
@Test
public void invokeMethod() throws Exception {
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
// 1. 获取指定的某个方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod", String.class);
// 2. 保证当前方法是可访问的
privateMethod.setAccessible(true);
// 3. 调用Method对象的invoke(),该方法的返回值即为对应方法的返回值
Object result = privateMethod.invoke(p, "China");
System.out.println(result); // void 返回 null
}
@Test
public void invokeStaticMethod() throws Exception {
Class clazz = Person.class;
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
staticMethod.setAccessible(true);
// 静态方法,除了传Class对象,传 null 也OK
// 非静态方法才需要知道要调哪个对象的,静态方法不需要
staticMethod.invoke(null);
}
@Test
public void invokeConstructor() throws Exception {
Class clazz = Person.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object o = constructor.newInstance("LiuJiaQi");
System.out.println(o);
}
}
8.3 关于 setAccessible() 的使用#
- Method、Field 和 Constructor 对象都有 setAccessible()
- setAccessible() 是启动和禁用访问安全检查的开关
- 参数值为 false 则指示反射的对象应该实施Java语言访问检查
- 参数值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为 true
- 使得原本无法访问的私有成员也可以访问
9. 反射的应用:动态代理#
9.1 代理设计模式的原理#
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
动态代理使用场合:① 调试 ② 远程方法调用
动态代理相比于静态代理的优点:抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
9.2 Re:静态代理#
由程序员创建或特定工具自动生成源代码,再对其进行编译。在程序运行之前,代理类.class文件就已经被创建,代理类和委托类的关系在运行前就确定。
public class StaticProxyDemo {
public static void main(String[] args) {
NikeClothFactory nike = new NikeClothFactory();
ProxyClothFactory proxy = new ProxyClothFactory(nike);
proxy.produceCloth();
}
}
interface ClothFactory {
void produceCloth();
}
// 代理类
class ProxyClothFactory implements ClothFactory {
private ClothFactory factory; // 用被代理类对象进行实例化
public ProxyClothFactory(ClothFactory factory) {
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工厂做一些准备工作");
factory.produceCloth();
System.out.println("代理工厂做一些收尾工作");
}
}
// 被代理类
class NikeClothFactory implements ClothFactory {
@Override
public void produceCloth() {
System.out.println("Nike工厂生产运动服");
}
}
9.3 动态代理#
动态代理类的源码是在程序运行期间由 JVM 根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
Java 动态代理相关 API:
- Proxy 类
- 专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
- 提供用于创建动态代理类和动态代理对象的静态方法。
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException 创建一个动态代理类所对应的Class对象 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 用来生成动态代理类对象
- InvocationHandler 接口必须实现
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
,以完成代理的具体操作。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
要实现动态代理,需要解决的问题:
Q1: 如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象
Q2: 当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法
*/
public class DynamicProxyDemo {
public static void main(String[] args) {
OrdinaryPeople o = new OrdinaryPeople();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(o);
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("米饭");
System.out.println("---------------------------");
NikeClothFactory nike = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nike);
proxyClothFactory.produceCloth();
}
}
class ProxyFactory {
/**
* 返回一个代理类对象
* @param obj 被代理类的对象
* @return 代理类对象
*/
public static Object getProxyInstance(Object obj) {
// ClassLoader loader, Class<?>[] interfaces, reflect.InvocationHandler h
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader()
, obj.getClass().getInterfaces(), handler);
}
}
// 代理实例的 [调用处理程序] 实现的接口↘ 每个代理实例都具有一个关联的调用处理程序
class MyInvocationHandler implements InvocationHandler {
private Object obj; // 需要使用被代理类的对象进行赋值
public void bind(Object obj) {
this.obj = obj;
}
// 对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("被代理对象相应方法执行之前,做点啥...");
Object ret = method.invoke(obj, args);
System.out.println("被代理对象相应方法执行之后,再做点啥...");
return ret;
}
}
// 被代理类
class OrdinaryPeople implements Human {
@Override
public String getBelief() {
return "I can fly ~~~";
}
@Override
public void eat(String food) {
System.out.println("我要开动了!目标:" + food);
}
}
interface Human {
String getBelief();
void eat(String food);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?