java反射学习笔记

主要参考P神代码审计知识星球的《java安全漫谈》系列文章

动态获取信息以及动态调用对象方法的功能可称为反射

在java中用来表示运行时类型信息对应的类就是Class类,Class类位于java.lang包中;Class类创建后的对象就是Class对象

几个重要的方法

1.获取类对象的三种方法:

  • Obj.getClass():调用用类实例的getClass()方法

  • Test1.class:直接取类的class属性

  • Class.forName()方法

2.实例化类的方法:newInstance(),调用类的无参构造函数

3.获取函数的方法:

  • getMethod(),获取的是当前类中所有公共方法,包括从父类继承的方法;它通过反射获取一个类的某个特定的公有的方法,如果有方法重载,则需要传递给它需要获取的参数类型列表
  • getConstructor():同上;接收的参数是构造函数列表类型
  • getDeclearedMethod():获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
  • getDeclearedConstructor():同上

4.执行函数的方法:invoke(),如果这个方法是静态方法,那么第一个参数是类;如果是普通方法,那么第一个参数是类对象


获取类对象的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.test.hello;

public class Test1 {
public static void main(String[] args) throws Exception {
Test1 t1 = new Test1();
System.out.println(t1.getClass());
System.out.println(Test1.class);
System.out.println(Class.forName("com.test.hello.Test1")); //有package需要加上
}
}
/*
class com.test.hello.Test1
class com.test.hello.Test1
class com.test.hello.Test1
*/

使用反射调用方法的例子:

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
package com.test.hello;

public class Test1 {
public static void main(String[] args) throws Exception {
Class t = Class.forName("com.test.hello.Person"); //调用static
t.getMethod("say").invoke(t.newInstance());
}
}

public class Person{
static {
System.out.println("I'm static method");
}

{
System.out.println("I'm Init code block");
}

public Person(){
System.out.println("I'm Init method");
}

public void say(){
System.out.println("I'm say method");
}
}
/*
I'm static method
I'm Init code block
I'm Init method
I'm say method
*/

static {}是执行类初始化时调用的

forName的两个重载:

1
2
3
Class.forName(className)

Class.forName(className, true, ClassLoader)

第一种方式可以看做第二种方式的封装;其中,forName的第三个参数ClassLoader作用是告诉JVM如何加载这个类,第二个参数表示是否执行类初始化


反射Runtime类命令执行

命令执行常常用到Runtime类,Runtime类使用了单例模式,它的构造方法是私有的

1
2
3
4
5
6
7
8
9
10
public class Runtime {
private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
return currentRuntime;
}

private Runtime() {}
......
}

命令执行用到的exec方法有6个重载:

那么使用反射来调用Runtime的exec方法进行命令执行:

1
2
3
4
5
6
public class Test1 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("java.lang.Runtime");
c.getMethod("exec", String.class).invoke(c.getMethod("getRuntime").invoke(c), "curl 62.234.60.226:666");
}
}

拆开来看:

1
2
3
4
5
6
7
8
9
10
11
import java.lang.reflect.Method;

public class Test1 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("java.lang.Runtime");
Method get = c.getMethod("getRuntime");
Method exec = c.getMethod("exec", String.class);
Object runtime = get.invoke(c);
System.out.println(exec.invoke(runtime, "curl 62.234.60.226:666"));
}
}

利用getDeclaredConstructor

1
2
3
4
5
6
7
8
9
10
11
12
package com.test.hello;

import java.lang.reflect.Constructor;

public class Test1 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.Runtime");
Constructor c = clazz.getDeclaredConstructor();
c.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(c.newInstance(), "curl 62.234.60.226:666");
}
}

获取到一个私有方法后,必须用setAccessible修改其作用域,否则不能调用


反射ProcessBuilder类命令执行

如果类有非静态方法,且构造函数是有参的,可以使用getConstructor

直接使用ProcessBuilder类进行命令执行:

1
2
3
4
5
6
7
8
9
package com.test.hello;

import java.util.Arrays;

public class Test1 {
public static void main(String[] args) throws Exception {
new ProcessBuilder(Arrays.asList("curl", "62.234.60.226:666")).start();
}
}

ProcessBuilder类的构造方法为:

1
2
3
4
5
6
7
8
9
10
11
12
public ProcessBuilder(String... command) {
this.command = new ArrayList<>(command.length);
for (String arg : command)
this.command.add(arg);
}

public ProcessBuilder command(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
return this;
}

第一种构造方法参数为可变长参数,类型为String,我们可以将它看成数组;第二种构造方法只有一个参数,类型为List

1.针对第二种构造方法,使用getConstructor获取其构造函数然后调用start进行命令执行:

1
2
3
4
5
6
7
8
9
10
11
package com.test.hello;

import java.util.Arrays;
import java.util.List;

public class Test1 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("curl", "62.234.60.226:666"))).start();
}
}

这里用了java的强制类型转换,再改一下,完全利用反射来执行:

1
2
3
4
5
6
7
8
9
10
11
package com.test.hello;

import java.util.Arrays;
import java.util.List;

public class Test1 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("curl", "62.234.60.226:666")));
}
}

2.针对第一种构造方法,newInstance接收的是可变长参数,第一种构造方法的参数也是可变长的,两者叠加成为一个二维数组

1
2
3
4
5
6
7
8
package com.test.hello;

public class Test1 {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"curl", "62.234.60.226:666"}}));
}
}