反射
Java反射是指在运行时检查、获取和操作类、接口、字段和方法等信息的能力。通过反射,可以在运行时动态地创建对象、调用方法、获取和设置字段的值,而不需要在编译时确定这些操作。
在Java中,类型的检查通常发生在编译阶段,所有对象的类型必须在代码编写时就已经确定。然而,反射机制提供了一种绕过这种限制的方法,允许程序在运行时根据需要加载和操作类。通过反射,程序可以动态地获取类的信息,并进行相关操作,而这些类在编译时可能并未被引用,因此不会被预加载到JVM中。
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect
包中
- Class类:代表一个类
- Constructor 类:代表类的构造方法
- Field 类:代表类的成员变量(属性)
- Method类:代表类的成员方法
反射在许多框架和库中得到了广泛的应用,比如Spring框架、JUnit测试框架等,它们利用反射实现了很多动态化的功能。
Class类
Class
类是Java反射机制的核心。它代表了一个类或接口在运行时的状态。
对于每个类,Java虚拟机(JVM)都会为其创建一个Class
实例,包含了该类的所有信息,如属性、方法、构造函数、父类、接口等。
Class
对象是在类加载时由JVM使用类加载器创建的。
获取Class
对象
使用
Class.forName("类的全限定名")
方法:这是最常用的方法。javaClass clazz = Class.forName("com.example.MyClass");
使用类的
.class
语法:适用于已知具体类的情况。javaClass clazz = MyClass.class;
使用对象的
getClass()
方法:适用于已经有一个对象实例的情况。javaMyClass obj = new MyClass(); Class clazz = obj.getClass();
tips:如果你只是希望一个类的静态代码块执行,其它代码一律不执行,可以使用:
Class.forName("完整类名");
这个方法的执行会导致类加载,类加载时,静态代码块执行。具体知识详见JVM专题。
使用反射创建对象
首先需要获取类的Class
对象。
根据Class
对象创建对象的方法有两种:
- 使用
Class
对象的newInstance()
方法(已过时)
Class clazz = MyClass.class;
MyClass obj = (MyClass) clazz.newInstance();
这种方式在Java 9及之后已经被标记为过时,不推荐使用。
- 使用
Constructor
类的newInstance
()方法:
Class clazz = MyClass.class;
Constructor<MyClass> constructor = clazz.getConstructor();
MyClass obj = constructor.newInstance();
获取带参数的构造函数(Constructor
)对象,创建对象实例并传入参数
Class clazz = MyClass.class;
Constructor<MyClass> constructor = clazz.getConstructor(String.class, int.class);
MyClass obj = constructor.newInstance("example", 123);
使用反射操作属性
获取类的Class对象:
javaClass clazz = MyClass.class;
获取目标属性(Field)对象:
javaField field = clazz.getDeclaredField("fieldName"); // 设置访问权限,如果属性是私有的需要设置为可访问 field.setAccessible(true);
读取属性的值:
javaObject value = field.get(objectInstance); // objectInstance为包含该属性的实例
设置属性的值:
javafield.set(objectInstance, value); // objectInstance为包含该属性的实例,value为要设置的值
需要注意的是,当使用反射机制获取或设置属性值时,如果属性是私有的,则默认情况下无法直接访问,会抛出IllegalAccessException
异常。
为了解决这个问题,可以通过调用Field
类的setAccessible(true)
方法来设置访问权限为可访问,这样就可以绕过访问权限检查,访问私有属性了。虽然通过设置访问权限为可访问可以绕过访问权限限制,但是这样做会破坏封装性。
使用反射执行方法
获取类的Class对象:
javaClass clazz = MyClass.class;
获取目标方法(Method)对象:
javaMethod method = clazz.getDeclaredMethod("methodName", parameterTypes); // 如果方法是私有的,需要设置为可访问 method.setAccessible(true);
调用方法:
对于静态方法:
javamethod.invoke(null, args);
对于实例方法:
javaObject objInstance = clazz.newInstance(); // 创建类的实例 method.invoke(objInstance, args);
其中,args为方法的参数。如果方法有返回值,invoke()方法会返回相应的返回值。
需要注意的是,对于私有方法,同样需要设置为可访问以避免访问权限限制导致的异常。
反射的应用
Java反射机制的应用非常广泛,它在框架设计、动态代理、注解处理、对象序列化、数据库ORM映射、远程方法调用等方面都有重要的应用。下面详细介绍一些反射的应用场景:
框架设计
许多流行的Java框架,如Spring、Hibernate、MyBatis等,都大量使用了反射机制。例如,Spring的依赖注入(DI)和面向切面编程(AOP)特性就是通过反射来实现的。框架可以通过反射动态地创建对象、调用方法、设置字段值,从而实现高度的可配置性和灵活性。
动态代理
Java提供了Proxy
类和InvocationHandler
接口,允许程序在运行时动态地创建代理类和代理对象。代理对象可以拦截并处理原始对象的 method 调用。反射在这里用于动态创建代理类和实现方法调用。
实现步骤
1. 定义InvocationHandler
接口的实现
首先,你需要创建一个实现了InvocationHandler
接口的类。这个接口只有一个方法invoke
,它负责处理所有代理方法的调用。
查看代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private final Object target; // 被代理的对象
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标方法之前可以执行一些操作
System.out.println("Before method " + method.getName());
// 使用反射调用目标对象的方法
Object result = method.invoke(target, args);
// 在调用目标方法之后可以执行一些操作
System.out.println("After method " + method.getName());
return result;
}
}
2. 使用Proxy
类创建代理对象
接下来,使用Proxy
类的newProxyInstance
方法来创建代理对象。这个方法需要三个参数:
ClassLoader
:用于加载代理类的类加载器。interfaces
:代理类要实现的接口列表。InvocationHandler
:处理代理方法的调用处理器。
查看代码
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
// 创建被代理的对象
RealObject realObject = new RealObject();
// 创建InvocationHandler实例
MyInvocationHandler handler = new MyInvocationHandler(realObject);
// 使用Proxy创建代理对象
SomeInterface proxyInstance = (SomeInterface) Proxy.newProxyInstance(
RealObject.class.getClassLoader(),
RealObject.class.getInterfaces(),
handler
);
// 使用代理对象调用方法
proxyInstance.doSomething();
}
}
// 假设有一个接口和它的实现类
interface SomeInterface {
void doSomething();
}
class RealObject implements SomeInterface {
public void doSomething() {
System.out.println("RealObject doing something.");
}
}
反射在动态代理中的作用
在上述过程中,反射在以下两个方面发挥作用:
- 创建代理对象:
Proxy.newProxyInstance
方法内部使用反射来动态创建一个实现了指定接口的代理类。这个代理类是运行时生成的,它的方法实现会委托给InvocationHandler
的invoke
方法。 - 方法调用:在
InvocationHandler
的invoke
方法中,使用反射API(如Method.invoke
)来调用被代理对象的方法。
注解处理
注解(Annotation)是Java 5引入的一个特性,它提供了一种为代码添加元数据的方法。反射可以用来读取注解信息,并根据这些信息动态地执行操作。例如,JUnit测试框架就使用反射来读取测试类和方法上的注解,以确定如何运行测试。
对象序列化
对象序列化是将对象状态转换为可存储或可传输形式的过程。Java的序列化机制中,反射用于在序列化和反序列化过程中检查对象的类型和属性。
数据库ORM映射
对象关系映射(Object-Relational Mapping,ORM)是一种将关系数据库中的数据映射到对象的技术。Hibernate等ORM框架使用反射来动态地将数据库记录映射到对象上,以及将对象状态同步回数据库。
调试器和可视化工具
调试器和可视化工具通常需要能够查看和操作运行中的程序的状态。反射机制使得这些工具能够获取类的详细信息,查看和修改变量和调用方法。