反射是 Java 的一个强大特征,它能在程序运行中查询、访问、和修改类、接口等信息,在安全角度上,简而言之就是它能够获取已经实例化的类对象,并能对这个类对象中的属性、方法进行操作,我们需要通过这个机制来修改已加载类对象的一些信息。
反射机制相关的类
Java 的反射 API 提供了一系列的类和方法来操作类对象,主要的类有(注意这里是类!,不熟悉类相关概念的师傅可以去了解一下面向对象编程):
java.lang.Class
:表示类的类,包含了获取类属性、方法等方法;java.lang.reflect.Field
:表示属性的类,获取到属性后,能够访问获取的属性;java.lang.reflect.Method
:表示方法的类,获取到方法后可以实现获取方法的功能;java.lang.reflect.Constructor
:表示构造函数的类,获取到构造函数后可以实现构造的功能。
如何使用反射机制?
这里以下方的 User
类作为示例
package com.doicat;
public class User {
public String Name;
private Integer Age;
protected Boolean Sex;
public User(){}
public User(String name){
this.Name = name;
}
private User(Integer age){
this.Age = age;
}
protected User(Boolean sex){
this.Sex = sex;
}
public String getName() {
return Name;
}
private Integer getAge(){
return this.Age;
}
protected Boolean getSex(){
return this.Sex;
}
}
获取 Class 对象:首先需要获取到目标类的
Class
对象(例如:User user = new User();
,这里user
的Class
对象就为com.doicat.User
);获取成员信息:成员信息包括了属性、方法等,通过
Class
对象可以获取类对象的这类信息。操作成员信息:通过反射 API 来对属性进行获取/修改,对方法进行调用等。
使用反射机制所涉及的方法
以上方的 User 类作为示例,接下来通过代码示例来实现各个步骤的操作
(Tips:下列的示例代码与User类处于同一个包当中)
通过已实例化对象获取 Class
对象 (java.lang.Class)
使用到的方法如下:
class - Class<?>
直接通过类字面 User
量获取 Class
类
Class<?> clazz = User.class;
System.out.println(clazz);
// 输出结果为: class com.doicat.User
getClass() - Class<?>
通过已实例化的 com.doicat.User
对象获取 Class
类
User user = new User();
Class<?> clazz = user.getClass();
System.out.println(clazz);
// 输出结果为: class com.doicat.User
forName() - Class<?>
通过 com.doicat.User
类的全路径获取 Class
类
Class<?> clazz = Class.forName("com.doicat.User");
System.out.println(clazz);
// 输出结果为: class com.doicat.User
\
通过 Class
类获取成员信息
下列介绍的方法均为 Class
类的方法
获取成员属性对象 (java.lang.reflect.Field)
可用方法如下
getFields() - Field[]
返回 Class
类对象 com.doicat.User
中所有公共成员属性对象数组
Class<?> clazz = User.class;
Field[] field = clazz.getFields();
System.out.println(Arrays.toString(field));
// 输出结果为:[public java.lang.String com.doicat.User.Name]
getDeclaredFields() - Field[]
返回 Class
类对象 com.doicat.User
中所有成员属性对象数组
Class<?> clazz = User.class;
Field[] field = clazz.getDeclaredFields();
System.out.println(Arrays.toString(field));
// 输出结果为: [public java.lang.String com.doicat.User.Name, private java.lang.Integer com.doicat.User.Age, protected java.lang.Boolean com.doicat.User.Sex]
getField() - Field
传入 Class
类对象 com.doicat.User
中公共成员属性名称,返回该公共成员属性对象
Class<?> clazz = User.class;
Field field = clazz.getField("Name");
System.out.println(field);
// 输出结果为: public java.lang.String com.doicat.User.Name
// 若传入的为私有、被保护的属性则会发生错误
getDeclaredField() - Field
传入 Class
类对象 com.doicat.User
中成员属性名称,返回该成员属性对象
Class<?> clazz = User.class;
Field field1 = clazz.getDeclaredField("Age");
Field field2 = clazz.getDeclaredField("Sex");
System.out.println(field1);
System.out.println(field2);
// 输出结果为: private java.lang.Integer com.doicat.User.Age
// protected java.lang.Boolean com.doicat.User.Sex
获取成员方法对象 (java.lang.reflect.Method)
可用方法如下
getMethods() - Method[]
返回 Class
类对象 com.doicat.User
中所有公共方法对象数组,包含继承的方法
Class<?> clazz = User.class;
Method[] method = clazz.getMethods();
System.out.println(Arrays.toString(method));
// 输出结果为: [public java.lang.String com.doicat.User.getName(), public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
getDeclaredMethods() - Method[]
返回 Class
类对象 com.doicat.User
中所有方法对象数组,不包含继承的方法
Class<?> clazz = User.class;
Method[] method = clazz.getDeclaredMethods();
System.out.println(Arrays.toString(method));
// 输出结果为: [public java.lang.String com.doicat.User.getName(), protected java.lang.Boolean com.doicat.User.getSex(), private java.lang.Integer com.doicat.User.getAge()]
getMethod() - Method
传入 Class
类对象 com.doicat.User
中公共方法名称及其所需参数,返回该公共方法对象
Class<?> clazz = User.class;
Method method = clazz.getMethod("getName");
System.out.println(method);
// 输出结果为: public java.lang.String com.doicat.User.getName()
// 若传入的为私有、被保护的方法则会发生错误
getDeclaredMethod() - Method
传入 Class
类对象 com.doicat.User
中方法名称及其所需参数,返回该方法对象
Class<?> clazz = User.class;
Method method1 = clazz.getDeclaredMethod("getAge");
Method method2 = clazz.getDeclaredMethod("getSex");
System.out.println(method1);
System.out.println(method2);
// 输出结果为: private java.lang.Integer com.doicat.User.getAge()
// protected java.lang.Boolean com.doicat.User.getSex()
获取构造方法对象 (java.lang.reflect.Constructor)
可用方法如下
getConstructors() - Constructor<?>[]
返回 Class
类对象 com.doicat.User
中所有公共构造方法对象数组
Class<?> clazz = User.class;
Constructor<?>[] constructor = clazz.getConstructors();
System.out.println(Arrays.toString(constructor));
// 输出结果为: [public com.doicat.User(java.lang.String), public com.doicat.User()]
getDeclaredConstructors() - Constructor<?>[]
返回 Class
类对象 com.doicat.User
中所有构造方法对象数组
Class<?> clazz = User.class;
Constructor<?>[] constructor = clazz.getDeclaredConstructors();
System.out.println(Arrays.toString(constructor));
// 输出结果为: [protected com.doicat.User(java.lang.Boolean), private com.doicat.User(java.lang.Integer), public com.doicat.User(java.lang.String), public com.doicat.User()]
getConstructor() - Constructor<T>
传入 Class
类对象 com.doicat.User
中公共构造方法所需参数,返回该公共构造方法对象
Class<?> clazz = User.class;
Constructor<?> constructor = clazz.getConstructor();
System.out.println(constructor);
// 输出结果为: public com.doicat.User()
// 若传入的为私有、被保护构造方法的参数则会发生错误
getDeclaredConstructor() - Constructor<T>
传入 Class
类对象 com.doicat.User
中构造方法所需参数,返回该构造方法对象
Class<?> clazz = User.class;
Constructor<?> constructor1 = clazz.getDeclaredConstructor(Integer.class);
Constructor<?> constructor2 = clazz.getDeclaredConstructor(Boolean.class);
System.out.println(constructor1);
System.out.println(constructor2);
// 输出结果为: private com.doicat.User(java.lang.Integer)
// protected com.doicat.User(java.lang.Boolean)
操作获取到的成员信息对象
成员属性 (java.lang.reflect.Field)
通过调用 java.lang.reflect.Field
类的 set()/get()
方法
set() - 传入一个具有该属性的对象和属性值,修改传入对象中的属性值
对于私有/被保护属性需要通过 Field.setAccessible(true)
允许访问属性。
// 获取 User 类中的 `Name` 属性对象
Class<?> clazz = User.class;
Field field = clazz.getDeclaredField("Name");
// 实例化一个 User 类对象
User user = new User();
// 将 user 中的 `Name` 属性值设置为 `Doicat`
field.set(user, "Doicat");
System.out.println(user.getName());
// 输出结果为: Doicat
get() - 传入一个具有该属性的对象,获取传入对象中的属性值
// 获取 User 类中的 `Name` 属性对象
Class<?> clazz = User.class;
Field field = clazz.getDeclaredField("Name");
// 实例化一个 User 类对象
User user = new User("Doicat");
// 获取 user 中的 `Name` 属性值
Object name = field.get(user);
System.out.println(name.toString());
// 输出结果为: Doicat
成员方法 (java.lang.reflect.Method)
通过调用 java.lang.reflect.Method
类的方法 invoke()
,实现方法
invoke() - 传入对象和该方法对象的方法所需参数
此处传入对象是当实现的方法需要访问对象属性时,就会访问传入对象的属性。
// 获取 User 类中的 getName() 方法对象
Class<?> clazz = User.class;
Method getName = clazz.getDeclaredMethod("getName");
// 实例化一个 User 类对象
User user = new User("Doicat");
// 实现 User 类中的 getName() 方法
Object result = getName.invoke(user);
System.out.println(result.toString());
// 输出结果为: Doicat
构造方法 (java.lang.reflect.Constructor)
通过调用 java.lang.reflect.Constructor
类的方法 newInstance()
,实现方法
newInstance() - 通过构造方法对象的方法进行实例化
// 获取 User 类中的 `User(String name)` 构造方法对象
Class<?> clazz = User.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
// 通过获取到的构造方法对象实例化一个 User 类对象
Object obj = constructor.newInstance("Doicat");
System.out.println(obj);
// 输出结果为: com.doicat.User@7d4991ad
使用反射机制实现 Runtime.getRuntime().exec("calc");
结束了上面的介绍,这里举一个简单的案例,通过反射来实现简化的代码 Runtime.getRuntime().exec("calc");
。
我们这里最终是需要调用到 java.lang.Runtime
类中的 exec()
方法,我们从 java.lang.Runtime
类开始,首先获取 Class
类,代码如下:
Class<?> runtime = Class.forName("java.lang.Runtime");
然后是获取这个类当中的 exec()
方法,代码如下
Method exec = runtime.getDeclaredMethod("exec", String[].class);
最后实例化一个对象,然后调用这个方法,实例化对象分为两种,一种是通过构造方法,另一种是通过类当中的 getRuntime()
方法来获取一个类对象,两种方式如下:
// 通过实例化对象
Constructor<?> constructor = runtime.getDeclaredConstructor();
constructor.setAccessible(true); // 私有方法需设置允许访问
Object obj1 = constructor.newInstance();
// 通过类成员方法 `getRuntime()`
Method method = runtime.getDeclaredMethod("getRuntime");
Object obj2 = method.invoke(""); // 这里invoke无需传入对象是因为 getRuntime() 方法中不访问 Runtime 类对象的属性
实现 exec()
方法如下:
exec.invoke(obj1,"calc");
exec.invoke(obj2,"calc");
完整代码如下:
package com.doicat;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
Class<?> runtime = Class.forName("java.lang.Runtime");
Method exec = runtime.getDeclaredMethod("exec", String.class);
Constructor<?> constructor = runtime.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj1 = constructor.newInstance();
Method method = runtime.getDeclaredMethod("getRuntime");
Object obj2 = method.invoke("");
exec.invoke(obj1,"calc");
exec.invoke(obj2,"calc");
}
}
个人理解
介绍完这个机制,从我个人理解,反射机制做的是获取到属性/方法等,然后传入这个属性/方法所属的类对象,就可以实现这个方法的功能/属性的访问,这个机制是能够在不加载这个类对象的类的情况下,来调用这个类对象当中的方法/访问这个类对象的属性;
换个方式来理解这个实现的结果,获取方法的过程就像是在写这个类的时候,将相关的方法写出来,获取属性的过程就是在定义这个类当中的属性,反射中的 set()/get()
就像是类当中的 getter/setter
,invoke()
可以理解成调用这个方法,newInstance()
就可以理解成调用构造方法。