最近开始学习java安全,发现有很多基础知识需要掌握,如java反射,jndi,rmi,类加载机制等等。本篇文章用来记录java反射的学习,后续会继续其它知识的学习。
反射的基本原理
什么是反射?
反射是java的特性之一。java反射机制可以无视类方法,变量去访问权限修饰符,并且可以调用任何类的任意方法,访问并修改成员变量值。
通俗的说java反射可以在运行时根据传参确定要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
看了上面的解释可能我们会有这样的疑问,通常我们都是new一个对象,为什么要多此一步使用反射创建对象?
1)动态的加载程序,例如动态加载插件,刚开始可能根本就不知道要实例化那个类。很多工厂模式都使用了反射
2)从配置文件读取类名,然后在加载。在各类框架中大量应用。
其实就是提供了一种动态创建对象的方式。
反射的作用
反射的作用主要是下面的几种:
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法。
生成动态代理
反射的具体应用
1)在开发过程中使用Eclipse、IDEA等开发工具时,当我们输入一个对象或类并想调用它的属性或方法时,编译器会自动列出它的属性或方法
2)JavaBean和JSP之间的调用也是通过反射实现的
3)反射最重要的用途是开发各种通用框架,如上文中提到的Spring框架以及ORM框架,都是通过反射机制来实现的
Class类
在正式学习反射以前,先来认识一下Class类
Class也是一个java类,保存的是与之对应java类的meta信息,用来描述这个类有哪些成员,有哪些方法等。
Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例(Class 对象)。也就是说,在 Java 中,每个 java 类都有一个相应的 Class 对象,用于表示这个 java 类的类型信息。
反射的使用
1.获取class方式
要使用发射必须要先获取类的对象class,获取class对象一般有三种方式。
1)通过class.forName()
2)直接获取
3)使用getClass()方法
如下:
/** * 获取class的三种方式 * Class.forName():参数是一个字符串,字符串为一个完整的类名,完整类名必须带有包名。这种方式是最常用的。 * s.getClass():类的对象通过getClass()方法可以获取到类的Class。 * 类.class:类名.class来获取 * 在使用过程中使用比较多的就是第一种 * **/ Class clazz1 = Class.forName("java.lang.Runtime"); Class clazz2 = Runtime.getRuntime().getClass(); Class<?> clazz3 = Runtime.class;
2.通过Class实例化类
通过Class实例化类可以通过调用类的无参构造函数或者有参构造函数。为了能够方便的使用,目前一般都要求类中包含无参构造函数。
/** * 通过Class实例化类 * 通过无参构造函数实例化类:clazz1.newInstance() *调用有参的构造函数实例化类:clazz1.getConstructors(); * */ try { clazz1.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } Constructor<?> cons[] = null; Runtime per = null; //首先通过getConstructors获取全部的构造方法,然后在进行实例化 cons = clazz1.getConstructors();
3.获取类的方法
获取类的所有方法,一般通过下面的方法
1)getDeclaredMethods方法:返回类或接口声明的所有方法,包括public、protected、private和默认方法,但不包括继承的方法返回类或接口声明的所有方法,包括public、protected、private和默认方法,但不包括继承的方法
2)getMethods方法:返回某个类的所有public方法,包括其继承类的public方法
3)getMethod方法:只能返回一个特定的方法
4)getDeclaredMethod方法:与getMethod方法类似,只能返回一个特定的方法。
Method[] declaredMethods = clazz1.getDeclaredMethods(); System.out.println("通过getDeclaredmehtods方式获取方法"); for(Method m:declaredMethods) System.out.println(m); Method[] methods = clazz1.getMethods(); System.out.println("通过getMethods方式获取方法"); for(Method m:methods) System.out.println(m); Method method=clazz1.getMethod("exec", String.class); System.out.println("通过getMethods方式获取方法"); System.out.println(method); Method method1 = clazz1.getDeclaredMethod("exec", String.class); System.out.println("通过getDeclaredMethod方式获取方法"); System.out.println(method1);
4.获取类的成员变量
1)getDeclaredFields方法:getDeclaredFields方法能够获得类的成员变量数组,包括public、private和proteced,但是不包括父类的申明字段
2)getFields:getFields能够获得某个类的所有的public字段,包括父类中的字段
3)getDeclaredField:该方法与getDeclaredFields的区别是只能获得类的单个成员变量,这里我们仅想获得Student 类中的name 变量
4)getField:与getFields类似,getField方法能够获得某个类特定的public字段,包括父类中的字段
Field[] getDeclaredFields = clazz1.getDeclaredFields(); System.out.println("通过getDeclaredFields方式获取变量"); for(Field m:getDeclaredFields) System.out.println(m); Field[] getFields = clazz1.getFields(); System.out.println("通过getFields方式获取变量"); for(Field m:getFields) System.out.println(m); Field getDeclaredMethod=clazz1.getDeclaredField("currentRuntime"); System.out.println("通过getDeclaredMethod方式获取变量"); System.out.println(getDeclaredMethod); Field getField = clazz1.getField("currentRuntime"); System.out.println("通过getField方式获取变量"); System.out.println(getField);
5.调用类的方法
通过反射调用类的方法,先通过getMethod获取要调用的方法,然后通过invoke调用。
Method getMethod(String name, Class[] params)
name参数指定方法的名字
params参数指定方法的参数类型
invoke(Object obj, Object args[])
第一个参数必须为要调用的对象
第二个参数为要传入的参数对象
下面为利用反射执行函数的一个例子
java正常执行命令的语句是:Runtime.getRuntime().exec("calc.exe")。通过反射如下:
Method method2 = clazz1.getMethod("exec", String.class); method2.invoke(Runtime.getRuntime(),"calc.exe");
反射的安全性分析
反射给程序带来了极大的便利性,但是同时也带来的安全威胁,很多的反序列化都用到了反射的知识。如在ysoserial中Transformer命令执行链就是运用的反射的知识。因此,使用发射时要注意避免产生安全问题。