Java代码审计-反射概述
好的,我现在需要帮用户总结一篇文章的内容,控制在100字以内,而且不需要特定的开头。首先,我得仔细阅读用户提供的文章内容。 文章主要讲的是Java的反射机制。反射允许在运行时访问类的信息,包括成员变量、方法和构造方法。文章详细介绍了如何获取Class对象的三种方式:Class.forName、类名.class和对象.getClass()。接着,文章讲解了如何通过反射获取构造方法、字段和成员方法,并展示了具体的代码示例。 然后,文章讨论了反射的综合应用,比如创建对象并打印属性值。最后,总结了反射的不安全性,包括破坏封装性、绕过访问控制、性能开销大、破坏模块系统以及可能导致安全漏洞等问题。 现在,我需要将这些内容浓缩到100字以内。重点应该放在反射的基本概念、功能以及其潜在的安全性和性能问题上。同时,要确保语言简洁明了,不使用复杂的术语。 首先,概述反射的作用:允许在运行时访问类的信息。然后提到获取Class对象的方式。接着说明如何操作构造方法、字段和方法。最后指出其安全性和性能问题。 整合起来应该是:Java反射机制允许在运行时访问类的信息,包括成员变量、方法和构造方法,并通过三种方式获取Class对象进行操作。然而,反射可能破坏封装性、绕过访问控制,并带来性能开销和安全风险。 </think> Java反射机制允许在运行时访问类的信息,包括成员变量、方法和构造方法,并通过三种方式获取Class对象进行操作。然而,反射可能破坏封装性、绕过访问控制,并带来性能开销和安全风险。 2025-12-24 07:59:29 Author: www.freebuf.com(查看原文) 阅读量:4 收藏

1.反射

顾名思义
反射允许对成员变量,成员方法和构造方法的信息进行编程访问
获取class对象
获取字段(成员变量)-》获取修饰符-》获取名字-》获取类型-》获取赋值/获取值
获取构造方法-》获取修饰符-》获取名字-》获取形参-》创建对象
获取成员方法。-》获取修饰符-》获取名字-》获取形参-》获取返回值-》抛出的异常-》获取注解-》运行方法

2.获取Class对象的三种方式

1.Class.forName("全类名");
2.类名.class
3.对象.getClass();
.java-.class(源代码阶段)-》是这个1.Class.forName("全类名");
A.class-》 内存加载阶段-》是这个2.类名.class
A a=new A();运行阶段-》是这个3.对象.getClass();
首先写好javaben配置环境
然后封装一下student类

public class student {
    private String name;
    private int age ;


    public student(){

    }

    public student(String name) {
        this.name = name;
    }

    protected student(int age){

        this.age = age;
    }

    private student(String name,int age){
        this.name =name;
        this.age = age;
    }
    /**
     * 获取
     * @return name
     */


    public String getName(){return name;}

    /**
     *
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     *
     *
     * @return  age
     */


    public int getAge() {
        return age;
    }
}

第一种方式
特点:最为常用的
全类名 :包名加类名

image.png

第二种方式
特点:一般更多的是当做参数进行传递

image.png

照样可以输出student类

第三种方式
特点:当我们已经有了这个类的对象是,才可以使用

image.png

3.反射获取Constructor构造方法

1.前言

在Java当中,万物皆对象
class中用于获取构造方法的方法
1.返回所有公共构造方法对象的数组
Constructor<?>[] getConstructors()
2.返回所有构造方法对象的数组
Constructor<?>[] getDeclaredConstructors()
3.返回单个公共构造方法对象
ConstructorgetConstructor(Class<?>... parameterTypes)
4.返回单个构造方法对象
ConstructorgetDeclaredConstructor(Class<?>... parameterTypes)
constructor类中用于创建对象的方法
newInstance(Object... initargs)
根据指定的构造方法创建对象
setAccessible(boolean flag)
设置为true,表示取消访问控制

2.获取公共(public)构造方法

1.获取class字节码文件对象
Class<?> clazz = Class.reflect("com.itheima.myreflect2.Student");
2.获取构造方法

Constructor[] cons = clazz.getConstructors();
for (Constructor con : cons){
    System.out.println(con);

}

控制台打印
public student() //反射空参数
public student(java.lang.String)//反射字符串
可以发现都获取到了public的构造方法

image.png

3.获取所有(all)构造方法

Class clazz =Class.forName("student");

 Constructor[] cons2 = clazz.getDeclaredConstructors();
        for (Constructor con : cons2) {
            System.out.println(cons2);
        }


运行,发现四个构造方法全部被反射到了

image.png

进行对比,发现确实是这样

image.png

不指定类型,获取空参构造

Constructor con1 = clazz.getDeclaredConstructor();
        System.out.println(con1);

image.png

指定类型,获取指定类型构造,当然getDeclaredConstructor方法是获取所有构造方法的,所有这里能够获取
到public方法

Constructor con2 = clazz.getDeclaredConstructor(String.class);
        System.out.println(con2);


image.png

如果尝试不用getDeclaredConstructor方法,我们来试一试获取一下Protected属性

Constructor con3 = clazz.getConstructor(int.class);
        System.out.println(con3);

image.png

可以看到程序报错了,在33行

我们使用getDeclaredConstructor()方法,发现就可以成功获取了

image.png

获取指定的两个参数a

Constructor con4 = clazz.getDeclaredConstructor(String.class,int.class);
        System.out.println(con4);

image.png

既然能够反射出对象的修饰符和类型,那么就能用一些其他方法来进行修饰
这样就能以整数的形式打印出

int modifiers = con4.getModifiers();
System.out.println(modifiers);

image.png

还可以获取参数,参数个数,参数类型

image.png

我们创建一个参数数组,将其打印出来

Parameter[] parameters = con4.getParameters();
        for (Parameter parameter : parameters){
            System.out.println(parameter);

image.png

student stu =(student) con4.newInstance("张三", 20);
        System.out.println(stu);

image.png

发现报错了,原来是修饰符是Private
发现getDeclaredConstructor()方法只能查看方法的权限,达不到修改方法的权限
在上面添加一个
setAccessible(true);//临时取消权限的校验

image.png

然后发现程序是不报错了,但是应该是版本兼容的问题导致不能显示出来
这种方式也称作暴力反射

4.反射获取字段(成员变量)Field

1.前言

class类中用于获取成员变量的方法
Field[] getFields()-》返回所有公共成员变量对象的数组
Field[] getDeclaredFields()-》返回所有成员变量对象的数组
Field getField(String name)-》返回单个公共成员变量对象
Field getDeclaredField(String name)-》返回单个成员变量对象
filed类中用于创建对象的方法
void set(Object obj, Object value--》赋值
Object get(Object obj)-》获取值

2.反射获取所有公共变量

Class clazz =Class.forName("Student");//获取class字节码文件的对象

Field[] fields = clazz.getFields();//获取公共成员变量


尝试用数组进行打印

for (Field field : fields){
            System.out.println(field);
        }


image.png

发现只包含了gender
查看student发现确实这样

image.png

3.反射获取所有变量

将getFields改为getDeclaredFields

Class clazz =Class.forName("Student");//获取class字节码文件的对象

        Field[] fields = clazz.getDeclaredFields();//获取所有成员变量

        for (Field field : fields){
            System.out.println(field);
        }


打印输出,发现全部都输出来了

image.png

4.反射获取单个成员变量

下列语句

Field gender = clazz.getField("gender");
        System.out.println(gender);


打印结果

image.png

尝试获取name

image.png

发现代码报错了,因为name是私有的,这里的方法获取不到,没有这个权限
改一下

Field gender = clazz.getDeclaredField("name");
        System.out.println(gender);

image.png

5.反射获取成员变量的修饰符

Field name = clazz.getDeclaredField("name");
        System.out.println(name);
    //  获取权限修饰符
        int modifiers = name.getModifiers();
        System.out.println(modifiers);

image.png

发现这是私有的2

String n = name.getName();
        System.out.println(n);

获取成员变量名

image.png

获取成员变量的数据类型

Class<?> type = name.getType();
        System.out.println(type);

image.png

获取成员变量记录的值

Student s = new Student("zhangsan",23,"男");
        name.setAccessible(true);
        Object value = name.get(s);
        System.out.println(value);

运行

image.png

修改对象里面记录的值

name.set(s,"lisi");
        System.out.println(s);

image.png

5.反射获取成员方法Method

1.前言

Class 类中用于获取成员方法的方法
Method[] getMethods()-》返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods()-》返回所有成员方法对象的数组,不包括继承的
Method getMethod(String name, Class<?>... parameterTypes)-》返回单个公共成员方法对象
Method getDeclaredMethod(String name, Class<?>... parameterTypes)-》返回单个成员方法对象
Method 类中用于创建对象的方法

image.png

示例代码

public class Student {
    private int age;
    private String name;


    public Student() {
    }


    public Student(String name, int age, String gender) {
        this.name = name;
        this.age = age;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    public int getAge() {
        return age;
    }


    public void setAge(int age) {
        this.age = age;
    }

    public void sleep(){
        System.out.println("睡觉");

    }
    private void eat(String something){
        System.out.println("在吃"+something);
    }
    public String toString() {
        return "Student{name='" + name + "', age=" + age + '}';
    }
}


2.获取class字节码文件对象

Class clazz =Class.forName("Student");//获取class字节码文件的对象

3.获取里面所有的方法对象

Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);

打印输出,可以看到很多方法,不过都是public修饰的,但是很多方法都没有我定义的

4.然后发现里面的方法其中也包括父类中包含的公共方法

image.png

5.不能获取父类的,但可以获取本类中私有的方法

改为DeclareMethods,可以看到方法少了很多

Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);

image.png

6.获取单个(指定)的成员方法

然后发现报错

image.png

发现这里的方法是private的,私有的,所以我们需要用获取所有成员方法

//获取指定的单一方法
Method m = clazz.getDeclaredMethod("eat", String.class);
        System.out.println(m);

image.png

7.获取方法的修饰符

int modifiers = m.getModifiers();
        System.out.println(modifiers);

image.png

打印结果发现是2,那么就证明是私有

8.获取方法的名字

String name = m.getName();
        System.out.println(name);

image.png

9.获取方法的形参

可以发现种类很多

image.png

Parameter[] parameters = m.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter);

可以发现是string类型的

image.png

10.获取方法抛出的异常

在eat这里我设置抛出了IO和NULL两个异常

image.png

Class[] exceptionTypes = m.getExceptionTypes();
        for (Class exceptionType : exceptionTypes) {
            System.out.println(exceptionType);

image.png

发现成功抛出两个设置的异常

11.方法运行

其中s表示方法的调用者。参数“汉堡包”表示在调用方法的时候传递的实际参数

Student s = new Student();
m.setAccessible(true);
m.invoke(s,"汉堡包");


image.png

12.方法返回值

image.png

Student s = new Student();
        m.setAccessible(true);
        Object result=m.invoke(s,"汉堡包");
        System.out.println(result);

运行发现

image.png

6.反射综合练习

1.前言

反射的作用

image.png

2.练习

image.png

私有的学生类

public class Student {
    private String name;
    private int age;
    private char gender;
    private double height;
    private String hobby;

    public Student(String name, int age, char gender, double height, String hobby) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.height = height;
        this.hobby = hobby;
    }

    // Getters for all fields
    public String getName() { return name; }
    public int getAge() { return age; }
    public char getGender() { return gender; }
    public double getHeight() { return height; }
    public String getHobby() { return hobby; }
}



私有的老师类

public class Teacher {
    private String name;
    private double salary;

    public Teacher(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    // Getters for all fields
    public String getName() { return name; }
    public double getSalary() { return salary; }
}



通过反射可以做到增删改查

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectionExample {

    public static void main(String[] args) throws Exception {
        // 创建 Student 对象并打印属性值
        Object studentObj = createAndPrintObject(Student.class, "小A", 23, '女', 167.5, "睡觉");

        // 创建 Teacher 对象并打印属性值
        Object teacherObj = createAndPrintObject(Teacher.class, "播妞", 10000.0);
    }

    public static Object createAndPrintObject(Class<?> clazz, Object... params) throws Exception {
        // 确定参数类型
        Class<?>[] paramTypes = new Class<?>[params.length];
        for (int i = 0; i < params.length; i++) {
            if (params[i] instanceof Integer) {
                paramTypes[i] = int.class;
            } else if (params[i] instanceof Character) {
                paramTypes[i] = char.class;
            } else if (params[i] instanceof Double) {
                paramTypes[i] = double.class;
            } else {
                paramTypes[i] = params[i].getClass();
            }
        }

        // 获取构造方法并创建对象
        Constructor<?> constructor = clazz.getConstructor(paramTypes);
        Object obj = constructor.newInstance(params);

        // 打印字段
        printFields(obj);

        return obj;
    }

    public static void printFields(Object obj) throws Exception {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            System.out.println(field.getName() + "=" + field.get(obj));
        }
    }
}


image.png

7.总结

反射这个java机制为啥不安全???

1. 破坏封装性

Java 的核心设计原则之一是封装性(Encapsulation),即类的内部实现细节应该对外隐藏。通过反射,可以访问类的私有字段和方法,绕过访问控制检查。
示例:
class Person {
private String name;
}

// 使用反射访问私有字段
Field field = Person.class.getDeclaredField("name");
field.setAccessible(true); // 绕过访问限制
Person p = new Person();
field.set(p, "Alice"); // 修改私有字段

影响:
• 破坏了类的设计意图。
• 可能导致对象状态被非法修改,引发不可预测的行为。

2. 绕过访问控制检查

Java 语言本身具有访问修饰符(如 private、protected、public)来控制代码的可访问性。但反射可以通过 setAccessible(true) 直接绕过这些限制。
示例:
Method method = MyClass.class.getDeclaredMethod("secretMethod");
method.setAccessible(true);
method.invoke(obj);

影响:
• 恶意代码可以调用本应受保护的方法。
• 安全敏感操作可能被非法执行。

3. 性能开销大

反射调用方法或访问字段比直接调用慢很多,因为 JVM 需要进行额外的安全检查和动态解析。
性能对比(大致):
操作 耗时
普通方法调用 1 ns
反射调用 100 - 500 ns
虽然现代 JVM 已经对反射进行了优化,但在高频调用场景下仍然存在显著性能损耗。

4. 破坏模块系统(Module System)

从 Java 9 开始引入了模块系统(JPMS),旨在限制外部代码访问 JDK 内部 API。然而,反射仍然可以通过 --add-opens 或 --add-exports 参数绕过这些限制。
示例:
java --add-opens java.base/java.lang=ALL-UNNAMED ...

影响:
• 导致 JDK 内部实现暴露给应用程序。
• 增加了维护和兼容性风险。

5. 可能导致安全漏洞

如果应用程序使用反射加载并执行任意类中的方法,可能会成为攻击入口。例如:
• 动态加载恶意类。
• 执行非预期的私有方法。
• 修改关键数据结构。
特别是在 Web 应用、插件系统、远程调用等场景中,反射如果不加以限制,容易成为攻击目标。

6. 编译期无法检测错误

反射操作是在运行时进行的,因此许多错误(如方法名拼写错误、参数类型不匹配)只有在运行时才会暴露出来。
示例:
Method method = MyClass.class.getMethod("nonExistentMethod"); // 编译通过
Object result = method.invoke(obj); // 运行时报错

影响:
• 增加调试难度。
• 降低代码健壮性。

安全问题 描述
破坏封装 可以访问私有成员,绕过访问控制
安全漏洞 恶意代码可通过反射执行危险操作
性能问题 反射调用效率低,影响性能
编译无报错 错误只能在运行时发现
模块限制失效 可绕过 JPMS 的模块隔离机制


文章来源: https://www.freebuf.com/articles/web/463601.html
如有侵权请联系:admin#unsafe.sh