一、何为反射?
如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
反射是 Java 语言中的一种强大机制,它允许程序在运行时动态地获取类的各种信息(如类的属性、方法、构造函数等),并且能够创建对象、调用方法和访问属性。这种机制使得 Java 程序可以在运行时自我检查和操作,而不是在编译阶段就完全确定。
例如,在常规情况下,我们编写代码时是明确知道要调用哪个类的哪个方法,就像Object obj = new Object(); obj.toString();
这样。但是通过反射,我们可以在运行时才决定要操作哪个类,比如根据用户输入或者配置文件来决定加载哪个类并且调用其中的方法。
二、反射相关的类
1. Class
类
Class
类是 Java 反射机制的核心。在 Java 中,每个类在内存中都有一个对应的Class
对象,这个对象包含了该类的所有信息。可以通过多种方式获取Class
对象:
-
通过类的
class
属性:Class<?> clazz = String.class;
(这里以String
类为例)。 -
通过对象的
getClass()
方法:Object obj = new String(); Class<?> clazz = obj.getClass();
。 -
通过
Class.forName()
方法:Class<?> clazz = Class.forName("java.lang.String");
,这种方式在根据类的全限定名动态加载类时非常有用,比如在加载数据库驱动时,会根据配置文件中的驱动类名动态加载对应的驱动类。
2. Constructor
类
用于表示类的构造函数。通过Class
对象可以获取Constructor
对象,从而可以使用反射来创建类的对象。例如:
Class<?> clazz = MyClass.class;
Constructor<?> constructor = clazz.getConstructor(String.class);
Object obj = constructor.newInstance("参数值");
3. Method
类
代表类的方法。可以通过Class
对象获取Method
对象,然后使用反射来调用方法。例如:
Class<?> clazz = MyClass.class;
Object obj = clazz.newInstance();
Method method = clazz.getMethod("myMethod", String.class);
method.invoke(obj, "参数值");
这里假设MyClass
有一个接受String
类型参数的构造函数,通过getConstructor
方法获取这个构造函数对应的Constructor
对象,然后使用newInstance
方法创建MyClass
的一个对象,并传入相应的参数。
4、Field
类
用于表示类的成员变量。可以通过Class
对象获取Field
对象,进而访问和修改类的成员变量。例如:
Class<?> clazz = MyClass.class;
Object obj = clazz.newInstance();
Field field = clazz.getField("myField");
field.set(obj, "新的值");
Object value = field.get(obj);
假设MyClass
有一个名为myField
的成员变量,首先创建MyClass
的一个对象,然后通过getField
获取myField
对应的Field
对象,使用set
方法修改obj
对象中myField
的值,再通过get
方法获取myField
的值。
三、反射的应用场景了解么?
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
以 Spring 框架为例,它通过配置文件或者注解来定义 Bean(对象)的创建和依赖关系。在运行时,Spring 利用反射来读取这些配置信息,动态地创建对象、注入依赖,并管理对象的生命周期。例如,当 Spring 容器启动时,它会根据配置文件中的<bean>
标签或者@Component
等注解,通过反射加载对应的类,调用构造函数创建对象,并根据@Autowired
等注解通过反射来注入其他相关对象。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method
来调用指定的方法。
public class DebugInvocationHandler implements InvocationHandler {/*** 代理类中的真实对象*/private final Object target;public DebugInvocationHandler(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {System.out.println("before method " + method.getName());Object result = method.invoke(target, args);System.out.println("after method " + method.getName());return result;}
}
另外,像 Java 中的一大利器 注解 的实现也用到了反射。
为什么你使用 Spring 的时候 ,一个@Component
注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value
注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
四、谈谈反射机制的优缺点
优点:
-
高度灵活性:能够在运行时动态地操作类,使得代码更加灵活,可以根据不同的情况加载和使用不同的类和方法,适应复杂多变的业务需求。例如在插件系统中,可以随时添加新的插件类并在运行时加载使用,而不需要修改主程序的代码。
-
框架构建利器:是构建 Java 框架的重要工具,许多框架的核心功能(如对象的创建、依赖注入、AOP 等)都依赖于反射机制。它使得框架能够以一种通用的方式处理各种不同的类和对象,提高了框架的通用性和扩展性。
缺点:
-
性能开销:反射操作相比于直接的代码调用(如普通的方法调用、对象创建等)会有一定的性能损失。因为反射涉及到动态解析类的信息、查找方法和字段等操作,这些操作在运行时需要更多的时间和资源。例如,频繁地使用反射来调用方法可能会导致程序的执行效率降低。
-
安全风险:反射机制可以绕过 Java 语言的一些访问限制,如访问私有成员变量和方法。如果使用不当,可能会导致安全漏洞。例如,一个恶意的程序可以通过反射访问和修改其他类的私有成员,从而破坏数据的完整性和安全性。
五、反射实战
1、获取 Class 对象的四种方式
如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:
1. 知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
2. 通过 Class.forName()
传入类的全路径获取:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
3. 通过对象实例instance.getClass()
获取:
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();
4. 通过类加载器xxxClassLoader.loadClass()
传入类路径获取:
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
2、反射的一些基本操作
创建一个我们要使用反射操作的类 TargetObject
。
public class TargetObject {private String value;public TargetObject() {value = "JavaGuide";}public void publicMethod(String s) {System.out.println("I love " + s);}private void privateMethod() {System.out.println("value is " + value);}
}
使用反射操作这个类的方法以及参数
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {/*** 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例*/Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");TargetObject targetObject = (TargetObject) targetClass.newInstance();/*** 获取 TargetObject 类中定义的所有方法*/Method[] methods = targetClass.getDeclaredMethods();for (Method method : methods) {System.out.println(method.getName());}/*** 获取指定方法并调用*/Method publicMethod = targetClass.getDeclaredMethod("publicMethod",String.class);publicMethod.invoke(targetObject, "JavaGuide");/*** 获取指定参数并对参数进行修改*/Field field = targetClass.getDeclaredField("value");//为了对类中的参数进行修改我们取消安全检查field.setAccessible(true);field.set(targetObject, "JavaGuide");/*** 调用 private 方法*/Method privateMethod = targetClass.getDeclaredMethod("privateMethod");//为了调用private方法我们取消安全检查privateMethod.setAccessible(true);privateMethod.invoke(targetObject);}
}
输出内容:
publicMethod
privateMethod
I love JavaGuide
value is JavaGuide
注意 : 有读者提到上面代码运行会抛出 ClassNotFoundException
异常,具体原因是你没有下面把这段代码的包名替换成自己创建的 TargetObject
所在的包 。