目录
[1. Java 反射概述](#1. Java 反射概述)
[1.1 反射的定义](#1.1 反射的定义)
[1.2 反射的用途](#1.2 反射的用途)
[1.3 反射与类加载器的关系](#1.3 反射与类加载器的关系)
[2. 反射的基本操作](#2. 反射的基本操作)
[2.1 获取类的信息:Class 类](#2.1 获取类的信息:Class 类)
[2.2 创建对象:Constructor 类](#2.2 创建对象:Constructor 类)
[2.3 获取成员变量:Field 类](#2.3 获取成员变量:Field 类)
[2.4 获取方法:Method 类](#2.4 获取方法:Method 类)
[2.5 获取构造方法:Constructor 类](#2.5 获取构造方法:Constructor 类)
[3. 反射与性能问题](#3. 反射与性能问题)
[4. Java 反射应用场景](#4. Java 反射应用场景)
[3.1 反射的性能开销](#3.1 反射的性能开销)
[3.2 如何优化反射的使用](#3.2 如何优化反射的使用)
[4.1 配置文件的自动加载(如 Spring 框架中的 Bean 自动装配)](#4.1 配置文件的自动加载(如 Spring 框架中的 Bean 自动装配))
[4.2 动态代理](#4.2 动态代理)
1. Java 反射概述
##### 1.1 反射的定义
* 反射是指在程序运行时,能够动态地获取类的元数据,并且可以操作该类的对象、构造器、字段、方法等。反射主要通过 Java 的 `Class` 类和相关的 `Field`、`Method`、`Constructor` 类实现。
* 反射的基本操作包括:
* **获取类的信息** :通过 `Class` 类。
* **实例化对象** :通过 `Constructor` 类。
* **访问字段** :通过 `Field` 类。
* **调用方法** :通过 `Method` 类。
* **获取类的构造方法** :通过 `Constructor` 类。
##### 1.2 反射的用途
* **动态加载类**:
* 在程序运行时动态加载类,不需要在编译时确定类。
* 通过 `Class.forName()` 方法可以加载指定的类,例如在 JDBC 中动态加载数据库驱动。
* **动态创建对象**:
* 通过反射可以在程序运行时创建类的实例,而无需显式的 `new` 操作。
* **访问和修改对象属性**:
* 通过反射,可以访问和修改对象的私有字段(包括私有字段、方法、构造器等),这是常规方法不能做到的。
* **调用方法**:
* 可以通过反射动态调用对象的任何方法,无论是公共方法还是私有方法。
* **框架设计**:
* 许多框架(如 Spring、Hibernate)都依赖于反射来实现功能,如动态生成对象、自动注入依赖、处理注解等。
* **开发工具和库**:
* 反射常用于开发通用的工具类库、代码生成工具、插件框架等。
* **实现动态代理**:
* 通过 `Proxy` 类和反射,可以实现动态代理机制,这在 AOP(面向切面编程)等场景中非常常见。
##### 1.3 反射与类加载器的关系
* **类加载器的基本概念**:
* 类加载器是 Java 虚拟机(JVM)的一部分,负责在程序运行时加载类的字节码文件(`.class` 文件)。它可以根据需要加载、卸载类,并管理类的生命周期。
* 类加载器有三种主要类型:
* **Bootstrap ClassLoader**:加载 JDK 核心类库。
* **Extension ClassLoader**:加载 JDK 扩展目录下的类库。
* **Application ClassLoader**:加载应用程序类路径下的类。
* **反射与类加载器的结合**:
* 反射通常通过 `Class.forName()` 方法加载类。这个方法不仅是反射的一部分,还是由类加载器来加载指定类的字节码的。
* 反射操作需要类的 `Class` 对象,而 `Class` 对象的获取方式包括通过类加载器加载类的字节码。在 Java 中,类加载器和反射配合使用,使得类可以在运行时动态地被加载并操作。
* **动态加载与反射**:
* 通过反射,开发者可以在运行时动态决定要加载的类。例如,JDBC 连接池的实现通常会使用反射来加载数据库驱动类,这样程序就能够在没有提前知道驱动类的情况下运行。
* **不同类加载器的反射行为**:
* 反射的类加载通常由类加载器控制,不同的类加载器可能会加载相同的类(例如,用户定义的类和 JDK 类)。这意味着在使用反射时,类加载器可能会影响类的访问权限,尤其是在存在不同类加载器隔离的环境下(如应用服务器中不同模块之间的类加载隔离)。
2. 反射的基本操作
##### 2.1 获取类的信息:`Class` 类
* **基本概念**
* 在 Java 中,反射的第一步通常是获取类的元数据(即类本身的信息)。这通常通过 `Class` 类来完成。`Class` 类是 Java 反射机制的核心,它代表了类的结构信息,包括类名、字段、方法、构造器等。通过反射,可以动态地获取类的定义,并操作类的成员。
* **`Class` 类**
* `Class` 类是每个 Java 类的元类(metaclass),它包含了该类的所有信息,如类名、字段、方法、构造方法、父类、实现的接口等。每个类都有一个唯一的 `Class` 对象,它是在类加载时由 Java 虚拟机自动创建的。
* **获取 `Class` 对象的方式**:
* `Class.forName(String className)`:通过类的全限定名(即类路径)获取 `Class` 对象。
* `.getClass()`:通过对象实例获取 `Class` 对象。
* `.class`:通过类名直接获取 `Class` 对象。
* **`Class.forName()`**
* `Class.forName(String className)` 是一个静态方法,它根据给定的类名(全路径名)动态加载类,并返回该类的 `Class` 对象。这个方法常用于反射中,尤其是当你在编写通用代码或框架时,不知道具体类的情况下,动态加载和反射类信息。
* **返回类型** :返回 `Class<?>` 类型的对象,表示加载的类的元数据。
* **异常** :`ClassNotFoundException`,如果指定的类无法找到,会抛出该异常。
*
try {
// 通过类的全路径名称加载类
Class<?> clazz = Class.forName("java.util.ArrayList");
System.out.println("Class Name: " + clazz.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
* **`getClass()`** 方法
* `getClass()` 是 `Object` 类的方法(因为所有类都继承自 `Object`),用于获取当前对象的 `Class` 对象。当你有一个类的实例时,可以调用该方法来获取该实例的元数据。
* **返回类型** :返回当前对象的 `Class` 对象。
* **注意** :`getClass()` 是一个实例方法,只能通过对象实例调用
*
String str = "Hello, World!";
Class<?> clazz = str.getClass();
System.out.println("Class Name: " + clazz.getName());
* **`getName()`、`getSimpleName()`** 等方法
* **通过 `Class` 对象,可以调用以下方法来获取类的相关信息:**
* **`getName()` 方法返回类的全限定名,即包括包名的类名。**
* **`getCanonicalName()` 返回类的规范名称,对于嵌套类,它返回外部类的名称加上嵌套类的名称。**
* **`getPackage()` 方法返回类所在包的 `Package` 对象,可以通过 `Package` 对象获取包的名称。**
* **getModifiers():返回类的访问修饰符(如 `public`、`private`、`protected`)。**
* **getSuperclass():返回该类的父类的 `Class` 对象。**
* **getInterfaces():返回该类实现的接口数组。**
* **getDeclaredFields():返回类声明的所有字段(包括私有字段)。**
* **getDeclaredMethods():返回类声明的所有方法(包括私有方法)。**
* **getDeclaredConstructors():返回类声明的所有构造方法(包括私有构造方法)。**
*
public static void main(String[] args) {
try {
// 1. 使用 Class.forName() 获取 Class 对象
Class<?> clazz = Class.forName("java.lang.String");
// 2. 使用 getName() 获取全类名
System.out.println("Full Class Name: " + clazz.getName());
// 3. 使用 getSimpleName() 获取类名
System.out.println("Simple Class Name: " + clazz.getSimpleName());
// 4. 获取父类
Class<?> superClass = clazz.getSuperclass();
System.out.println("Superclass: " + superClass.getName());
// 5. 获取实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
System.out.println("Interfaces:");
for (Class<?> iface : interfaces) {
System.out.println(iface.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
![](https://i-blog.csdnimg.cn/direct/e25d912564ff456f84ad24ffb766531f.png)
##### 2.2 创建对象:`Constructor` 类
* 通过构造器实例化对象
* 要通过反射创建对象,首先需要获取类的 `Constructor` 对象,然后调用 `newInstance()` 方法来实例化对象。
*
```java
package com.lirui;
import java.lang.reflect.Constructor;
public class DemoService {
public static void main(String[] args) throws Exception {
// 获取类的 Class 对象
Class<?> clazz = MyClass.class;
// 获取带参数构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 通过构造器创建对象
MyClass obj = (MyClass) constructor.newInstance("张三", 25);
obj.sayHello();
}
}
class MyClass {
private final String name;
private final int age;
// 带参数的构造器
public MyClass(String name, int age) {
this.name = name;
this.age = age;
System.out.println(name + age);
}
public void sayHello() {
System.out.println("Hello" + name + age);
}
}
```
* 私有构造器访问
*
```java
package com.lirui;
import java.lang.reflect.Constructor;
public class DemoService {
public static void main(String[] args) throws Exception {
// 获取类的 Class 对象
Class<?> clazz = MyClass.class;
// 获取带参数构造器
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
// 通过构造器创建对象
MyClass obj = (MyClass) constructor.newInstance("张三", 25);
obj.sayHello();
}
}
class MyClass {
private final String name;
private final int age;
// 私有带参数的构造器
private MyClass(String name, int age) {
this.name = name;
this.age = age;
System.out.println(name + age);
}
public void sayHello() {
System.out.println("Hello" + name + age);
}
}
```
##### 2.3 获取成员变量:`Field` 类
* `Field` 类
* `Field` 类是 Java 反射机制中提供的一个类,表示类的字段。你可以使用它来获取字段信息、修改字段值、设置字段可访问性等
* 通过反射获取字段
* 通过反射获取字段,可以使用 `Class` 类的 `getDeclaredField()` 或 `getField()` 方法。区别在于:
* `getField()` 只能获取公有字段(`public`)。
* `getDeclaredField()` 可以获取包括私有字段(`private`)在内的所有字段。
* 获取/修改字段值
* `Field` 类提供了两个重要方法来获取字段和修改字段值:
* `get(Object obj)`:获取字段的值。
* `set(Object obj, Object value)`:修改字段的值。
*
```java
package com.lirui;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class DemoService {
public static void main(String[] args) throws Exception {
// 创建对象
MyClass obj = new MyClass("张三", 30);
// 获取 Class 对象
Class<?> clazz = obj.getClass();
// 获取 public 字段
Field field = clazz.getField("name");
// 获取字段的值
String nameValue = (String) field.get(obj);
System.out.println(" name: " + nameValue);
// 修改字段的值
field.set(obj, "李四");
// 再次获取字段的值
nameValue = (String) field.get(obj);
System.out.println("name: " + nameValue);
}
}
class MyClass {
public String name;
private int age;
public MyClass(String name, int age) {
this.name = name;
this.age = age;
}
}
```
##### 2.4 获取方法:`Method` 类
* `Method` 类
* 通过反射获取方法,可以使用 `Class` 类的 `getMethod()` 或 `getDeclaredMethod()` 方法。它们的区别是:
* `getMethod()` 只能获取公共方法(`public`)。
* `getDeclaredMethod()` 可以获取类的所有方法,包括私有方法。
* 通过反射调用方法 /私有的换换就行了
*
```java
package com.lirui;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class DemoService {
public static void main(String[] args) throws Exception {
// 创建对象
MyClass obj = new MyClass();
// 获取 Class 对象
Class<?> clazz = obj.getClass();
// 获取方法 sum(int, int)
Method method = clazz.getMethod("sum", int.class, int.class);
// 调用方法并传递参数
Object result = method.invoke(obj, 5, 3); // 输出:Sum is: 8
System.out.println("Result: " + result);
}
}
class MyClass {
public int sum(int a, int b) {
int result = a + b;
System.out.println("Sum is: " + result);
return result;
}
}
```
##### 2.5 获取构造方法:`Constructor` 类
* 获取类的构造方法
* 你可以使用 `Class` 对象的 `getConstructor` 或 `getDeclaredConstructor` 方法来获取类的构造方法。`getConstructor` 获取公共构造方法,而 `getDeclaredConstructor` 获取所有构造方法(包括私有构造方法)。
* 使用反射实例化对象
*
```java
public static void main(String[] args) {
try {
// 获取 Class 对象
Class<?> clazz = Class.forName("com.example.MyClass");
// 获取无参构造方法
Constructor<?> noArgConstructor = clazz.getConstructor();
System.out.println("无参构造方法: " + noArgConstructor);
// 获取带参构造方法
Constructor<?> paramConstructor = clazz.getConstructor(String.class, int.class);
System.out.println("带参构造方法: " + paramConstructor);
// 使用反射实例化对象
Object instance1 = noArgConstructor.newInstance();
System.out.println("实例化的对象: " + instance1);
Object instance2 = paramConstructor.newInstance("Hello", 42);
System.out.println("实例化的对象: " + instance2);
} catch (Exception e) {
e.printStackTrace();
}
}
```
3. 反射与性能问题
4. Java 反射应用场景
##### 3.1 反射的性能开销
* **方法调用延迟:** 通过反射调用方法时,JVM 无法进行正常的内联优化。相比于直接调用,反射需要额外的步骤,如查找方法、参数类型检查、权限验证等。
* **类型检查:** 反射调用方法时,JVM 会执行额外的类型检查和转换,尤其是对于泛型类型或参数类型的匹配,这些都会增加性能开销。
* **反射缓存:** 每次使用反射获取类信息(如方法、字段等)时,都会进行一定的查找过程。频繁的查找和实例化对象可能导致性能问题。
* **不可预测性:** 反射无法享受编译时类型检查和优化。JVM 不能对反射操作进行 JIT(即时编译)优化,因此会影响执行效率。
##### 3.2 如何优化反射的使用
* **缓存反射结果,避免重复查找。**
*
```java
// 缓存方法信息
private static final Map<String, Method> methodCache = new HashMap<>();
public static Object invokeMethod(Object obj, String methodName, Object[] params) throws Exception {
Method method = methodCache.get(methodName);
if (method == null) {
// 如果缓存中没有,获取并缓存方法
method = obj.getClass().getMethod(methodName, (Class<?>[]) params);
methodCache.put(methodName, method);
}
return method.invoke(obj, params);
}
```
* **尽量减少反射操作的次数,避免频繁调用。**
* **在性能敏感部分避免使用反射。**
* **通过动态代理代替反射操作。**
##### 4.1 配置文件的自动加载(如 Spring 框架中的 Bean 自动装配)
* 反射用于自动加载配置文件,并自动实例化和装配 Bean。例如,Spring 中的 Bean 自动装配就是通过反射机制来根据配置文件(如 XML 或注解)自动创建对象并注入依赖。
* Spring 会扫描项目中所有的类,然后根据配置的注解(如 `@Component`, `@Autowired`)或 XML 配置来自动创建和注入对象。反射在此过程中的应用如下:
* **扫描类路径** :Spring 会通过反射扫描项目中的类,查找带有 `@Component`, `@Service`, `@Repository` 等注解的类。
* **创建 Bean 实例**:通过反射创建这些标注的类的实例,并调用它们的构造方法。
* **自动装配依赖** :使用反射根据 `@Autowired` 注解,自动装配字段和方法。
##### 4.2 动态代理
* Spring AOP 使用动态代理来插入横切逻辑(如事务管理、日志记录等)