java反射 什么是反射?
简单来说就是再java执行的过程中,能够动态的获取类的信息(类名,方法,属性),并且还能调用方法,修改属性。
获取Class对象 获取Class对象的方法有以下几种:
根据类名:类名.class
根据对象:对象.getclass()
根据全限定类名:Class.forName(全路径类名)
通过类加载器获取class对象:ClassLoader.getSystemClassLoader().loadClass(“com.example.xxx”);
下面举一个例子:
首先创建User.java:
1 2 3 4 5 6 7 8 9 10 11 12 package org.example.entity;public class User { public String name = "fsrm" ; public String getName () { return name; } public void setName (String testStr) { this .name = name; } }
再创建一个Getclass.java,分别对应着获取class对象的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.example;import org.example.entity.User;public class GetClass { public static void main (String[] args) throws ClassNotFoundException { Class c1 = User.class; System.out.println(c1); User user = new User (); Class c2 = user.getClass(); System.out.println(c2); Class c3 = Class.forName("org.example.entity.User" ); System.out.println(c3); ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class c4 = classLoader.loadClass("org.example.entity.User" ); System.out.println(c4); } }
比较常用的是第三种方法,通过Class.forNmae(“xxx”)获取类名。
Java反射API java.lang.Class 用来描述类的内部信息,Class的实例可以获取类的包、注解、修饰符、名称、超类、接口等。
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html
方法名
释义
getPackage()
获取该类的包
getDeclaredAnnotations()
获取该类上所有注解
getModifiers()
获取该类上的修饰符
getName()
获取类名称
getSimpleName()
获取简单类名称
getGenericSuperclass()
获取直属超类
getGenericInterfaces()
获取直属实现的接口
newInstance()
根据构造函数创建一个实例
更多方法可查看官方文档……
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 package org.example.reflectdemo;import java.lang.reflect.Modifier;public class ClassDemo { public static void main (String[] args) throws ClassNotFoundException { Class clazz = Class.forName("org.example.reflectdemo.UserInfo" ); Package aPackage = clazz.getPackage(); System.out.println("当前类的包名是:" +aPackage); int modifiers = clazz.getModifiers(); String modifierStr = Modifier.toString(modifiers); System.out.println("当前类的修饰符是:" +modifierStr); String className = clazz.getName(); System.out.println("当前类的名称是:" +className); String simpleName = clazz.getSimpleName(); System.out.println("当前类的简单名称是:" +simpleName); } }
java.lang.reflect.Field 提供了类的属性信息。可以获取属性上的注解、修饰符、属性类型、属性名等。
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html
方法名
释义
getField(“xxx”)
获取目标类中声明为 public 的属性
getFields()
获取目标类中所有声明为 public 的属性
getDeclaredField(“xxx”)
获取目标类中声明的属性
getDeclaredFields()
获取目标类中所有声明的属性
更多方法可查看官方文档……
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 package org.example.reflectdemo;import java.lang.reflect.Field;public class FieldDemo { public static void main (String[] args) throws Exception{ Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo" ); Field field1 = clazz.getField("age" ); System.out.println("get field结果" + field1); Field[] filedArray = clazz.getFields(); for (Field field : filedArray) { System.out.println("get fields结果" + field); } Field field2 = clazz.getDeclaredField("age" ); System.out.println("getDeclaredField结果" + field2); Field[] declareAaary = clazz.getDeclaredFields(); for (Field declareField : declareAaary){ System.out.println("getDeclaredFields结果" + declareField); } } }
修改字段值
1.修改public属性的值。
这个定义为20,利用反射修改它的值,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Field field2 = clazz.getDeclaredField("age" ); UserInfo user = new UserInfo (); field2.set(user, 30 ); Object name = field2.get(user); System.out.println("getDeclaredField结果" + name); 或者是 Method method = clazz.getMethod("getAge" ); Object obj = method.invoke(null ); Field field2 = clazz.getField("age" ); field2.set(obj, 30 ); Object obj1 = field2.get(obj); System.out.println(obj1); 前提是getAge方法是静态的 并且它是返回实例化之后的对象的
如果是修改私有属性的值,代码如下:
1 2 3 4 5 6 Field field2 = clazz.getDeclaredField("name" ); field2.setAccessible(true ); UserInfo user = new UserInfo (); field2.set(user, "李四" ); Object name = field2.get(user); System.out.println("getDeclaredField结果" + name);
修改final定义的值。
java.lang.reflect.Method 提供了类的方法信息。可以获取方法上的注解、修饰符、返回值类型、方法名称、所有参数。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html
方法名
释义
getMethod(“setAge”, String.class)
获取目标类及父类中声明为 public 的方法,需要指定方法的入参类型
getMethods()
获取该类及父类中所有声明为 public 的方法
getDeclaredMethod()
获取一个在该类中声明的方法
getDeclaredMethods()
获取所有在该类中声明的方法
getParameters()
获取所有传参
更多方法可查看官方文档……
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 package org.example.reflectdemo;import java.lang.reflect.Method;import java.lang.reflect.Parameter;public class MethodDemo { public static void main (String[] args) throws Exception { Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo" ); Method method = clazz.getMethod("setName" , String.class); System.out.println("01-getMethod运行结果:" + method); Method[] methods = clazz.getMethods(); for (Method m : methods){ System.out.println("02-getMethods运行结果:" + m); } Parameter[] parameters = method.getParameters(); for (Parameter p : parameters){ System.out.println("03-getParameters运行结果:" + p); } Method DeclareMethod = clazz.getDeclaredMethod("getName" ); System.out.println("04-getDeclaredMethod运行结果:" + DeclareMethod); Method[] DeclareMethods = clazz.getDeclaredMethods(); for (Method declareMethod : DeclareMethods){ System.out.println("05-getDeclaredMethods运行结果:" + declareMethod); } } }
java.lang.reflect.Modifier 提供了访问修饰符信息。通过Class、Field、Method、Constructor等对象都可以获取修饰符,这个访问修饰符是一个整数,可以通过Modifier.toString方法来查看修饰符描述。并且该类提供了一些静态方法和常量来解码访问修饰符。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Modifier.html
方法名
释义
getModifiers()
获取类的修饰符值
getDeclaredField(“username”).getModifiers()
获取属性的修饰符值
更多方法可查看官方文档……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.example.reflectdemo;import java.lang.reflect.Modifier;public class ModifierDemo { public static void main (String[] args) throws Exception { Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo" ); int modifiers = clazz.getModifiers(); System.out.println("获取类的修饰符值getModifiers运行结果:" + modifiers); int modifiers1 = clazz.getDeclaredField("name" ).getModifiers(); System.out.println("获取属性的修饰符号getDeclaredField运行结果:" + modifiers1); int modifers2 = clazz.getDeclaredMethod("introduce" ).getModifiers(); System.out.println("获取方法的修饰符号getMethod运行结果:" + modifers2); String modifersStr = Modifier.toString(modifers2); System.out.println("根据修饰符值获取修饰符标志的字符串:" + modifersStr); } }
java.lang.reflect.Constructor 提供了类的构造函数信息。可以获取构造函数上的注解信息、参数类型等。
官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html。
方法名
释义
getConstructor()
获取一个声明为 public 构造函数实例
getConstructors()
获取所有声明为 public 构造函数实例
getDeclaredConstructor()
获取一个声明的构造函数实例
getDeclaredConstructors()
获取所有声明的构造函数实例
更多方法可查看官方文档……
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 package org.example.reflectdemo;import java.lang.reflect.Constructor;public class ConstructorDemo { public static void main (String[] args) throws Exception { Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo" ); Constructor<?> constructor1 = clazz.getConstructor(String.class, int .class); System.out.println("01-constructor1: " +constructor1); Object obj1 = constructor1.newInstance("张三" , 20 ); System.out.println("02-obj1: " +obj1); Constructor<?> constructorArray1[] = clazz.getConstructors(); for (Constructor<?> constructor : constructorArray1){ System.out.println("03-constructor: " +constructor); } Constructor<?> constructor2 = clazz.getDeclaredConstructor(int .class); System.out.println("04-constructor2: " +constructor2); constructor2.setAccessible(true ); Object obj2 = constructor2.newInstance(66 ); System.out.println("05-obj2: " +obj2); Constructor<?> constructorArray2[] = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructorArray2){ System.out.println("06-constructor: " +constructor); } } }
java.lang.reflect.AccessibleObject 是Field、Method和Constructor类的超类。该类提供了对类、方法、构造函数的访问控制检查的能力(如:私有方法只允许当前类访问)。
该访问检查 在设置/获取属性、调用方法、创建/初始化类的实例时执行。
方法名
释义
setAccessible()
将可访问标志设为true(默认为false),会关闭访问检查。这样即使是私有的属性、方法或构造函数,也可以访问。
当访问某个私有属性的值,或者需要修改某个私有属性值的时候,就受到访问限制的约束,这个时候就要利用setAccessible 使私有属性内容可修改或可访问。
newInstance()
将获取到的对象实例化。调用的是这个类的无参构造函数。
使用 newInstance 不成功的话可能是因为:①、你使用的类没有无参构造函数,②、你使用的类构造函数是私有的。
参考文章:https://mp.weixin.qq.com/s/c7myV7OurX26hx10fqeLdA?scene=1
Java反射到命令执行(java.lag.Runtime) 通过getMethod 先说明一下有关invoke的理解
invoke中第一个参数需要是一个实例化,或者是静态方法的调用。
先说一下静态方法的调用:
1 2 3 4 5 6 7 8 9 10 11 12 package org.example.entity;import java.lang.reflect.Method;public class test {public static void main (String[] args) throws Exception { Class <?> clazz = Class.forName("java.lang.Runtime" ); Method method1 = clazz.getMethod("getRuntime" ); Object obj = method1.invoke(clazz); } }
java.lang.Runtime中的getRuntime方法是静态方法。
所以再进行调用invoke的时候,第一个参数可以是null,也可以是clazz.
那么另外一种情况,第一个参数是实例化的时候。
1 2 3 Class <?> clazz = Class.forName("java.lang.Runtime" ); Method method = clazz.getMethod("exec" ,String.class); System.out.println(method);
这个方法就不是静态方法了,那么第一个参数就需要是经过实例化的。
1 2 3 Class <?> clazz = Class.forName("java.lang.Runtime" );Method method = clazz.getMethod("exec" ,String.class);Object obj = method.invoke(Runtime.getRuntime(),"calc" );
下面是一些私有类的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.example.reflectdemo;import org.example.reflectdemo.UserInfo;import java.lang.reflect.Method;public class invokeDemo { public static void main (String[] args) throws Exception { Class clazz = Class.forName("org.example.reflectdemo.UserInfo" ); Method method = clazz.getDeclaredMethod("introduce" ); method.setAccessible(true ); Object obj = method.invoke(new UserInfo ("张三" ,25 )); System.out.println(obj); } }
下面则是利用getMethod反射调用exec执行命令的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package org.example.codeexec;import java.lang.reflect.Method;public class RuntimegetMethod { public static void main (String[] args) throws Exception { Class <?> clazz = Class.forName("java.lang.Runtime" ); Method method1 = clazz.getMethod("getRuntime" ); Method method2 = clazz.getMethod("exec" , String.class); Object obj = method1.invoke(null ); method2.invoke(obj, "calc" ); } }
合在一起的:
1 2 3 4 public static void main (String[] args) throws Exception { Class <?> clazz = Class.forName("java.lang.Runtime" ); clazz.getMethod("exec" , String.class).invoke(clazz.getMethod("getRuntime" ).invoke(null ),"calc" ); }
通过getDeclaredConstructor(高版本jdk绕过反射限制) 这个就设计到高版本jdk绕过反射的限制,也就是再调用一些私有类的时候,绕过这个限制。
JDK 9 引入了模块系统,其中包括java.lang, java.io, java.util 等包。
java.lang.*(包括 Runtime、ProcessImpl、System 等)属于模块 java.base
java.base 明确地「封装」了这些包,不对外开放反射访问
是这个setAccessible的问题,跟进去看下。
这里的意思是返回调用setAccessible方法的类,比如说是RuntimeDeclaredConstructor类调用的这个方法,那么返回的就是RuntimeDeclaredConstructor
跟进checkCanSetAccessible。
到最后跟进的的方法如下:
要想返回true的话,一共有6个if条件。
先说前三个:
1 2 3 if (callerModule == declaringModule) return true ; // 如果调用者的模块和声明类的模块一致,则返回true if (callerModule == Object .class .getModule()) return true ; // 如果调用者的模块是java.base的话,则返回true if (!declaringModule.isNamed()) return true ; // 如果声明类的模块是未命名的,则返回true
后面三个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 boolean isClassPublic = Modifier . isPublic(declaringClass .getModifiers () ); if (isClassPublic && declaringModule.isExported(pn , callerModule ) ) { if (Modifier . isPublic(modifiers ) ) { return true ; } if (Modifier . isProtected(modifiers ) && Modifier . isStatic(modifiers ) && isSubclassOf(caller , declaringClass ) ) { return true ; } } if (declaringModule.isOpen(pn , callerModule ) ) { return true ; }
所以要想绕过jdk下反射的限制,就要满足上面任意一个if条件即可。
在 jdk 9 后的模块化机制提到
1 Note that the sun.misc and sun.reflect packages are available for reflection by tools and libraries in all JDK releases, including JDK 17.
sun.misc和sun.reflect包下的我们是可以正常反射的,关键利用点就在这里。
这里利用的是sun.misc.Unsafe,将调用类中的模块改为java.base
代码如下:
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 com.sun.tools.javac.Main;import sun.misc.Unsafe;import java.io.BufferedReader;import java.io.InputStreamReader;import java.lang.invoke.MethodHandle;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.nio.charset.Charset;import java.util.ArrayList;import java.util.List;import java.util.Map;public class test { public static void main (String[] args) throws Exception { Class<?> unsafeClass = Class.forName("sun.misc.Unsafe" ); Field field = unsafeClass.getDeclaredField("theUnsafe" ); field.setAccessible(true ); Unsafe unsafe = (Unsafe) field.get(null ); System.out.println(unsafe); Module baseModule = Object.class.getModule(); System.out.println(baseModule); Class<?> currentClass = test.class; System.out.println(currentClass); long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )); System.out.println(addr); System.out.println(unsafe.getAndSetObject(currentClass, addr, baseModule)); } }
唯一要变的是 Class<?> currentClass = test.class; 根据当前的类名不同,进行修改。
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 package org.example.codeexec;import sun.misc.Unsafe;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class RuntimeDeclaredConstructor { public static void main (String[] args) throws Exception { Class<?> clazz = Class.forName("java.lang.Runtime" ); Constructor<?> m = clazz.getDeclaredConstructor(); Class<?> clazz1 = Class.forName("sun.misc.Unsafe" ); Field field = clazz1.getDeclaredField("theUnsafe" ); field.setAccessible(true ); Unsafe unsafe = (Unsafe) field.get(null ); Module module = Object.class.getModule(); Class<?> Run = RuntimeDeclaredConstructor.class; long addr= unsafe.objectFieldOffset(Class.class.getDeclaredField("module" )); unsafe.getAndSetObject(Run, addr, module ); m.setAccessible(true ); Method c1 = clazz.getMethod("exec" , String.class); c1.invoke(m.newInstance(), "calc.exe" ); } }
反射之java.lang.ProcessBuilder ProcessBuilder进行命令执行的大概思路如下:
首先通过command设置命令执行的参数,如calc等,最后start调用this.command中的参数信息,从而进行命令执行。
ProcessBuilder中传递 this.command 大概有4个 其中两个构造函数,两个普通函数。
ProcessBuilder有两个构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 public ProcessBuilder (List<String> command) { if (command == null ) throw new NullPointerException (); this .command = command; } public ProcessBuilder (String... command) { this .command = new ArrayList <>(command.length); for (String arg : command) this .command.add(arg); }
利用构造函数进行命令执行如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 Class<?> clazz = Class.forName("java.lang.ProcessBuilder" ); Constructor<?> constructor = clazz.getDeclaredConstructor(String[].class); Object obj = constructor.newInstance((Object) new String [] {"calc" }); Method method = clazz.getMethod("start" ); method.invoke(obj); Class<?> clazz = Class.forName("java.lang.ProcessBuilder" ); Constructor<?> constructor = clazz.getDeclaredConstructor(List.class); List<String> cmdlist = new ArrayList <>(); cmdlist.add("calc" ); Object obj = constructor.newInstance((cmdlist)); Method method = clazz.getMethod("start" ); method.invoke(obj);
两个普通函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 public ProcessBuilder command (List<String> command) { if (command == null ) throw new NullPointerException (); this .command = command; return this ; } public ProcessBuilder command (String... command) { this .command = new ArrayList <>(command.length); for (String arg : command) this .command.add(arg); return this ; }
利用普通函数进行命令执行代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Class<?> clazz = Class.forName("java.lang.ProcessBuilder" ); Method method1 = clazz.getDeclaredMethod("command" , String[].class); Method method2 = clazz.getDeclaredMethod("start" ); ProcessBuilder processBuilder = new ProcessBuilder (); Object obj = method1.invoke(processBuilder,(Object) new String [] {"calc" }); method2.invoke(processBuilder); Class<?> clazz = Class.forName("java.lang.ProcessBuilder" ); Method method1 = clazz.getDeclaredMethod("command" , List.class); Method method2 = clazz.getDeclaredMethod("start" ); ProcessBuilder processBuilder = new ProcessBuilder (); List<String> cmdlist = new ArrayList <>(); cmdlist.add("calc" ); Object obj = method1.invoke(processBuilder, cmdlist); method2.invoke(processBuilder);