反射是 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;
    }
}
  1. 获取 Class 对象:首先需要获取到目标类的 Class 对象(例如:User user = new User();,这里 userClass 对象就为 com.doicat.User);

  2. 获取成员信息:成员信息包括了属性、方法等,通过 Class 对象可以获取类对象的这类信息。

  3. 操作成员信息:通过反射 API 来对属性进行获取/修改,对方法进行调用等。

使用反射机制所涉及的方法

以上方的 User 类作为示例,接下来通过代码示例来实现各个步骤的操作

(Tips:下列的示例代码与User类处于同一个包当中)

通过已实例化对象获取 Class 对象 (java.lang.Class)

使用到的方法如下:

方法

描述

定义

class

根据类名获取Class对象类

getClass()

返回当前对象的Class对象类

Class<?> getClass()

forName()

传入全路径类名,返回 Class 对象类

Class<?> forName(String className)

getSystemClassLoader()

这个方法返回的是系统类加载器 ClassLoader 类型

ClassLoader getSystemClassLoader()

loadClass()

系统加载器的方法,传入全路径类名,返回 Class 对象类

Class<?> loadClass(String name)

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[] getFields()

getDeclaredFields()

返回所有成员变量对象的数组

Field[] getDeclaredFields()

getField()

传入对象属性变量名,返回公共单个成员变量对象

Field getField(String name)

getDeclaredField()

传入对象属性变量名,返回私有单个成员变量对象

Field getDeclaredField(String name)

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[] getMethods()

getDeclaredMethods()

返回所有成员方法对象的数组,不包括继承的方法

Method[] getDeclaredMethods()

getMethod()

返回单个公共成员方法对象

Method getMethod(String name, Class<?>... parameterTypes)

getDeclaredMethod()

返回单个成员方法对象

Method getDeclaredMethod(String name, Class<?>... parameterTypes)

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<?>[] getConstructors()

getDeclaredConstructors()

返回所有构造方法对象的数组

Constructor<?>[] getDeclaredConstructors()

getConstructor(Class<?>... parameterTypes)

返回单个公共构造方法对象

Constructor<T> getConstructor(Class<?>... parameterTypes)

getDeclaredConstructor(Class<?>... parameterTypes)

返回单个构造方法对象

Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

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()

传入成员属性对象和对象的值,为成员变量赋值

void set(Object obj, Object value)

get()

传入属性对象,获取该属性变量的值

Object get(Object obj)

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/setterinvoke() 可以理解成调用这个方法,newInstance() 就可以理解成调用构造方法。