logo头像

黑客的本质就是白嫖

Java反射机制

简介

主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

一个类有多个组成部分,例如:成员变量、方法、构造方法等,反射就是加载类,并解剖出类的各个组成部分。

反射机制主要提供以下功能:

①在运行时判断任意一个对象所属的类;

②在运行时构造任意一个类的对象;

③在运行时判断任意一个类所具有的成员变量和方法;

④在运行时调用任意一个对象的方法;

⑤生成动态代理。

从上面的描述来看,个人的理解为,对象可以访问自己所属的类并对其进行修改,或实例化一个对象,但下面又说道,可以判断任意一个对象所属的类,这样的话,反射的目标应该不仅仅是自身,而是所有的类以及对象

我们知道类都要实例化为一个对象之后才能运行,那么反射机制的作用大概就是让一个运行中的对象可以调用或修改一个静态的类或者另一个动态的对象或者其本身

使用

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.dldxz.reflex;

import java.io.Serializable;

public class TestReflect implements Serializable {
private static final long serialVerionUID = -2862585049955236662L;

public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.dldxz.reflex.TestReflect");

//获取类名
System.out.println("clazz的类名为:"+clazz.getClass().getName());

//获取父类
Class<?> parentClass = clazz.getSuperclass();
System.out.println("clazz的父类为:"+parentClass.getName());

//获取所有接口
Class<?> intes[] = clazz.getInterfaces();
System.out.println("clazz实现的接口有:");
for (int i = 0; i < intes.length; i++){
System.out.println((i+1)+":"+intes[i].getName());
}

//通过反射机制实例化类
Class<?> class1 = null;
class1 = Class.forName("com.dldxz.reflex.User");
User user = (User) class1.newInstance();
user.setAge(10);
user.setName("dldxz");
System.out.println(user);
}
}

class User {
private int age;
private String name;
public User() {
super();
}
public User(String name) {
super();
this.name = name;
}
public User(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}

以上代码输出为

1
2
3
4
5
clazz的类名为:java.lang.Class
clazz的父类为:java.lang.Object
clazz实现的接口有:
1:java.io.Serializable
User [age=10, name=dldxz]

补充

补充几点在别处看到的知识

  1. getMethod 获取函数的方法
  2. invoke 执行函数的方法

上面两种方法的具体用法如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.dldxz.reflex;

import java.io.Serializable;

public class TestReflect implements Serializable {
private static final long serialVerionUID = -2862585049955236662L;

public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.dldxz.reflex.TestReflect");

System.out.println(clazz.getMethod("testFunc")+"\n");

clazz.getMethod("testFunc").invoke(clazz.newInstance());
}

public void testFunc() {
System.out.println("This is a test func\n");
}
}

其输出为

1
2
3
public void com.dldxz.reflex.TestReflect.testFunc()

This is a test func

除了上面两个方法之外,还有一点不知道能不能算是反射机制的,但因为是在同一篇文章上看到,而原作者又比我强了不知道多少,所以还是写下来为妙

剩下一个就是forName函数的两个重载之中的一个,其两个重载是

  • Class<?> forName(String name)
  • Class<?> forName(String name,**boolean** initialize,ClassLoader loader)

第一种就是常用的forName函数原型,和第二种的区别就是后面两个参数相当于固定,为forName(className,true,currentLoader)

第二个参数表示在实例化类时是否初始化,第三个参数意思是加载器,告诉Java虚拟机如何加载这个类。Java默认的加载器就是根据完整类名来加载类,即包含完整路径的类名

这里要讲的是第二个参数的问题,当把initialize设为true时,虽然类会进行初始化,但是并不是通过构造函数进行的初始化,而是通过某一个特殊的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TestClass.java

package com.dldxz.reflex;

public class TestClass {
{
System.out.println("{}方式初始化");
}
static {
System.out.println("static方式初始化");
}
public TestClass() {
System.out.println("构造函数初始化");
}
}

以上为一个测试类,当我们实例化他时会得到一下输出

1
2
3
static方式初始化
{}方式初始化
构造函数初始化

而当我们使用forName方法实例化他时

1
2
3
4
5
6
7
8
9
10
package com.dldxz.reflex;

import java.io.Serializable;

public class TestReflect implements Serializable {

public static void main(String[] args) throws Exception {
Class<?> class1 = Class.forName("com.dldxz.reflex.TestClass");
}
}

则会有以下输出

1
static方式初始化

可以看到当通过forName方法实例化类时,并不是通过构造函数初始化类,而是通过static代码块,这应该可以在某些情况下绕过使用


References

java反射机制深入理解剖析

Java反序列化学习之Apache Commons Collections

【代码审计】知识星球

评论系统未开启,无法评论!