九维团队-绿队(改进)| java 反射机制(上)

admin 2023年5月18日01:18:19评论18 views字数 12699阅读42分19秒阅读模式

九维团队-绿队(改进)| java 反射机制(上)

一、何为反射


反射即Reflection,Java 的反射是指程序在运行期可以拿到一个对象的所有信息。即 Java 反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为 java 语言的反射机制。


class(包括interface)的本质是数据类型(Type),而class是由 JVM 在执行过程中动态加载的。JVM 在第一次读取到一种class类型时,将其加载进内存。


每加载一种class,JVM 就为其创建一个Class类型的实例,并关联起来。

*注意:这里的Class类型是一个名叫Class的class。它长这样:

public final class Class {    private Class() {}}


以String类为例,当 JVM 加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);


这个Class实例是 JVM 内部创建的,如果我们查看 JDK 源码,可以发现Class类的构造方法是private,只有 JVM 能创建Class实例,我们自己的 Java 程序是无法创建Class实例的。


所以,JVM 持有的每个Class实例都指向一个数据类型(class或interface)。


由于 JVM 为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。


这种通过Class实例获取class信息的方法称为反射(Reflection)。


二、获取 class 的 Class 实例


获取一个class的Class实例,有4种方法:


方法一

直接通过一个class的静态变量class获取:

Class cls = String.class;


方法二

如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:

String s = "Hello";Class cls = s.getClass();


方法三

如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

Class cls = Class.forName("java.lang.String);

*左右滑动查看更多


方法四

利用classLoader:

Class cls = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime)

*左右滑动查看更多


比较


因为Class实例在 JVM 中是唯一的,所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例:

Class cls1 = String.class;
String s = "Hello";Class cls2 = s.getClass();
boolean sameClass = cls1 == cls2; // true

*左右滑动查看更多


获取基本信息


获取 class 的基本信息:

package org.example;
import java.util.ArrayList;
public class App{ public static void main(String[] args) {        Class<String> cls1 = String.class;
ArrayList a = new ArrayList(); Class cls2 = a.getClass();
printInfo(cls1); printInfo(cls2); }
static void printInfo(Class cls){ System.out.println(&quot;Class name : &quot; + cls.getName()); System.out.println(&quot;Simple name: &quot; + cls.getSimpleName()); if (cls.getPackage() != null) { System.out.println(&quot;Package name: &quot; + cls.getPackage().getName()); } System.out.println(&quot;is interface: &quot; + cls.isInterface()); System.out.println(&quot;is enum: &quot; + cls.isEnum()); System.out.println(&quot;is array: &quot; + cls.isArray()); System.out.println(&quot;is primitive: &quot; + cls.isPrimitive()); } }

*左右滑动查看更多


小结


1、JVM 为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;

2、获取一个class对应的Class实例后,就可以获取该class的所有信息; 

3、通过 Class 实例获取class信息的方法称为反射(Reflection);

4、JVM 总是动态加载class,可以在运行期根据条件来控制加载 class。


三、访问字段  


对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。


我们先看看如何通过Class实例获取字段信息。


获取字段的一些信息


import java.util.Arrays;
public class Test{ public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Class ot = OtherTest.class; System.out.println(Arrays.toString(ot.getFields())); // 获取所有public的field(包括父类) System.out.println(Arrays.toString(ot.getDeclaredFields())); // 获取当前类的所有field(不包括父类) System.out.println(ot.getField(&quot;a&quot;)); // 根据字段名获取某个 public 的field(包括父类) System.out.println(ot.getDeclaredField(&quot;b&quot;)); // 根据字段名获取当前类的某个field(不包括父类)
System.out.println(ot.getField(&quot;a&quot;).getName()); // 字段名称 System.out.println(ot.getField(&quot;a&quot;).getType()); // 字段类型,也是一个Class实例 System.out.println(ot.getField(&quot;a&quot;).getModifiers()); // 修饰符 }}
class OtherTest extends emmTest{ public int a = 5; private int b;}
class emmTest { public float cc;}

*左右滑动查看更多


[public int OtherTest.a, public float emmTest.cc][public int OtherTest.a, private int OtherTest.b]public int OtherTest.aprivate int OtherTest.baint1

*左右滑动查看更多


获取字段的值


先获取Class实例,再获取Field实例,然后,用Field.get(Object)获取指定实例的指定字段的值。

package org.example;

import java.lang.reflect.Field;
public class App { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { OtherTest ot = new OtherTest(&quot;haha&quot;);
Class cls = ot.getClass(); Field f = cls.getDeclaredField(&quot;name&quot;); f.setAccessible(true); // 设置访问权限,一律为true,不然不能访问 private 的 Object value = f.get(ot); // 从对象ot中获取值,因为所有的同类型class共用一个Class,所以获取内容要选定对象 System.out.println(value); }}
class OtherTest { private String name;
public OtherTest(String name) { this.name = name; }}
// 输出 haha

*左右滑动查看更多


反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。


此外,setAccessible(true)可能会失败。如果 JVM 运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证 JVM 核心库的安全。


获取所有的字段


public class APP {    public static void main(String[] args) throws IllegalAccessException {        OtherTest ot = new OtherTest(&quot;haha&quot;);        Class cls = ot.getClass();        Field[] f = cls.getDeclaredFields(); //取所有的字段        for (Field field : f) {            field.setAccessible(true);// 设置访问权限,一律为true,不然不能访问 private 的            System.out.println(field.getName() + &quot; &quot; + field.getType()); // 获取field 的Name,Type            System.out.println(field.get(ot));        }    }}
class OtherTest { private String name;
public OtherTest(String name) { this.name = name; }}// 输出// name class java.lang.String// haha

*左右滑动查看更多


修改字段的值


package org.example;

import java.lang.reflect.Field;
public class App { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { OtherTest ot = new OtherTest(&quot;haha&quot;);
Class cls = ot.getClass(); Field f = cls.getDeclaredField(&quot;name&quot;); f.setAccessible(true); // 设置访问权限,一律为true,不然不能访问 private 的 f.set(ot, &quot;modify&quot;); // 反射修改值
System.out.println(ot.getName()); }}
class OtherTest { private String name;
public OtherTest(String name) { this.name = name; }
public String getName() { return name; }}

*左右滑动查看更多


小结


Java 的反射 API 提供的Field类封装了字段的所有信息:

1、通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields(); 

2、通过 Field 实例可以获取字段信息:getName(),getType(),getModifiers(); 

3、通过 Field 实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。

4、通过反射读写字段是一种非常规方法,它会破坏对象的封装。


四、调用方法


获取方法


通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method:


import java.util.Arrays;
public class Test{ public static void main(String[] args) throws NoSuchMethodException { Class<OtherTest> cls = OtherTest.class; // Class cls = ot.getClass(); System.out.println(Arrays.toString(cls.getMethods())); // 获取所有public的Method(包括父类) System.out.println(Arrays.toString(cls.getDeclaredMethods())); // 获取当前类的所有Method(不包括父类) System.out.println(cls.getMethod(&quot;echoEver&quot;, String.class)); // 获取某个public的Method(包括父类) //.getMethod(方法名,这个方法的参数类型) System.out.println(cls.getDeclaredMethod(&quot;echoEver&quot;, String.class)); // 获取当前类的某个Method(不包括父类) }}
class OtherTest{ public void echoEver(String thing){ System.out.println(thing); }}
// =====
/*[public void org.example.OtherTest.echoEver(java.lang.String), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
[public void org.example.OtherTest.echoEver(java.lang.String)]
public void org.example.OtherTest.echoEver(java.lang.String)
public void org.example.OtherTest.echoEver(java.lang.String)*/

*左右滑动查看更多


调用方法


  • 获取 Class 实例

  • 反射获取方法

  • invoke 调用方法

package org.example;
import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;
public class App { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { OtherTest ot = new OtherTest(); Class cls = ot.getClass(); Method echoEver = cls.getDeclaredMethod(&quot;echoEver&quot;, String.class); echoEver.setAccessible(true); echoEver.invoke(ot,&quot;test&quot;); // 第一个参数是调用该方法的对象,第二个参数是一个可变长参数,是这个方法的需要传入的参数
}}
class OtherTest{ private void echoEver(String thing){ System.out.println(thing); }}

*左右滑动查看更多


示例


这里再以 Java 中使用反射调取 Runtime 来执行命令注入:

// 传统使用Java 来执行Runtime进行命令执行代码package org.example;
import java.io.*;
public class App { public static void main(String[] args) throws IOException{ Process s = Runtime.getRuntime().exec(&quot;whoami&quot;); InputStream inputStream = s.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } }}

*左右滑动查看更多


使用反射:

package com.ReflectTest;
import java.io.*;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;
public class Reflect { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class clazz = Class.forName(&quot;java.lang.Runtime&quot;); Method method = clazz.getDeclaredMethod(&quot;exec&quot;, String.class); Process process = (Process) method.invoke(Class.forName(&quot;java.lang.Runtime&quot;).getDeclaredMethod(&quot;getRuntime&quot;).invoke(Class.forName(&quot;java.lang.Runtime&quot;)), &quot;whoami&quot;); // 这里细看Class.forName(&quot;java.lang.Runtime&quot;).getDeclaredMethod(&quot;getRuntime&quot;).invoke(Class.forName(&quot;java.lang.Runtime&quot;)) // 在这里,method.invoke 第一个参数是实例化的对象,从不使用反射的代码中看,应该是Runtime.getRuntime() 这个实例化对象 // 然后要调用 getRuntime()的实例化对象则是Runtime,所以整合起来如下: // Class.forName(&quot;java.lang.Runtime&quot;).getDeclaredMethod(&quot;getRuntime&quot;).invoke(Class.forName(&quot;java.lang.Runtime&quot;)) // 这个就是上述调用exec方法的实例化对象RunTime.getRuntime() // 反射调用方法,就是从后往前依次寻找调用方法的实例化对象,加上参数 InputStream inputStream = process.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } }}
class OtherTest { private void echoEver(String thing) { System.out.println(thing); }}

*左右滑动查看更多


小结


Java 的反射 API 提供的 Method 对象封装了方法的所有信息:

1、通过Class实例的方法可以获取Method实例:getMethod()、getMethods()、getDeclaredMethod()、getDeclaredMethods();

2 、通过Method实例可以获取方法信息:getName()、getReturnType()、getParameterTypes()、getModifiers(); 

3 、通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters); 

4、通过设置setAccessible(true)来访问非public方法; 

5、通过反射调用方法时,仍然遵循多态原则。


五、调用构造方法


举例


我们通常使用new操作符创建新的实例:

Person p = new Person();


如果通过反射来创建新的实例,可以调用 Class 提供的newInstance()方法:

Person p = Person.class.newInstance();

*左右滑动查看更多


调用Class.newInstance()的局限是,它只能调用该类的 public 无参数构造方法。如果构造方法带有参数,或者不是 public,就无法直接通过Class.newInstance()来调用。


为了调用任意的构造方法,Java 的反射 API 提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor 对象和 Method 非常类似,不同之处仅在于它是一个构造方法,并且调用结果总是返回实例:

package org.example;
import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.Arrays;
public class App { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {        Class<Integer> cls = Integer.class; System.out.println(cls.getName()); System.out.println(Arrays.toString(cls.getConstructors())); // Integer.class.getConstructor(int.class); Constructor<Integer> cons1 = cls.getConstructor(int.class); Integer int1 = cons1.newInstance(123); System.out.println(int1);
Constructor<Integer> cons2 = cls.getConstructor(String.class); System.out.println(cons2.newInstance(&quot;456&quot;)); }}/*java.lang.Integer[public java.lang.Integer(int), public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException]123456*/

*左右滑动查看更多


通过 Class 实例获取 Constructor 的方法如下:

  • getConstructor(Class...):

    获取某个public的Constructor;

  • getDeclaredConstructor(Class...):

    获取某个Constructor;

  • getConstructors():

    获取所有public的Constructor;

  • getDeclaredConstructors():

    获取所有Constructor。


注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。


调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问,否则setAccessible(true)可能会失败。


示例


调用方法示例使用 invoke 方法反射调用 Runtime.getRuntime.exec(String.class)方法,那么由 Construct 改写如下:

package com.ReflectTest;

import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;
public class App { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { Class clazz = Class.forName(&quot;java.lang.Runtime&quot;); Method method = clazz.getDeclaredMethod(&quot;exec&quot;, String.class); Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); // 实例化 Runtime runtime = (Runtime) constructor.newInstance(); // 使用exec调用 Process process = (Process) method.invoke(runtime.getRuntime(), &quot;whoami&quot;); InputStream inputStream = process.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } }}

*左右滑动查看更多


小结


Constructor对象封装了构造方法的所有信息;

1、通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors(); 

2、通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters);通过设置setAccessible(true)来访问非public构造方法。


(未完待续)



往期回顾

九维团队-绿队(改进)| java 反射机制(上)

九维团队-绿队(改进)| java 反射机制(上)

九维团队-绿队(改进)| java 反射机制(上)

九维团队-绿队(改进)| java 反射机制(上)

九维团队-绿队(改进)| java 反射机制(上)

九维团队-绿队(改进)| java 反射机制(上)


关于安恒信息安全服务团队
安恒信息安全服务团队由九维安全能力专家构成,其职责分别为:红队持续突破、橙队擅于赋能、黄队致力建设、绿队跟踪改进、青队快速处置、蓝队实时防御,紫队不断优化、暗队专注情报和研究、白队运营管理,以体系化的安全人才及技术为客户赋能。

九维团队-绿队(改进)| java 反射机制(上)

九维团队-绿队(改进)| java 反射机制(上)

九维团队-绿队(改进)| java 反射机制(上)

原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| java 反射机制(上)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月18日01:18:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   九维团队-绿队(改进)| java 反射机制(上)http://cn-sec.com/archives/1740743.html

发表评论

匿名网友 填写信息