Java安全基础-反射机制

目录

[1. 什么是反射?](#1. 什么是反射?)

2.反射的核心操作

[2.1 反射组成相关的类](#2.1 反射组成相关的类)

[2.2 反射常见使用的方法](#2.2 反射常见使用的方法)

[2.3 如何获取Class对象?](#2.3 如何获取Class对象?)

[2.4 获取成员变量Field](#2.4 获取成员变量Field)

[2.5 获取成员方法Method](#2.5 获取成员方法Method)

[2.6 获取构造函数Constructor](#2.6 获取构造函数Constructor)

[2.7 反射创建类对象及调用方法](#2.7 反射创建类对象及调用方法)

[2.7.1 创建类对象](#2.7.1 创建类对象)

[2.7.2 调用实例方法](#2.7.2 调用实例方法)

[2.7.3 调用静态方法](#2.7.3 调用静态方法)

[2.8 利用反射构造Runtime类执行](#2.8 利用反射构造Runtime类执行)

[2.9 设置setAccessible(true)暴力访问权限](#2.9 设置setAccessible(true)暴力访问权限)


1. 什么是反射?

反射是一种动态机制,能够在程序运行时:

  • 动态获取类信息(属性、方法、构造器)
  • 突破封装性限制(访问私有成员)
  • 动态创建对象与调用方法

想象你在餐厅点餐,服务员递给你一本菜单。传统方式是根据菜单上的菜品直接点单,但反射就像你拿到菜单后,不仅能看到菜品名称,还能知道每道菜的原料、烹饪方法,甚至可以要求厨师按照你的方式调整口味。Java 反射机制赋予程序在运行时动态解析类结构的能力,让代码具备了类似 "透视" 的魔法。

2.反射的核心操作

2.1 反射组成相关的类

反射机制相关操作一般位于java.lang.reflect包中。

而java反射机制组成需要重点注意以下的类:

java.lang.Class:类对象;

java.lang.reflect.Constructor:类的构造器对象;

java.lang.reflect.Field:类的属性对象;

java.lang.reflect.Method:类的方法对象;

2.2 反射常见使用的方法

获取类的方法:forname

实例化类对象的方法:newInstance

获取函数的方法:getMethod

执行函数的方法:invoke

2.3 如何获取Class对象?

在反射中,我们想获取一个类或调用一个类的方法,需要先获取到该类的Class对象。而Class类构造器是私有的,所以只有JVM能够创建Class对象(了解),所以是无法通过创建对象的方式来获取class对象的,一般我们获取class对象就有以下三种方法:

java 复制代码
// 三种获取Class对象的方式
Class<?> clazz1 = String.class;          // 通过类名的属性class获取。
Class<?> clazz2 = "test".getClass();     // 实例化一个对象,之后在调用getClass()方法。
Class<?> clazz3 = Class.forName("java.util.Date"); // 调用Class类中的forName方法,将字节码文件加载进内存,返回Class对象。动态加载

而这三种获取Class类方式中,一般使用第三种通过Class.forName方法去动态加载类。且使用forName就不需要import导入其他类,可以加载我们任意的类。

getClass()方法,需要本身创建一个对象,就没有了使用反射机制意义。

类.class属性,需要导入类的包,依赖性太强,在大型项目中容易抛出编译错误;

2.4 获取成员变量Field

获取成员变量Field位于java.lang.reflect.Field包中

Field[] getFields() :获取所有public修饰的成员变量

Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符

Field getField(String name) 获取指定名称的 public修饰的成员变量

Field getDeclaredField(String name) 获取指定的成员变量

java 复制代码
package com.reflect;

import java.lang.reflect.Field;

public class FieldTest {
    public String name;
    public String profession;
    protected int age;
    private String number;
    char sex;

    public static void main(String[] args){
        try{

            Class c1 = Class.forName("com.reflect.FieldTest"); // 创建Class对象

            Field[] fieldArray1 = c1.getDeclaredFields(); //获取全部成员变量
            Field[] fieldArray2 = c1.getFields();// 获取全部public成员变量
            System.out.println("全部成员变量");
            for (Field field : fieldArray1){
                System.out.println(field.getName());
            }

            System.out.println("-------分割线---------");
            System.out.println("全部public成员变量");
            for (Field field : fieldArray2){
                System.out.println(field.getName());
            }
            System.out.println("-------分割线---------");

            System.out.println("获取指定名称的public修饰的成员变量");
            Field fieldArray3 = c1.getField("name"); // 获取指定名称的public修饰的成员变量
            System.out.println(fieldArray3.getName());
            System.out.println("-------分割线---------");

            System.out.println("获取指定的成员变量");
            Field fieldArray4 = c1.getDeclaredField("number"); // 获取指定的成员变量
            System.out.println(fieldArray4.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.5 获取成员方法Method

Method getMethod(String name, 类<?>... parameterTypes) //返回该类所声明的public方法

Method getDeclaredMethod(String name, 类<?>... parameterTypes) //返回该类所声明的所有方法

//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型

Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法

Method[] getDeclaredMethods() // 获取该类中的所有方法

java 复制代码
package com.reflect;

import java.lang.reflect.Method;

public class MethodTest {

    public void study(String s) {
        System.out.println("学习中..." + s);
    }

    protected void run() {
        System.out.println("跑步中...");
    }

    void eat() {
        System.out.println("吃饭中...");
    }

    private String sleep(int age) {
        System.out.println("睡眠中..." + age);
        return "sleep";
    }

    public static void main(String[] args) {
        try {
            Class c = Class.forName("com.reflect.MethodTest"); // 创建Class对象
            Method[] methods1 = c.getDeclaredMethods(); // 获取所有该类中的所有方法
            Method[] methods2 = c.getMethods(); // 获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法

            System.out.println("获取所有该类中的所有方法");
            for (Method m:methods1) {
                System.out.println(m);
            }

            System.out.println("-------分割线---------");
            System.out.println("获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法");

            for (Method m:methods2) {
                System.out.println(m);
            }

            System.out.println("-------分割线---------");
            System.out.println("获取study方法");
            Method methods3 = c.getMethod("study", String.class); // 获取study方法
            System.out.println(methods3);
            System.out.println("-------分割线---------");
            System.out.println("获取sleep方法");
            Method method4 = c.getDeclaredMethod("sleep", int.class); // 获取sleep方法
            System.out.println(method4);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.6 获取构造函数Constructor

Constructor<?>[] getConstructors() :只返回public构造函数

Constructor<?>[] getDeclaredConstructors() :返回所有构造函数

Constructor<> getConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的public构造函数

Constructor<> getDeclaredConstructor(类<?>... parameterTypes) : 匹配和参数配型相符的构造函数

java 复制代码
package com.reflect;

import java.lang.reflect.Constructor;
public class ConstructorTest {
    public ConstructorTest() {
        System.out.println("无参构造函数");
    }
    public ConstructorTest(String name) {
        System.out.println("有参构造函数" + name);
    }
    private ConstructorTest(boolean n) {
        System.out.println("私有构造函数");
    }
    public static void main(String[] args) {
        try {
            Class c1 = Class.forName("com.reflect.ConstructorTest");
            Constructor[] constructors1  = c1.getDeclaredConstructors();
            Constructor[] constructors2 = c1.getConstructors();
            System.out.println("只返回public构造函数");
            for (Constructor c : constructors1) {
                System.out.println(c);
            }
            System.out.println("-------分割线---------");
            System.out.println("返回所有构造函数");
            for (Constructor c : constructors2) {
                System.out.println(c);
            }
            System.out.println("-------分割线---------");
            System.out.println(" 匹配和参数配型相符的public构造函数");
            Constructor constructors3 = c1.getConstructor(String.class);
            System.out.println(constructors3);
            System.out.println("-------分割线---------");
            System.out.println(" 匹配和参数配型相符的构造函数");
            Constructor constructors4 = c1.getDeclaredConstructor(boolean.class);
            System.out.println(constructors4);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.7 反射的应用

在Java的反射机制中,java.lang.reflect.Method类的invoke()方法扮演着核心角色。它允许我们在运行时动态调用对象的方法,无论是实例方法还是静态方法。一般会和getMethod方法配合进行调用。

public Object invoke(Object obj, Object... args)

2.7 反射创建类对象及调用方法

2.7.1 创建类对象

一般我们使用Class对象的newInstance()方法来进行创建类对象。只需要通过forname方法获取到的class对象中进行newInstance方法创建即可。

java 复制代码
Class c = Class.forName("com.reflect.MethodTest"); // 创建Class对象
Object m1 =  c.newInstance(); // 创建类对象

2.7.2 调用实例方法

当需要调用一个对象的实例方法时,invoke()的第一个参数应传入该对象的实例。

java 复制代码
package com.reflect;

import java.lang.reflect.Method;

public class InvokeExample {
    public void printMessage(String message) {
        System.out.println("实例方法输出: " + message);
    }

    public static void main(String[] args) throws Exception {
        // 使用完整的类名(包含包路径)
        Class<?> clazz = Class.forName("com.reflect.InvokeExample");

        // 创建类的实例
        Object instance = clazz.newInstance();

        // 获取printMessage方法对象
        Method method = clazz.getMethod("printMessage", String.class);

        // 调用实例方法
        method.invoke(instance, "Hello, World!");
    }
}

2.7.3 调用静态方法

对于静态方法,invoke()的第一个参数应传入null,因为静态方法属于类本身,而非类的实例。

java 复制代码
package com.reflect;

import java.lang.reflect.Method;

public class InvokeExample {
    public static int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) throws Exception {

        // 获取MathUtils的Class对象
        Class<?> clazz = Class.forName("com.reflect.InvokeExample");
        // 获取add方法对象
        Method method = clazz.getMethod("add", int.class, int.class);
        // 调用静态方法(第一个参数为null)
        Object result = method.invoke(null, 5, 3);
        System.out.println("静态方法结果: " + result);
    }
}

2.8 利用反射构造Runtime类执行

平时漏洞测试中,如何使用java反射中如何获取Runtime类来命令执行测试弹出计算器。

尝试通过反射机制调用Runtime.getRuntime().exec()方法,执行系统命令打开系统的计算器应用。

java 复制代码
package com.reflect;

import java.lang.reflect.Method;

public class RuntimeTest {
    public static void main(String[] args) throws Exception {
        Class c1 = Class.forName("java.lang.Runtime");
        Object m = c1.newInstance();
    }
}

在Java反射机制中,当尝试通过Class.newInstance()直接实例化一个类时,若该类的构造函数被声明为private,则会抛出IllegalAccessException。原因是因为在Java标准库中,java.lang.Runtime类采用了经典的单例模式设计,其通过私有构造函数+静态工厂方法的组合,完美实现了对单例的管控。

1.私有构造函数Runtime类的构造函数被显式声明为private,禁止外部直接通过new关键字或反射创建实例。

2.静态工厂方法 :提供public static Runtime getRuntime()方法作为唯一获取实例的途径,确保全局唯一性。

所以通过反射获取getRuntime静态方法并调用,绕过了私有构造函数的限制。

java 复制代码
package com.reflect;

import java.lang.reflect.Method;

public class RuntimeDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("java.lang.Runtime");
        // 获取getRuntime方法(静态方法)
        Method getRuntime = clazz.getMethod("getRuntime");
        Object runtime = getRuntime.invoke(null);  // 静态方法传null

        // 调用exec方法
        Method exec = clazz.getMethod("exec", String.class);
        exec.invoke(runtime, "calc.exe");  // 弹出计算器(Windows)
    }
}

2.9 设置setAccessible(true)暴力访问权限

在Java反射体系中,java.lang.reflect.AccessibleObject类扮演着关键角色。作为FieldMethodConstructor类的共同基类,它提供了setAccessible()方法这一"特权通道",使得反射机制能够突破访问控制限制。

AccessibleObject

├─ Field(字段反射类)

├─ Method(方法反射类)

└─ Constructor(构造器反射类)

所有反射对象都继承自AccessibleObject,因此都拥有setAccessible()方法。

所有我们最开始的代码再修改简略下,就能成功命令执行了。

java 复制代码
package com.reflect;

import java.lang.reflect.Constructor;

public class RuntimeTest {
    public static void main(String[] args) throws Exception {
        Class c1= Class.forName("java.lang.Runtime");
        Constructor m = c1.getDeclaredConstructor();
        m.setAccessible(true);
        c1.getMethod("exec", String.class).invoke(m.newInstance(), "calc");
    }
}
相关推荐
白露与泡影7 分钟前
Java面试题及答案整理( 2025年 4 月最新版,持续更新)
java·开发语言
hunzi_119 分钟前
选择网上购物系统要看几方面?
java·微信小程序·小程序·uni-app·php
ChinaRainbowSea37 分钟前
1. 初始 RabbitMQ 消息队列
java·中间件·rabbitmq·java-rabbitmq
lmryBC491 小时前
golang接口-interface
java·前端·golang
ゞ 正在缓冲99%…1 小时前
leetcode75.颜色分类
java·数据结构·算法·排序
橘猫云计算机设计1 小时前
基于springboot的考研成绩查询系统(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·python·考研·django·毕业设计
时光呢1 小时前
JAVA常见的 JVM 参数及其典型默认值
java·开发语言·jvm
程序媛学姐1 小时前
SpringKafka错误处理:重试机制与死信队列
java·开发语言·spring·kafka
向阳2561 小时前
SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)
java·vue.js·spring boot·后端·sa-token·springboot·登录流程
XiaoLeisj2 小时前
【MyBatis】深入解析 MyBatis XML 开发:增删改查操作和方法命名规范、@Param 重命名参数、XML 返回自增主键方法
xml·java·数据库·spring boot·sql·intellij-idea·mybatis