本节的代码链接:reflection
1. 反射的由来
反射机制允许程序执行期借助于Reflection API取得任何类的内部信息,如成员变量、构造器、成员方法等,并能操作对象的属性及方法,在设计模式和框架底层都会用到。
1.1 引入需求
编写框架的时候有一个常见的需求:通过外部配置文件,在不修改源码的情况下,来扩展功能。
有这么一个类:
public class Obj {
private String name = "mimi";
public Obj() {
}
public Obj(String name) {
this.name = name;
}
public void sayHi() {
System.out.println(this.name + " say: hi!");
}
}
试着思考:在了解反射之前,你是否可以使用现有技术,通过一个指定了某个类的全限定类名和方法的.properties
配置文件,实例出对象并且调用其中的方法呢?
1.2 尝试通过I/O解决
我们知道,通过全限定类名确实可以实例化一个对象
Obj obj = new com.chenshu.Obj();
那我就先通过I/O把Obj类的全限定类名和方法拿出来呗,使用Properties类,可以轻松的读写文件:
public class Main {
public static void main(String[] args) throws IOException {
//1.读写文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/static/re.properties"));
String classPathName = properties.getProperty("classPath");
String methodName = properties.getProperty("method");
System.out.println("全限定类名:" + classPathName);
System.out.println("方法名:" + methodName);
}
}
拿到全限定类名和方法名后,我们开始犯难了,拿到了Obj类的全限定类名后,也不可能通过这个 String 创建对象啊,这时就要用到反射机制了。
2. 反射机制快速入门
2.1 加载类
通过一个类名为Class的类来获取到Obj类,可以把下面的clazz对象看成Obj类的图纸:
Class clazz = Class.forName(classPathName);
2.2 获得实例
通过clazz得到 com.chenshu.Obj 的对象实例:
Object o = clazz.newInstance();
2.3 获得方法
在 java.lang.reflect
包中有一个 Method 类可以从 clazz 中接收方法,也就是把方法视为一个对象,并通过方法.invoke(对象)
来实现调用:
Method method = clazz.getMethod(methodName);
method.invoke(o);
3. 反射原理
加载完类之后,在堆中就产生了一个Class
类型的对象 (一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一个镜子,透过这个镜子看到类的结构,所以,形象的称之为反射。
为什么能够通过这样一个Class对象来进行创建实例,操作属性,操作方法呢?
以前面提到的Obj类为例子:
Obj.java这样一个java代码,会在编译时期被转换为Obj.class的字节码文件
当Java程序在“运行阶段”首次使用某个类时,这个类的字节码会被加载到JVM中。这个阶段称为“加载阶段”。加载不仅仅是将类的字节码读入内存,还会创建一个代表该类的Class对象。这个Class对象可以看作是一个可以操作的数据结构。
直到类加载完了才生成了这么一个对象
由于Obj知道自身是属于哪一个对象,因此能够getClass()
方法找到它的Class对象,并且能通过Class对象提供的一系列API来修改这个数据结构,进而进行这些操作:创建对象、调用方法、操作属性等。
4. 反射的主要类
//加载类
Class clazz = Class.forName(classPathName);
Object o = clazz.newInstance();
4.1 java.lang.reflect.Field
代表类的成员变量,字段 (Field) 对象表示某个类的成员变量
Field nameField = clazz.getField("age");
通过get获取属性值,也可以通过set修改属性值:
nameField.set(o, 30);
System.out.println("修改age后的值为:" + nameField.get(o));
运行结果:
修改age后的值为:30
4.2 java.lang.reflect.Method
代表类的方法,方法(Method)对象表示某个类的方法
Method method = clazz.getMethod(methodName);
//通过invoke调用方法
method.invoke(o);
前面已经使用过,这里就不多说了。
4.3 java.lang.reflect.Constructor
代表类的构造方法,构造函数(Constructor)对象表示构造器
Constructor constructor1 = clazz.getConstructor();
Constructor constructor2 = clazz.getConstructor(String.class);
在程序中可以通过构造器来构造对象:
//使用无参构造器
Object object = constructor1.newInstance();
//使用带参构造器
Object object1 = constructor2.newInstance("test");
System.out.println(object);
System.out.println(object1);
运行结果:
Obj{name='mimi', age=20}
Obj{name='test', age=20}
5. java.lang.Class
5.1 概述
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是当一个类第一次被使用时,ClassLoader(类加载器)调用loadClass()方法创建出来的
- 对于某个类的字节码文件,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个 Class 实例所生成,因此可以通过getClass()获得
- 通过Class可以完整地得到一个类的完整结构,并通过一系列API来操作这个类
- Class对象是存放在堆的类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)
5.2 获取Class对象的六种方式
- Class.forName() 应用场景:通过配置文件获取
String classPathName = "com.chenshu.Obj";
Class cls1 = Class.forName(classPathName);
- 类名.class 应用场景:用于参数传递
Class cls2 = Obj.class;
- 对象.getClass() 应用场景:有对象实例
Obj obj = new Obj();
Class cls3 = obj.getClass();
- classLoader.loudClass()
ClassLoader classLoader = obj.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(classPathName);
- 基本数据类型.class
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
- 包装类型.TYPE
Class<Integer> type = Integer.TYPE;
Class<Character> type1 = Character.TYPE;
5.3 常用API及使用
定义一个下面的类用于测试:
class User {
//四种修饰符修饰的属性
public String name;
protected int age;
String gender;
private double salary;
//无参公开构造器
public User() {
}
//有参公开构造器
public User(String name) {
this.name = name;
}
//有参私有构造器
private User(String name, int age) {
this.name = name;
this.age = age;
}
//四种修饰符的方法
public void m1() {
}
protected void m2() {
}
void m3() {
}
private void m4() {
}
}
先获取User的Class对象,后面的API都通过这个Class对象来使用:
Class<?> userCls = Class.forName("com.chenshu.test.User");
- getName()获取全类名
System.out.println(userCls.getName());
运行结果:
com.chenshu.test.User
- getSimpleName()获取简单类名
System.out.println(userCls.getSimpleName());
运行结果:
User
- getFields()获取本类和父类的public属性
Field[] fields = userCls.getFields();
for (Field field : fields) {
System.out.println("本类及父类的public属性:" + field.getName());
}
运行结果:
本类及父类的public属性:name
- getDeclaredFields()获取所有本类的属性
Field[] declareFields = userCls.getDeclaredFields();
for (Field field : declareFields) {
System.out.println("本类的所有属性:" + field.getName());
}
运行结果:
本类的所有属性:name
本类的所有属性:age
本类的所有属性:gender
本类的所有属性:salary
- getMethods()获取本类及父类的public方法
Method[] methods = userCls.getMethods();
for (Method method : methods) {
System.out.println("本类及父类的public方法:" + method.getName());
}
运行结果:
本类及父类的public方法:m1
本类及父类的public方法:wait
本类及父类的public方法:wait
本类及父类的public方法:wait
本类及父类的public方法:equals
本类及父类的public方法:toString
本类及父类的public方法:hashCode
本类及父类的public方法:getClass
本类及父类的public方法:notify
本类及父类的public方法:notifyAll
- getDeclaredMethods() 获取本类的所有方法
Method[] declareMethods = userCls.getDeclaredMethods();
for (Method method : declareMethods) {
System.out.println("本类的所有方法:" + method.getName());
}
运行结果:
本类的所有方法:m1
本类的所有方法:m2
本类的所有方法:m3
本类的所有方法:m4
- getConstructors() 获取本类的public构造方法(没有父类)
Constructor<?>[] constructors = userCls.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("本类的public构造方法:" + constructor.getName());
}
运行结果:
本类的public构造方法:com.chenshu.test.User
本类的public构造方法:com.chenshu.test.User
- getDeclaredConstructors() 获取本类所有构造方法
Constructor<?>[] declaredConstructors = userCls.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
System.out.println("本类的所有构造方法:" + constructor.getName());
}
运行结果:
本类的所有构造方法:com.chenshu.test.User
本类的所有构造方法:com.chenshu.test.User
本类的所有构造方法:com.chenshu.test.User
- getPackage() 获取该类所属包
System.out.println(userCls.getPackage());
运行结果:
package com.chenshu.test
- getSuperclass() 获取该类的父类
新建一个People类,并让User继承People:
class People {
}
class User extends People{
测试代码:
Class<?> superclass = userCls.getSuperclass();
System.out.println(superclass);
运行结果:
class com.chenshu.test.People
- getInterfaces() 获取该类的所有接口
先编写两个接口,并让User类implement它们:
interface IA {
}
interface IB{
}
class User extends People implements IA, IB{
测试代码:
Class<?>[] interfaces = userCls.getInterfaces();
for(Class anInterface : interfaces) {
System.out.println("接口信息:" + anInterface);
}
运行结果:
接口信息:interface com.chenshu.test.IA
接口信息:interface com.chenshu.test.IB
- getAnnotations() 获取该类的所有注解
随便写一个注解,用于测试:
@Resource
class User extends People implements IA, IB{
测试代码:
Annotation[] annotations = userCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息:" + annotation);
}
运行结果:
注解信息:@javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=)
6. 反射暴力破解
前面提到的所有API,都只能获取到 公有的字段、方法、构造器,想要通过反射来获得私有的字段、方法、构造器就必须得用到暴力破解
暴力破解指的就是使用setAccessible()
这个方法,在这个方法中可以传true
或false
两种参数,分别代表禁用或启用安全检查,如果禁用安全检查,就可以不受修饰符的影响,随意实例化、调用方法、修改和获取属性,接下来我们来学习如何利用反射机制暴力破解。
创建一个User类用于测试:
class User {
private int age;
private String name;
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + ''' +
'}';
}
private User(int age, String name) {
this.age = age;
this.name = name;
}
private void userMethod() {
System.out.println("this is a private method..");
}
}
6.1 暴力创建实例
上面的User类只有一个private修饰的构造方法,通过new的方式无法创建实例,但是如果通过反射机制可以暴力破解。
测试代码:
Class userClazz = Class.forName("com.chenshu.vialate.User");
//获取私有构造器
Constructor declaredConstructor = userClazz.getDeclaredConstructor(int.class, String.class);
//暴破,使用反射可以访问private构造器
declaredConstructor.setAccessible(true);
Object user = declaredConstructor.newInstance(20, "zhangsan");
//打印user
System.out.println(user);
运行结果:
User{age=20, name='zhangsan'}
6.2 暴力破解属性
同样的,没有set方法是无法修改private修饰的属性的,但是通过反射,可以暴力破解。
测试代码:
Field age =userClazz.getDeclaredField("age");
age.setAccessible(true);
age.set(user, 99);
System.out.println(user);
我们可以发现age字段成功被修改:
User{age=99, name='zhangsan'}
6.3 暴力使用方法
最后,在出了User类这个作用域后,无法使用private修饰的方法,但是通过反射机制,一切都是浮云。
测试代码:
Method method = userClazz.getDeclaredMethod("userMethod");
method.setAccessible(true);
method.invoke(user);
运行结果:
this is a private method..