反射
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
一、反射的概述
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、概述
专业的解释(了解一下):
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意属性和方法;
这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
通俗的理解:(掌握)
利用反射创建的对象可以无视修饰符调用类里面的内容
可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。
读取到什么类,就创建什么类的对象
读取到什么方法,就调用什么方法
此时当需求变更的时候不需要修改代码,只要修改配置文件即可。
2、反射的作用?
反射都是从class字节码文件中获取的内容。
- 如何获取class字节码文件的对象
- 利用反射如何获取构造方法(创建对象)
- 利用反射如何获取成员变量(赋值,获取值)
- 利用反射如何获取成员方法(运行)
二、字节码文件和字节码文件对象
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
java文件:就是我们自己编写的java代码。
字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)
**字节码文件对象:**当class文件加载到内存之后,虚拟机自动创建出来的对象。
这个对象里面至少包含了:构造方法,成员变量,成员方法。
而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。
三、获取==字节码文件对象==的三种方式-Class
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、概述
- Class这个类里面的**静态方法forName(“全类名”)(最常用)**
- 通过class属性获取
- 通过对象获取字节码文件对象
2、代码示例
Student类
package com.itheima.myreflect1;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试
package com.itheima.myreflect1;
public class MyReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException {
/*
* 获取class对象的三种方式:
* 1. Class.forName("全类名");
* 2. 类名.class
* 3. 对象.getClass();
*
* */
//1. 第一种方式
//全类名 : 包名 + 类名
//最为常用的
//1.Class这个类里面的静态方法forName
//Class.forName("类的全类名"): 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.myreflect1.Student");
//源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象
//clazz 就表示Student这个类的字节码文件对象。
//就是当Student.class这个文件加载到内存之后,产生的字节码文件对象
//2. 第二种方式- 类名.class
// 一般更多的是当做参数进行传递,锁对象
Class clazz2 = Student.class;
//因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
System.out.println(clazz1 == clazz2);//true
//3.第三种方式
//当我们已经有了这个类的对象时,才可以使用。
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz2 == clazz3);//true
}
}
3、Class.forName(“全类名”)
- Class.forName("类的全类名"): 全类名 = 包名 + 类名
- Class clazz1 = Class.forName("com.itheima.myreflect1.Student");
- 源代码阶段获取
- 先把Student加载到内存中,再获取字节码文件的对象
- clazz 就表示Student这个类的字节码文件对象。
- 就是当Student.class这个文件加载到内存之后,产生的字节码文件对象
//1. 第一种方式
//全类名 : 包名 + 类名
//最为常用的
//1.Class这个类里面的静态方法forName
//Class.forName("类的全类名"): 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.myreflect1.Student");
//源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象
//clazz 就表示Student这个类的字节码文件对象。
//就是当Student.class这个文件加载到内存之后,产生的字节码文件对象
4、类名.class
- Class clazz2 = Student.class;
- 一般更多的是当做参数进行传递,锁对象
-
-
//第二种方式- 类名.class
// 一般更多的是当做参数进行传递,锁对象
Class clazz2 = Student.class;
5、对象.getClass()
第三种方式
- 当我们已经有了这个类的对象时,才可以使用。
- Student s = new Student();
- Class clazz3 = s.getClass();
四、获取==构造方法==-Constructor
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、概述
规则:
- get表示获取
- Declared表示已经声明了的构造方法,包括私有构造方法
- 最后的s表示所有,复数形式
- 如果当前获取到的是私有的,必须要临时修改访问权限,否则无法使用
Class类中用于获取构造方法的方法:
Constructor
<?>
[] getConstructors(): 获取所有public
访问权限的构造方法,返回数组。Constructor
<?>
[] getDeclaredConstructors(): 获取所有构造方法,包括private
、protected
、public
,返回数组。Constructor
<T>
getConstructor(Class<?>
... parameterTypes): 获取单个指定参数类型的public
构造方法。Constructor
<T>
getDeclaredConstructor(Class<?>
... parameterTypes): 获取单个指定参数类型的构造方法**(无论权限修饰符)**。
Constructor类中用于创建对象的方法:
- T newInstance(Object... initargs):使用构造方法实例化对象。
- setAccessible(boolean flag): 设为
true
以取消访问权限检查,允许访问private
构造方法。
方法名 | 说明 |
---|---|
Constructor<?> [] getConstructors() | 获得所有的构造(只能获得public修饰的) |
Constructor<?> [] getDeclaredConstructors() | 获得所有的构造(包含private修饰) |
Constructor<T> getConstructor(Class<?> ... parameterTypes) | 获取指定构造(只能public修饰) |
Constructor<T> getDeclaredConstructor(Class<?> ... parameterTypes) | 获取指定构造(包含private修饰) |
2、反射获取构造方法
(1)获取 Class
对象
要使用反射获取类的构造方法,首先需要获取该类的 Class
对象:
Class clazz = Class.forName("com.itheima.myreflect2.Student");
(2)获取所有构造方法
① 获取 public
访问权限的构造方法
Constructor<?>[] constructors = clazz.getConstructors();
返回值:
Constructor<?>[]
(仅public
访问权限的构造方法)。示例:
javafor (Constructor<?> constructor : constructors) { System.out.println(constructor); }
② 获取所有构造方法(包括 private
、protected
、public
)
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
返回值:
Constructor<?>[]
(包含private
、protected
、public
)。示例:
javafor (Constructor<?> constructor : declaredConstructors) { System.out.println(constructor); }
(3)获取指定参数类型的构造方法
使用 getConstructor(Class<?>... parameterTypes)
或 getDeclaredConstructor(Class<?>... parameterTypes)
获取指定参数类型的构造方法:
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor);
getConstructor()
只能获取public
修饰的构造方法。getDeclaredConstructor()
可以获取任意权限修饰的构造方法。
3、 反射操作构造方法
(1)获取构造方法的修饰符-getModifiers
使用 getModifiers()
获取构造方法的修饰符:
//获取构造方法的修饰符
int modifiers = con4.getModifiers();
System.out.println(modifiers); //私有的返回1
(2)获取构造方法的参数列表-getParameters
使用 getParameters()
获取构造方法的参数:
Parameter[] parameters = constructor.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter);
}
(3)通过构造方法实例化对象-newInstance
使用 newInstance(Object... initargs)
调用构造方法创建对象:
- 如果构造方法是
private
,需要先调用setAccessible(true)
进行暴力反射。 newInstance()
方法传入参数必须与构造方法参数匹配。
Constructor类中用于创建对象的方法:
- T newInstance(Object... initargs):使用构造方法实例化对象。
- setAccessible(boolean flag): 设为
true
以取消访问权限检查,允许访问private
构造方法。
// 5. 取消权限校验(暴力反射),设为 `true` 以取消访问权限检查,允许访问 `private` 构造方法
con4.setAccessible(true);
// 6. 使用获取到的构造方法实例化 `Student` 对象
Student stu = (Student) con4.newInstance("张三", 23);
// 7. 输出实例化后的对象
System.out.println(stu);
4. 总结
方法 | 作用 |
---|---|
getConstructors() | 获取所有 public 访问权限的构造方法 |
getDeclaredConstructors() | 获取所有构造方法(包括 private 、protected 、public ) |
getConstructor(Class<?>... parameterTypes) | 获取指定参数类型的 public 构造方法 |
getDeclaredConstructor(Class<?>... parameterTypes) | 获取指定参数类型的构造方法(无论权限修饰符) |
getModifiers() | 获取构造方法的修饰符 |
getParameters() | 获取构造方法的参数列表 |
setAccessible(true) | 取消权限校验,允许访问 private 构造方法 |
newInstance(Object... initargs) | 通过构造方法实例化对象 |
5、完整代码示例
Student
package com.itheima.myreflect2;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name) {
this.name = name;
}
protected Student(int age) {
this.age = age;
}
private Student(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试
package com.itheima.myreflect2;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class MyReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/*
Class类中用于获取构造方法的方法:
- Constructor<?>[] getConstructors():
获取所有 `public` 访问权限的构造方法。
- Constructor<?>[] getDeclaredConstructors():
获取所有构造方法,包括 `private`、`protected`、`public`。
- Constructor<T> getConstructor(Class<?>... parameterTypes):
获取指定参数类型的 `public` 构造方法。
- Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):
获取指定参数类型的构造方法(无论权限修饰符)。
Constructor类中用于创建对象的方法:
- T newInstance(Object... initargs):
使用构造方法实例化对象。
- setAccessible(boolean flag):
设为 `true` 以取消访问权限检查,允许访问 `private` 构造方法。
*/
// 1. 通过反射获取 `Student` 类的 `Class` 对象
Class clazz = Class.forName("com.itheima.myreflect2.Student");
// 2. 获取所有 `public` 构造方法(仅能获取 `public` 修饰的)
Constructor[] cons1 = clazz.getConstructors();
for (Constructor con : cons1) {
System.out.println(con);
}
// 3. 获取所有构造方法(包括 `private`、`protected`)
Constructor[] cons2 = clazz.getDeclaredConstructors();
for (Constructor con : cons2) {
System.out.println(con);
}
// 4. 获取指定的构造方法:
// 获取无参构造
Constructor con1 = clazz.getDeclaredConstructor();
System.out.println(con1);
// 获取带 `String` 参数的构造方法
Constructor con2 = clazz.getDeclaredConstructor(String.class);
System.out.println(con2);
// 获取带 `int` 参数的构造方法
Constructor con3 = clazz.getDeclaredConstructor(int.class);
System.out.println(con3);
// 获取带 `String` 和 `int` 参数的构造方法
Constructor con4 = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(con4);
// 获取构造方法的修饰符
int modifiers = con4.getModifiers();
System.out.println(modifiers);
// 获取构造方法的参数列表
Parameter[] parameters = con4.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter);
}
// 5. 取消权限校验(暴力反射),设为 `true` 以取消访问权限检查,允许访问 `private` 构造方法
con4.setAccessible(true);
// 6. 使用获取到的构造方法实例化 `Student` 对象
Student stu = (Student) con4.newInstance("张三", 23);
// 7. 输出实例化后的对象
System.out.println(stu);
}
}
五、获取==成员变量==-Field
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1. 获取成员变量的方法
Java 的 Class
类提供了一些方法用于获取类的字段(Field
)对象:
方法 | 说明 |
---|---|
Field[] getFields() | 获取所有 public 修饰的成员变量(包括父类继承的) |
Field[] getDeclaredFields() | 获取本类中 所有(包括 private 、protected 、默认访问权限)的成员变量 |
Field getField(String name) | 获取指定名称的 public 成员变量 |
Field getDeclaredField(String name) | 获取指定名称的成员变量(包括 private ) |
示例
Class<?> clazz = Class.forName("com.itheima.myreflect3.Student");
// 获取所有public成员变量
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
// 获取所有成员变量(包括private、protected、默认)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
// 获取指定的成员变量
Field nameField = clazz.getDeclaredField("name");
System.out.println(nameField);
2、获取成员变量的属性
Field
类提供了多个方法来获取成员变量的详细信息:
方法 | 说明 |
---|---|
int getModifiers() | 获取成员变量的修饰符(public、private、static等) |
String getName() | 获取成员变量的名称 |
Class<?> getType() | 获取成员变量的数据类型 |
示例
Field nameField = clazz.getDeclaredField("name");
// 获取修饰符
int modifiers = nameField.getModifiers();
System.out.println("修饰符: " + modifiers);
// 获取成员变量名称
String fieldName = nameField.getName();
System.out.println("成员变量名称: " + fieldName);
// 获取成员变量的数据类型
Class<?> fieldType = nameField.getType();
System.out.println("数据类型: " + fieldType);
3、访问和修改成员变量的值
反射允许我们读取和修改对象的字段,即使是 private
修饰的字段。
(1)读取字段值
方法 | 说明 |
---|---|
Object get(Object obj) | 获取指定对象 obj 的成员变量值 |
Student student = new Student("zhangsan", 23, "男");
Field nameField = clazz.getDeclaredField("name");
// 取消权限检查,允许访问 private 字段
nameField.setAccessible(true);
// 获取成员变量的值
String value = (String) nameField.get(student);
System.out.println("原始值: " + value);
(2)修改字段值
方法 | 说明 |
---|---|
void set(Object obj, Object value) | 为指定对象 obj 的成员变量赋值 |
// 修改对象的 name 字段
nameField.set(student, "lisi");
System.out.println("修改后: " + student);
2、完整的示例代码
Student
package com.itheima.myreflect3;
public class Student {
private String name;
private int age;
public String gender;
public Student() {
}
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + "}";
}
}
测试
package com.itheima.myreflect3;
import java.lang.reflect.Field;
public class MyReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
/*
Class类中用于获取成员变量的方法
Field[] getFields(): 返回所有公共成员变量对象的数组
Field[] getDeclaredFields(): 返回所有成员变量对象的数组
Field getField(String name): 返回单个公共成员变量对象
Field getDeclaredField(String name):返回单个成员变量对象
Field类中用于创建对象的方法
void set(Object obj, Object value):赋值
Object get(Object obj) 获取值
*/
//1.获取class字节码文件的对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.获取所有的public的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
//获取单个的成员变量
Field name = clazz.getDeclaredField("name");
System.out.println(name);
//获取权限修饰符
int modifiers = name.getModifiers();
System.out.println(modifiers);
//获取成员变量的名字
String n = name.getName();
System.out.println(n);
//获取成员变量的数据类型
Class<?> type = name.getType();
System.out.println(type);
//获取成员变量记录的值
Student s = new Student("zhangsan",23,"男");
name.setAccessible(true);
String value = (String) name.get(s);
System.out.println(value);
//修改对象里面记录的值
name.set(s,"lisi");
System.out.println(s);
}
}
六、获取==成员方法==-Method
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、概述
2、Class 类用于获取成员方法的方法
方法 | 说明 |
---|---|
Method[] getMethods() | 获取 所有 public 方法,包括 继承 的方法 |
Method[] getDeclaredMethods() | 获取 本类中 声明的所有方法(包括 private 、protected 、default 访问权限),但 不包含继承 的方法 |
Method getMethod(String name, Class<?>... parameterTypes) | 获取 指定的 单个 public 方法,包括继承的方法 |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 获取 本类中声明的指定的单个方法(可获取 private 方法) |
3、Method 类用于操作成员方法
方法 | 说明 |
---|---|
Object invoke(Object obj, Object... args) | 运行方法,其中:obj 是调用该方法的对象,args 是传递给方法的参数 |
int getModifiers() | 获取方法的修饰符(可使用 Modifier 类解析) |
String getName() | 获取方法名称 |
Parameter[] getParameters() | 获取方法的参数列表 |
Class<?> getReturnType() | 获取方法的返回类型 |
Class<?>[] getExceptionTypes() | 获取方法声明抛出的异常类型 |
setAccessible(boolean flag) | 设置访问权限(true 取消权限检查,可访问 private 方法) |
4、获取成员方法对象
(1)获取所有公共方法
- 方法:
Method[] getMethods()
- 说明: 返回当前类及其父类中所有 public 的方法(包括继承的)。
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
(2)获取当前类中所有声明的方法
- 方法:
Method[] getDeclaredMethods()
- 说明: 返回当前类中声明的所有方法(包括
private
、protected
、默认访问权限的方法),但不包括父类中继承的方法。
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
(3)获取指定的方法
- 方法:
Method getMethod(String name, Class<?>... parameterTypes)
:获取指定名称及参数类型的 public 方法。Method getDeclaredMethod(String name, Class<?>... parameterTypes)
:获取指定名称及参数类型的方法,不受权限限制。
// 例如:获取 Student 类中带有 String 参数的 eat 方法
Method m = clazz.getDeclaredMethod("eat", String.class);
System.out.println(m);
5、方法对象的基本信息
通过 Method
类可以获取方法的各种属性,包括修饰符、名称、参数列表、返回值类型以及抛出的异常等。
(1)获取方法修饰符
- 方法:
int getModifiers()
- 说明: 返回一个整型数值,表示该方法的访问权限(如 public、private、static 等)。
int modifiers = m.getModifiers();
System.out.println(modifiers);
(2)获取方法名称
- 方法:
String getName()
- 说明: 返回方法名。
String name = m.getName();
System.out.println(name);
(3)获取方法参数列表
- 方法:
Parameter[] getParameters()
- 说明: 返回一个
Parameter
数组,包含了方法中声明的所有参数信息。
Parameter[] parameters = m.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter);
}
(4)获取方法抛出的异常类型
- 方法:
Class[] getExceptionTypes()
- 说明: 返回该方法声明抛出的异常类型的数组。
Class[] exceptionTypes = m.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
System.out.println(exceptionType);
}
6、运行方法(方法调用)
通过反射可以在运行时动态调用方法,这对框架开发和工具类编程非常有用。
(1)调用方法
- 方法:
Object invoke(Object obj, Object... args)
- 说明: 调用当前 Method 对象表示的方法。
- 参数一: 调用该方法的对象实例。
- 参数二: 调用方法时传入的实际参数。
- 返回值: 方法执行后的返回值(无返回值则为
null
)。
注意: 如果方法不是
public
,则必须先调用setAccessible(true)
取消权限检查。
// 创建 Student 对象
Student s = new Student();
// 取消权限校验(如果方法为 private)
m.setAccessible(true);
// 调用方法,传入参数 "汉堡包",返回结果为 String 类型
String result = (String) m.invoke(s, "汉堡包");
System.out.println(result);
7、完整示例代码
Student
package com.itheima.myreflect4;
import java.io.IOException;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void sleep(){
System.out.println("睡觉");
}
private String eat(String something) throws IOException,NullPointerException,ClassCastException {
System.out.println("在吃" + something);
return "奥利给";
}
private void eat(String something,int a) {
System.out.println("在吃" + something);
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
测试
package com.itheima.myreflect4;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class MyReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
/*
Class类中用于获取成员方法的方法
Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name, Class<?>... parameterTypes) :返回单个公共成员方法对象
Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回单个成员方法对象
Method类中用于创建对象的方法
Object invoke(Object obj, Object... args):运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
获取方法的修饰符
获取方法的名字
获取方法的形参
获取方法的返回值
获取方法的抛出的异常
*/
// 1. 获取class字节码文件对象
Class clazz = Class.forName("com.itheima.myreflect4.Student");
// 2. 获取里面所有的方法对象(包含父类中所有的公共方法)
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println(method);
}
// 3. 获取里面所有的方法对象(不能获取父类的,但是可以获取本类中私有的方法)
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
// 4. 获取指定的单一方法 ,例如 `eat(String food)`
Method m = clazz.getDeclaredMethod("eat", String.class);
System.out.println(m);
// 5. 获取方法修饰符
System.out.println("Modifiers: " + method.getModifiers());
// 6. 获取方法名
System.out.println("Method Name: " + method.getName());
// 7. 获取方法参数
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
System.out.println("Parameter: " + parameter);
}
// 8. 获取方法的返回类型
System.out.println("Return Type: " + method.getReturnType());
// 9. 获取方法的抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
System.out.println("Throws Exception: " + exceptionType);
}
//方法运行
/*Method类中用于创建对象的方法
Object invoke(Object obj, Object... args):运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)*/
// 10. 通过反射调用方法
Student s = new Student();
m.setAccessible(true); // 取消访问限制,允许访问 private 方法
//参数一s:表示方法的调用者
//参数二"汉堡包":表示在调用方法的时候传递的实际参数
String result = (String) m.invoke(s, "汉堡包");
System.out.println(result);
}
}
七、反射的作用
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、动态加载和运行代码
- 运行时获取类信息:反射可以在程序运行时获取类、方法、字段、构造方法等详细信息,而不需要在编译时就确定所有细节。
- 动态创建对象:可以通过反射在运行时实例化对象,即使类的名称和构造方法在编译时未知。
- 动态调用方法:根据需要调用任意方法,使程序具有更高的灵活性。
2、降低耦合度
- 解耦代码:使用反射可以让代码不直接依赖具体的类,通过接口和配置文件动态加载实现类,提高系统的扩展性和灵活性。
- 插件化和框架设计:许多框架(如 Spring、Hibernate、MyBatis)依赖反射来实现自动装配、依赖注入、ORM 映射等功能,使得业务逻辑与底层实现分离。
3、方便工具开发和调试
- 测试工具:利用反射可以在不修改源码的情况下访问私有成员,这对于编写单元测试、调试和性能分析工具非常有帮助。
- 序列化和反序列化:反射使得对象与其字节流之间的转换更加通用和自动化,例如 JSON、XML 等数据格式的解析。
4、支持框架和中间件
- 框架内部机制:很多大型框架和中间件通过反射来动态扫描类、加载配置、生成代理对象,进而实现 AOP(面向切面编程)、IOC(控制反转)等设计模式。
- 注解处理:反射结合注解(Annotation)可以实现灵活的配置管理、元数据分析等功能。
5、灵活扩展和动态配置
- 扩展性:通过反射,可以在程序运行时根据配置加载和使用不同的类,从而实现灵活扩展和动态配置,而无需重新编译程序。
- 插件架构:支持插件架构的应用程序通常使用反射来动态加载和管理插件,提高系统的灵活性和可扩展性。
八、综合练习
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
1、保存对象信息
Student
package com.itheima.myreflect5;
public class Student {
private String name;
private int age;
private char gender;
private double height;
private String hobby;
public Student() {
}
public Student(String name, int age, char gender, double height, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.hobby = hobby;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}";
}
}
Teacher
package com.itheima.myreflect5;
public class Teacher {
private String name;
private double salary;
public Teacher() {
}
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String toString() {
return "Teacher{name = " + name + ", salary = " + salary + "}";
}
}
测试
package com.itheima.myreflect5;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
public class MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException {
/*
对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
*/
Student s = new Student("小A",23,'女',167.5,"睡觉");
Teacher t = new Teacher("播妞",10000);
saveObject(s);
}
//把对象里面所有的成员变量名和值保存到本地文件中
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
//1.获取字节码文件的对象
Class clazz = obj.getClass();
//2. 创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\\a.txt"));
//3. 获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); //临时取消访问权限
//获取成员变量的名字
String name = field.getName();
//获取成员变量的值
Object value = field.get(obj);
//写出数据
bw.write(name + "=" + value);
bw.newLine();
}
bw.close();
}
}
2、结合配置文件
Student
package com.itheima.myreflect6;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void study(){
System.out.println("学生在学习!");
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
Teacher
package com.itheima.myreflect6;
public class Teacher {
private String name;
private double salary;
public Teacher() {
}
public Teacher(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void teach(){
System.out.println("老师在教书!");
}
public String toString() {
return "Teacher{name = " + name + ", salary = " + salary + "}";
}
}
prop.properties配置文件
classname=com.itheima.myreflect6.Student
method=study
测试
package com.itheima.myreflect6;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class MyReflectDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/*
反射可以跟配置文件结合的方式,动态的创建对象,并调用方法
*/
//1.读取配置文件中的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("myreflect\\prop.properties");
prop.load(fis);
fis.close();
System.out.println(prop);
//2.获取全类名和方法名
String className = (String) prop.get("classname"); //获取全类名
String methodName = (String) prop.get("method"); //获取方法名
System.out.println(className);
System.out.println(methodName);
//3.利用反射创建对象并运行方法
Class clazz = Class.forName(className); //获取字节码文件对象
//获取构造方法
Constructor con = clazz.getDeclaredConstructor();
Object o = con.newInstance();
System.out.println(o);
//获取成员方法并运行
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
method.invoke(o);
}
}
九、总结
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
动态代理
更新: 2025/4/9 字数: 0 字 时长: 0 分钟
- 无侵入式的给方法增强功能
动态代理是一种在运行时创建代理对象的技术,它能够在不修改原有代码的情况下,对目标对象的方法调用进行拦截和增强,从而实现如日志记录、权限校验、事务管理等横切关注点的统一处理。
1、动态代理是什么?
定义:动态代理指在程序运行时,根据指定接口自动生成代理类,并通过该代理类调用目标对象的方法,同时在调用前后插入额外的逻辑。
实现方式:在 Java 中,主要有两种动态代理方式:
- JDK 动态代理:适用于目标对象实现了接口的情况,通过
java.lang.reflect.Proxy
和InvocationHandler
接口实现。
- CGLIB 动态代理:适用于目标对象没有实现接口的情况,通过生成目标类的子类来实现代理。
- JDK 动态代理:适用于目标对象实现了接口的情况,通过
2、为什么需要动态代理?
- 解耦和扩展
- 将横切关注点(例如日志、事务、权限检查等)与业务逻辑分离,降低模块之间的耦合度。
- 减少冗余代码
- 通过代理可以将公共的前置和后置逻辑封装在一个地方,避免在每个方法中重复编写相同代码。
- 运行时灵活性
- 可以在运行时决定是否代理某个对象,以及如何增强其方法调用,不需要在编译期确定。
- 动态配置和切面编程(AOP)
- 很多框架(如 Spring AOP)正是基于动态代理来实现横切逻辑的插入,使系统更灵活、配置更简单。
3、动态代理“长什么样”
动态代理生成的代理对象在外部看来和目标对象完全一致,因为它们**实现了相同的接口或继承自同一个类**。但在内部,代理对象包装了目标对象,并在调用目标方法时先执行一些额外逻辑。下面是一个简单的 JDK 动态代理示例,展示了代理对象的“长相”:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 目标接口
interface Service {
void execute(String task);
}
// 目标对象实现类
class ServiceImpl implements Service {
@Override
public void execute(String task) {
System.out.println("执行任务:" + task);
}
}
// 动态代理处理器
class ServiceInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
public ServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强逻辑
System.out.println("【代理前】记录日志或权限校验");
// 调用目标方法
Object result = method.invoke(target, args);
// 后置增强逻辑
System.out.println("【代理后】记录日志或事务提交");
return result;
}
}
public class DynamicProxyDemo {
public static void main(String[] args) {
// 创建目标对象
Service service = new ServiceImpl();
// 创建代理对象
Service proxyInstance = (Service) Proxy.newProxyInstance(
service.getClass().getClassLoader(),
new Class[]{Service.class},
new ServiceInvocationHandler(service)
);
// 通过代理对象调用方法
proxyInstance.execute("发送邮件");
}
}
输出示例:
【代理前】记录日志或权限校验
执行任务:发送邮件
【代理后】记录日志或事务提交
从这个示例中可以看出,代理对象 proxyInstance
与目标对象 ServiceImpl
实现了相同的接口 Service
,调用时不仅执行了目标方法,还插入了前置和后置的增强逻辑。这就是动态代理“长什么样”的体现:外部接口不变,内部包装了一层拦截器,灵活地处理方法调用。
总结
- 动态代理允许在运行时创建一个代理对象来包装目标对象,从而在方法调用时动态插入额外逻辑。
- 需要动态代理主要是为了降低耦合、实现横切关注点(如日志、权限、事务)统一管理,并提高系统的灵活性和可扩展性。
- 代理对象的外观与目标对象一致,但在内部会执行预定义的前置、后置逻辑,使得代码更加简洁和易于维护。
4、代码实现-newProxyInstance
1、真正干活的对象
2、代理对象
3、利用代理调用方法
切记一点:代理可以增强或者拦截的方法都在接口中,接口需要写在newProxyInstance的第二个参数里。
(1)Star接口
package com.itheima.mydynamicproxy1;
public interface Star {
//我们可以把所有想要被代理的方法定义在接口当中
//唱歌
public abstract String sing(String name);
//跳舞
public abstract void dance();
}
(2)BigStar实现类
package com.itheima.mydynamicproxy1;
public class BigStar implements Star {
private String name;
public BigStar() {
}
public BigStar(String name) {
this.name = name;
}
//唱歌
@Override
public String sing(String name){
System.out.println(this.name + "正在唱" + name);
return "谢谢";
}
//跳舞
@Override
public void dance(){
System.out.println(this.name + "正在跳舞");
}
public String toString() {
return "BigStar{name = " + name + "}";
}
}
(3)代理工具类-ProxyUtil
package com.itheima.mydynamicproxy1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*
*
* 类的作用:
* 创建一个代理
*
* */
public class ProxyUtil {
/*
*
* 方法的作用:
* 给一个明星的对象,创建一个代理
*
* 形参:
* 被代理的明星对象
*
* 返回值:
* 给明星创建的代理
*
*
*
* 需求:
* 外面的人想要大明星唱一首歌
* 1. 获取代理的对象
* 代理对象 = ProxyUtil.createProxy(大明星的对象);
* 2. 再调用代理的唱歌方法
* 代理对象.唱歌的方法("只因你太美");
* */
public static Star createProxy(BigStar bigStar){
/* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:用于指定用哪个类加载器,去加载生成的代理类
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情*/
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类
new Class[]{Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
//参数三:用来指定生成的代理对象要干什么事情
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 参数一:代理的对象
* 参数二:要运行的方法 sing
* 参数三:调用sing方法时,传递的实参
* */
if("sing".equals(method.getName())){
System.out.println("准备话筒,收钱");
}else if("dance".equals(method.getName())){
System.out.println("准备场地,收钱");
}
//去找大明星开始唱歌或者跳舞
//代码的表现形式:调用大明星里面唱歌或者跳舞的方法
return method.invoke(bigStar,args);
}
}
);
return star;
}
}
(4)测试类Test
package com.itheima.mydynamicproxy1;
public class Test {
public static void main(String[] args) {
/*
需求:
外面的人想要大明星唱一首歌
1. 获取代理的对象
代理对象 = ProxyUtil.createProxy(大明星的对象);
2. 再调用代理的唱歌方法
代理对象.唱歌的方法("只因你太美");
*/
//1. 获取代理的对象
BigStar bigStar = new BigStar("鸡哥");
Star proxy = ProxyUtil.createProxy(bigStar);
//2. 调用唱歌的方法
String result = proxy.sing("只因你太美");
System.out.println(result);
}
}
(5)额外扩展-拦截方法
动态代理,还可以拦截方法
比如:
在这个故事中,经济人作为代理,如果别人让邀请大明星去唱歌,打篮球,经纪人就增强功能。
但是如果别人让大明星去扫厕所,经纪人就要拦截,不会去调用大明星的方法。
/*
* 类的作用:
* 创建一个代理
* */
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("cleanWC".equals(method.getName())){
System.out.println("拦截,不调用大明星的方法");
return null;
}
//如果是其他方法,正常执行
return method.invoke(bigStar,args);
}
}
);
return star;
}
}
5 、动态代理的练习-对List集合的add方法增强
对add方法进行增强,对remove方法进行拦截,对其他方法不拦截也不增强
public class MyProxyDemo1 {
public static void main(String[] args) {
//动态代码可以增强也可以拦截
//1.创建真正干活的人
ArrayList<String> list = new ArrayList<>();
//2.创建代理对象
//参数一:类加载器。当前类名.class.getClassLoader()
// 找到是谁,把当前的类,加载到内存中了,我再麻烦他帮我干一件事情,把后面的代理类,也加载到内存
//参数二:是一个数组,在数组里面写接口的字节码文件对象。
// 如果写了List,那么表示代理,可以代理List接口里面所有的方法,对这些方法可以增强或者拦截
// 但是,一定要写ArrayList真实实现的接口
// 假设在第二个参数中,写了MyInter接口,那么是错误的。
// 因为ArrayList并没有实现这个接口,那么就无法对这个接口里面的方法,进行增强或拦截
//参数三:用来创建代理对象的匿名内部类
List proxyList = (List) Proxy.newProxyInstance(
//参数一:类加载器
MyProxyDemo1.class.getClassLoader(),
//参数二:是一个数组,表示代理对象能代理的方法范围
new Class[]{List.class},
//参数三:本质就是代理对象
new InvocationHandler() {
@Override
//invoke方法参数的意义
//参数一:表示代理对象,一般不用(了解)
//参数二:就是方法名,我们可以对方法名进行判断,是增强还是拦截
//参数三:就是下面第三步调用方法时,传递的参数。
//举例1:
//list.add("阿玮好帅");
//此时参数二就是add这个方法名
//此时参数三 args[0] 就是 阿玮好帅
//举例2:
//list.set(1, "aaa");
//此时参数二就是set这个方法名
//此时参数三 args[0] 就是 1 args[1]"aaa"
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//对add方法做一个增强,统计耗时时间
if (method.getName().equals("add")) {
long start = System.currentTimeMillis();
//调用集合的方法,真正的添加数据
method.invoke(list, args);
long end = System.currentTimeMillis();
System.out.println("耗时时间:" + (end - start));
//需要进行返回,返回值要跟真正增强或者拦截的方法保持一致
return true;
}else if(method.getName().equals("remove") && args[0] instanceof Integer){
System.out.println("拦截了按照索引删除的方法");
return null;
}else if(method.getName().equals("remove")){
System.out.println("拦截了按照对象删除的方法");
return false;
}else{
//如果当前调用的是其他方法,我们既不增强,也不拦截
method.invoke(list,args);
return null;
}
}
}
);
//3.调用方法
//如果调用者是list,就好比绕过了第二步的代码,直接添加元素
//如果调用者是代理对象,此时代理才能帮我们增强或者拦截
//每次调用方法的时候,都不会直接操作集合
//而是先调用代理里面的invoke,在invoke方法中进行判断,可以增强或者拦截
proxyList.add("aaa");
proxyList.add("bbb");
proxyList.add("ccc");
proxyList.add("ddd");
proxyList.remove(0);
proxyList.remove("aaa");
//打印集合
System.out.println(list);
}
}