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对象也同样拥有,可以通过其去访问私有属性。

相关链接

附录

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) 根据传递的参数创建类的对象
posted @ 2020-03-25 00:57  DEADF1SH_CAT  阅读(436)  评论(0编辑  收藏  举报