Reflection(反射)被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理

这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射应用

创建一个person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.drop.test;

public class Person {
public String name;
private int age;

public Person() {
super();
}

public Person(String name) {
this.name = name;
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public void show(){
System.out.println("人类的测试");
}

public void display(String nation){
System.out.println("国籍是:"+nation);
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

没有用反射,如何创建一个类的对象,并调用其中的方法,属性。

1
2
3
4
5
6
7
8
9
@Test
public void test1(){
Person p = new Person();
p.setName("test1name");
p.setAge(10);
System.out.println(p);
p.show();
p.display("HK");
}

通过反射构建一个类的对象,并调用其中的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test2() throws Exception{
Class<Person> clazz = Person.class;
//1.创建clazz对应的运行时类Person类的对象
Person p = clazz.newInstance();
System.out.println(p);
//2.通过反射调用运行时类的指定的属性
Field f1 = clazz.getField("name");
f1.set(p,"test2name");
System.out.println(p);
//获取private封装的age
Field f2 = clazz.getDeclaredField("age");
f2.setAccessible(true);
f2.set(p,20);
System.out.println(p);
//3.通过反射调用运行时类的指定的方法(注意方法有重载):
Method m1 = clazz.getMethod("show");
m1.invoke(p);
Method m2 = clazz.getMethod("display", String.class); //参数类型
m2.invoke(p,"CH");
System.out.println(p);

class类

在Object类中定义了以下方法,其将被所有子类继承:

1
public final Class getClass()

以上方法的返回值的类型是一个Class类,是java反射的源头。

正常方式:引入需要的包名称–>通过new实例化 –>取得实例化对象

反射方式:实例化对象–> getClass()方法–>得到完整的”包类”名称

创建一个类 –编译–>对应.class–jave.exe加载(JVM类加载器)–>此.class文件就是一个运行时类,存在缓存区,这个运行时类本身就是一个Class的实例。

1
2
3
4
5
6
@Test
public void test3(){
Person p = new Person();
Class clazz = p.getClass(); //通过运行时类的对象,调用其getClass(),返回其运行时类。
System.out.println(clazz);
}

1.每个运行时类只加载一次

2.有了class实例后,可做如下操作:

  1. 创建对应的运行时类的对象
  2. 获取对应的运行时类的完整结构(属性、方法、构造器、内部类、父类、所在的包、异常、注释…)
  3. 调用对应的运行时类的指定的结构(属性、方法、构造器)
  4. 反射的应用:动态代理

获取运行时类的对象四种方法

调用运行时类本身的.class属性

1
Class<?> name1 = Runtime.class;

要明确用到类中的静态成员

通过运行时类的对象调用 getClass()

1
2
Runtime rt = Runtime.getRuntime();
Class<?> name = rt.getClass();

通过Object类中的getClass()方法获取字节码对象,必须明确具体的类,然后创建对象。

通过Class的静态方法获取 forName() (只需要类名)

1
Class<?> name = Class.forName("java.lang.Runtime");

只需要有类名称即可,forName()的静态方法JVM会装载类,并且执行static()中代码。

通过类的加载器

1
Class<?> name = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");

类似forName(),但不同在于getSystemClassLoader().loadClass()不执行static()中代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test4() throws ClassNotFoundException {
//1.调用运行时类本身的.class属性
Class clazz1 = Person.class; //自己创建的类
System.out.println(clazz1.getName());
Class clazz2 = String.class; //本身自带的
System.out.println(clazz2.getName());
//2.通过运行时类的对象调用
Person p = new Person();
Class clazz3 = p.getClass();
System.out.println(clazz3.getName());
//3.通过Class的静态方法获取
String className = "com.drop.test.Person";
Class clazz4 = Class.forName(className);
System.out.println(clazz4.getName());
//4.通过类的加载器
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz5 = classLoader.loadClass(className);
System.out.println(clazz5.getName());
}

获取构造方法

在Java的任何一个类都必须有一个或多个构造方法,如果代码中没有创建构造方法那么在类编译的时候会自动创建一个无参数的构造方法。

getDeclaredConstructor()

1
Constructor constructor = name.getDeclaredConstructor();

没有访问构造方法权限时调用constructor.setAccessible(true);修改访问权限就可以成功的创建出类实例。

getConstructor()

1
Constructor constructor = name.getConstructor();

无法获取到私有方法

构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组

如:Car类

image-20211231005233544

Constructor constructor = name.getDeclaredConstructor(String.class, int.class);

或者如下代码:

1
2
Class[] classes = new Class[] { String.class, int.class };
Constructor constructor = name.getDeclaredConstructor(classes);

获取类的所有构造方法可以使用:name.getDeclaredConstructors来获取一个Constructor数组

image-20211231005050728

后续创建类实例,同理如果有参数的情况下应该传入对应的参数值,如:constructor.newInstance("admin", "123456")

获取Class类的实例

调用Class对象的newInstance()方法:

1
Object runtimeInstance = constructor.newInstance();
1
2
3
4
5
6
7
8
9
@Test
public void test10() throws Exception {
String className = "com.drop.test.Person";
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz = classLoader.loadClass(className);
Object obj = clazz.newInstance();
Person p = (Person) obj;
System.out.println(p);
}

获取当前类指定的成员方法

getDeclaredMethods()

1
2
3
4
5
6
7
8
@Test
public void test8() throws ClassNotFoundException {
Class<?> name = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
Method[] declaredMethods = name.getDeclaredMethods();
for(Method m:declaredMethods){
System.out.println(m);
}
}

能获取到当前类的所有成员方法(不包含父类)。

getMethods()

1
2
3
4
5
6
7
8
@Test
public void test8() throws ClassNotFoundException {
Class<?> name = Class.forName("java.lang.Runtime");
Method[] methods = name.getMethods();
for(Method m:methods){
System.out.println(m);
}
}

只能获取到当前类和父类的所有有权限的方法(如:public)

getMethod()

1
2
3
4
5
6
@Test
public void test8() throws NoSuchMethodException {
Class<?> name = Runtime.class;
Method method = name.getMethod("exec", String.class);
System.out.println(method);
}

只能返回一个特定的方法,第一参数为方法名称,后面的参数为方法的参数对应Class的对象。

getDeclaredMethod()

1
2
3
4
5
6
7
@Test
public void test8() throws NoSuchMethodException {
Runtime rt = Runtime.getRuntime();
Class<?> name = rt.getClass();
Method method = name.getDeclaredMethod("exec", String.class);
System.out.println(method);
}

只能返回一个特定的方法,第一参数为方法名称,第二个参数名是方法参数

反射调用方法invoke

  1. invoke调用普通方法时,传入的必须是实例化后的类。
  2. invoke调用静态方法时,传入类即可。

获取到java.lang.reflect.Method对象以后我们可以通过Methodinvoke方法来调用类方法。

1
method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);

method.invoke的第一个参数必须是类实例对象,如果调用的是static方法那么第一个参数值可以传null,因为在java中调用静态方法是不需要有类实例的,因为可以直接类名.方法名(参数)的方式调用。

1
2
3
4
Class<?> cmdClass = curl.loadClass("com.drop.test.CMD");
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null,cmd);
//同下
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(cmdClass,cmd);

method.invoke的第二个参数不是必须的,如果当前调用的方法没有参数,那么第二个参数可以不传,如果有参数那么就必须严格的依次传入对应的参数类型

调用类成员变量

Java反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值。

getDeclaredFields

1
Field[] fields = clazz.getDeclaredFields();

获取当前类的所有成员变量

1
Field field  = clazz.getDeclaredField("变量名");

获取当前类指定的成员变量:

getFields

用法同上,区别同获取当前类指定的成员方法。

获取Runtime类Class对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void test() throws Exception {
//不使用反射执行本地命令代码片段 导入import org.apache.commons.io.IOUtils;
System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));
//使用反射
//获取Runtime类对象
String className = "java.lang.Runtime";
Class runtimeClass1 = Class.forName(className);
// 获取构造
Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);
// 创建Runtime类实例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();
// 获取Runtime的exec(String cmd)方法
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
// 调用exec方法,等价于 rt.exec(cmd);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, "whoami");
// 获取命令执行结果
InputStream in = process.getInputStream();
// 输出命令执行结果
System.out.println(IOUtils.toString(in, "UTF-8"));
}

java.lang.Runtime因为有一个exec方法可以执行本地命令