Java安全初探-反射篇
Java反射机制
什么是Java 反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类信息以及动态调用对象内容就称为java语言的反射机制。
简单说,反射机制给java提供了一定的动态特性,让程序在运行时能够获取自身的信息,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息(方法和属性)。
可以干什么
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
可以看出,“任意”字眼突出了其可用性很大,诸如php反序列化,也是类似原理,通过反序列化操作对象执行恶意命令,因此这也是反序列化漏洞产生的一个前提条件。
如何实现反射
我们知道,要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个class对象就保存了这个类的一切信息。
反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。
反射的应用场合
编译时类型和运行时类型
在 Java 程序中许多对象在运行时都会出现两种类型:编译时类型和运行时类型。 编译时的类型由声明对象时使用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。如:
Person p=new Student();
其中编译时类型为 Person,运行时类型为 Student。
编译时类型无法获取具体方法
程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为 Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。
反射的使用
反射API
反射 API 用来生成 JVM 中的类、接口或则对象的信息。
- Class 类:反射的核心类,可以获取类的属性,方法等信息。
- Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。
上面提供的四个类都有提供对应的方法去操作对象,篇幅太长,就放在文末的附录部分了,可自行参考。接下来,来看看一下如何利用这些API实现操作任意对象
如何获取class对象
-
调用某个对象的 getClass()方法
Person p = new Person(); Class clazz = p.getClass();
-
调用某个类的 class 属性来获取该类对应的 Class 对象
Class clazz = Person.class;
-
使用 Class 类中的 forName()静态方法(最安全/性能最好)
Class clazz = Class.forName("reflection.Person"); (类的全路径,最常用)
如何获取类的属性和方法
以上面的Person类来说,假设已经获得其class对象,看看如何获得Person类中的属性与方法
-
获得所有成员属性信息
Field[] field = clazz.getDeclaredFields();
-
获得所有方法信息
Method[] method = clazz.getDeclaredMethods();
-
获得所有构造方法信息
Constructor[] constructor = clazz.getDeclaredConstructors();
当然,类似的getFilelds(),getMethods()等等也行的,只不过他们只能获取公共部分的信息,而getDeclaredFields()这些可以获得所有的信息,包括私有部分的,但从父类里继承来的就不包含了。
如何通过class对象创建对象
上面我们获得了Person类的class对象,也获得了其成员属性以及方法等信息,接下来我们就要利用这些信息了,那首先得创建一个Person对象先,下面举两个方法。
-
Class对象的newInstance()
Person p1 = (Person)clazz.newInstance();
-
调用Constructor对象的newInstance()
//假设Person存在构造方法Person(String name,int age) Constructor c = clazz.getDeclaredConstructor(String.class,int.class); Person p2 = (Person)c.newInstance("张三",20);
两种方法,区别其实很明显,第一种方法调用类的无参构造方法去创建对象,而第二种方法则可以自己选择需要的构造函数进行创建对象。在使用第一种方法时,往往还容易出错,这时候我们也容易猜出来原因可能是:
- 你使用的类没有无参构造函数
- 你使用的类构造函数是私有的
如何通过class对象调用对象的方法
要调用对象的方法,首先得获得一个Method对象,再通过Method对象的invoke方法去执行。invoke 的作用是执行方法,它的第一个参数是:
- 如果这个方法是一个普通方法,那么第一个参数是类对象
- 如果这个方法是一个静态方法,那么第一个参数是类
其实也不难理解,假设我们正常执行方法是 [1].method([2], [3], [4]...)
,那么到反射里就是method.invoke([1], [2], [3], [4]...)
,下面举例。
//假设Person存在一个方法eat(String food)
Method method = clazz.getMethod("eat",String.class);
method.invoke(p1,"apple");
//假设eat方法是私有的呢
Method method = clazz.getMethod("eat",String.class);
method.setAccessible(true);
method.invoke(p1,"apple");
setAccessible()可以取消Java的权限控制检查,使私有方法可以访问,注意此时并没有更改其访问权限,可以理解为无视了作用域。当然,这不是Method对象专用的,Field对象也同样拥有,可以通过其去访问私有属性。
相关链接
- Java高级特性——反射
- https://developer.android.google.cn/reference/java/lang/reflect/package-summary?hl=zh-cn
- https://developer.android.google.cn/reference/java/lang/reflect/Method?hl=zh-cn
附录
Class类
Class代表类的实体,在运行的Java应用程序中表示类和接口。
- 获得类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
- 获得类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
- 获得类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
- 获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
- 类中其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
Field类
Field代表类的成员变量(成员变量也称为类的属性)。
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
Method类
Method代表类的方法。
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
Constructor类
Constructor代表类的构造方法。
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |