# 20260814 课堂笔记 — 反射 Method 进阶:暴力反射调用私有方法与模拟 JUnit 单元测试框架 - **日期**:2026-08-14 - **项目**:`c260814` - **包路径**:`homework0813`(默认包根目录下的具名包) - **作者**:WanJL - **内容概览**:0813 作业进阶讲解——Student 实体类(回顾 4 种字段权限 + 4 个构造方法 + 私有实例方法 read / 私有静态方法 write + 新增 3 个 `test` 前缀测试方法 testAdd/testHello/testFail)+ MethodPrivateTest(**暴力反射调用私有实例方法 read——invoke 传对象、暴力反射调用私有静态方法 write——invoke 传 null**、封装通用调用工具 `invokeAnyMethod`——用 `instanceof Class` 判断"传的是 Class 则调静态方法、传的是对象则调实例方法"+ `args[i].getClass()` **动态计算参数类型数组**)+ MiniJUnitTest(**模拟 JUnit 单元测试框架**——`getConstructor().newInstance()` 反射创建被测对象 → `getDeclaredMethods()` 遍历全部方法 → 按 `test` 方法名前缀识别测试方法 → `setAccessible(true)` + `invoke()` 执行 → 捕获 `InvocationTargetException` 统计通过/失败个数,底层原理:注解识别 + 反射调用) --- ## 目录 1. [项目结构概述:0813 作业进阶——Method 反射的综合应用](#1-项目结构概述0813-作业进阶method-反射的综合应用) 2. [Student 实体类解析:反射目标回顾 + 新增测试方法](#2-student-实体类解析反射目标回顾--新增测试方法) 3. [MethodPrivateTest 演示:暴力反射调用私有方法(实例 + 静态)](#3-methodprivatetest-演示暴力反射调用私有方法实例--静态) 4. [暴力反射调用私有实例方法 read():invoke 传对象](#4-暴力反射调用私有实例方法-readinvoke-传对象) 5. [暴力反射调用私有静态方法 write():invoke 传 null](#5-暴力反射调用私有静态方法-writeinvoke-传-null) 6. [通用工具 invokeAnyMethod:instanceof 判断静态/实例 + 动态计算参数类型](#6-通用工具-invokeanymethodinstanceof-判断静态实例--动态计算参数类型) 7. [MiniJUnitTest 演示:模拟 JUnit 单元测试框架(反射终极应用)](#7-minijunittest-演示模拟-junit-单元测试框架反射终极应用) 8. [反射创建被测对象:getConstructor().newInstance()](#8-反射创建被测对象getconstructornewinstance) 9. [getDeclaredMethods() 遍历 + test 前缀识别测试方法](#9-getdeclaredmethods-遍历--test-前缀识别测试方法) 10. [setAccessible(true) + invoke 执行测试方法](#10-setaccessibletrue--invoke-执行测试方法) 11. [InvocationTargetException 包装异常:getCause() 获取真实异常](#11-invocationtargetexception-包装异常getcause-获取真实异常) 12. [通过/失败统计:模拟测试报告输出](#12-通过失败统计模拟测试报告输出) 13. [知识点全景总结](#13-知识点全景总结) 14. [随堂练习要点](#14-随堂练习要点) 15. [拓展阅读](#15-拓展阅读) --- ## 1. 项目结构概述:0813 作业进阶——Method 反射的综合应用 ### 概念 —— 一个包内三个类,把上一课的 Method 反射学以致用 `c260814` 项目把上一课(08-13 反射 Method)的知识点**落到两个实际场景**:`MethodPrivateTest` 演示**暴力反射调用私有方法**(并把调用过程封装成通用工具),`MiniJUnitTest` 演示**用反射模拟 JUnit 单元测试框架**(遍历方法 → 识别测试方法 → 反射执行 → 统计结果)。`Student` 实体类既是两个演示的反射目标,又新增了 3 个 `test` 前缀方法专门供 MiniJUnit 扫描执行。 ```text c260814/ └── src/ └── homework0813/ ← 包名 homework0813(0813 作业的进阶应用) ├── Student.java ← 反射目标实体类(字段/构造/私有方法 + 测试方法) ├── MethodPrivateTest.java ← 暴力反射调用私有方法 + 通用调用工具 invokeAnyMethod └── MiniJUnitTest.java ← 模拟 JUnit 单元测试框架(测试方法扫描/执行/统计) ``` > **要点**: > - **项目类型是"每日默认包 + 具名包"**:包名为 `homework0813`——表示这是 08-13 课后作业的进阶练习,三个类在同一个包下,相互可以直接引用(无需 import 同包类) > - **三个类各司其职**:Student 提供"被测对象与测试方法",MethodPrivateTest 演示"私有方法怎么反射调用",MiniJUnitTest 演示"测试框架怎么用反射批量执行测试" > - **本课是反射 Method 的收尾应用**:上一课学了 API(getDeclaredMethod/invoke/setAccessible),本课把 API 组合成"能干活"的工具和框架——**反射的价值在于组合应用** --- ## 2. Student 实体类解析:反射目标回顾 + 新增测试方法 ### 概念 —— 一个同时满足"私有方法反射"与"测试框架反射"的实体类 `Student` 类是三个演示的共用反射目标,字段/构造方法与上一课(08-13)基本一致(private/public/public static 三种权限字段 + 4 个构造方法 + 私有实例方法 read / 私有静态方法 write + 公开方法 study),**新增了 3 个 `test` 开头的方法**——它们是 MiniJUnit 框架扫描并执行的"模拟测试用例"。 ```java // 来源:homework0813/Student.java public class Student { private Integer id; private String name; private Integer age; private String gender; public String email; public String phone; public static String school; public Student() { } // public 无参构造(MiniJUnit 反射创建对象用) private Student(String name, Integer id) { } // private 二参构造(上一课暴力反射目标) public Student(Integer id, String name, Integer age, String gender) { } // public 四参构造 public Student(Integer id, String name, Integer age, String gender, String email, String phone) { } // public 六参构造 // …… 7 组 getter/setter(id/name/age/gender/email/phone 实例 + school 静态)…… private void read(){ // private 实例方法:输出 name+"读......." System.out.println(this.name+"读......."); } private static void write(){ // private 静态方法:输出"静态方法...写..............." System.out.println("静态方法...写..............."); } public void study(){ // public 实例方法:输出 name+"在学习....." System.out.println(name+"在学习....."); } // 新增 3 个 test 前缀方法:MiniJUnit 扫描识别的"模拟测试用例" private void testAdd() { // 测试用例①:加法计算 System.out.println("测试:加法计算"); int a = 1 + 1; if (a != 2) throw new RuntimeException("加法计算错误"); } private void testHello() { // 测试用例②:打招呼 System.out.println("测试:打招呼 " + this.name); } private void testFail() { // 测试用例③:故意失败 throw new RuntimeException("故意失败的方法"); } } ``` ### 新增测试方法对照表 | 测试方法 | 权限 | 行为 | 执行结果 | |----------|------|------|----------| | `testAdd()` | private | 输出"测试:加法计算",计算 `1+1`,若不等于 2 抛异常 | **通过**(`1+1==2`,不抛异常) | | `testHello()` | private | 输出"测试:打招呼 " + name | **通过**(只是打印,不抛异常) | | `testFail()` | private | 直接 `throw new RuntimeException("故意失败的方法")` | **失败**(抛异常 → invoke 包装成 InvocationTargetException) | ### 字段/方法权限设计对照表(回顾上一课) | 成员 | 权限 | 作用 / 对应演示 | |------|------|----------------| | `private Integer id` | private | 私有字段——Field 反射 `getDeclaredFields()` 能拿到 | | `private String name` | private | 私有字段——暴力反射 Field 读取/修改目标;testHello 打印 name | | `private Integer age` | private | 私有字段 | | `private String gender` | private | 私有字段 | | `public String email` | public | 公有字段——`getField` + `get/set` 读取/修改 | | `public String phone` | public | 公有字段 | | `public static String school` | **public + static** | 静态字段——`get(null)` / `set(null, 值)` | | `read()` | **private 实例方法** | 暴力反射 Method 调用目标——`getDeclaredMethod("read")` + `setAccessible(true)` + `invoke(对象)` | | `write()` | **private 静态方法** | 暴力反射 Method 调用目标——`getDeclaredMethod("write")` + `setAccessible(true)` + `invoke(null)` | | `study()` | public 实例方法 | 公开方法——普通/反射均可调用 | | `testAdd/testHello/testFail` | **private 测试方法** | MiniJUnit 扫描执行目标——`getDeclaredMethods()` 遍历 + test 前缀识别 | > **要点**: > - **私有方法不写 getter/setter**:`read()`/`write()`/`testXxx` 都是 private 且无公开入口,**普通代码无法调用**——只有反射(暴力反射)能调用它们,这正是本课"为什么需要反射"的典型场景 > - **测试方法刻意设计"两成一败"**:`testAdd` 通过、`testHello` 通过、`testFail` 故意抛异常——这样 MiniJUnit 统计结果时能演示"成功 2 个、失败 1 个" > - **`testFail` 抛的是 RuntimeException**:被调方法内部抛异常时,反射 invoke 不会直接抛 RuntimeException,而是**包装成 InvocationTargetException 抛出**——这是本课重点(见第 11 节) --- ## 3. MethodPrivateTest 演示:暴力反射调用私有方法(实例 + 静态) ### 概念 —— 一个 main 方法三个任务,私有方法反射的"全家福" `MethodPrivateTest` 的 main 方法演示了暴力反射调用私有方法的**三种完整路径**:调用私有**实例**方法(invoke 传对象)、调用私有**静态**方法(invoke 传 null)、以及用**封装好的通用工具**统一调用(自动判断实例/静态)。 ```java // 来源:homework0813/MethodPrivateTest.java public class MethodPrivateTest { public static void main(String[] args) throws Exception { // 1. 创建 Student 对象,反射调用 setName("张三")(复用练习六工具) Student student = new Student(101, "张三", 25, "男", "zhangsan@qq.com", "13636363366"); // 2. 暴力反射调用私有实例方法 read()(invoke 传对象 s) // 3. 暴力反射调用私有静态方法 write()(invoke 传 null) // 4. 用封装好的 invokeAnyMethod 工具调用 read(obj 传 s)/ write(obj 传 null) invokeAnyMethod(student, "read"); invokeAnyMethod(student.getClass(), "write"); } // …… invokeAnyMethod 通用工具见第 6 节 …… } ``` ### main 方法执行流程 ``` new Student(101, "张三", 25, "男", "zhangsan@qq.com", "13636363366") ↓ invokeAnyMethod(student, "read") ← obj 传 Student 对象 → 调用私有实例方法 read(),输出"张三读......." ↓ invokeAnyMethod(student.getClass(), "write") ← obj 传 Class 对象 → 调用私有静态方法 write(),输出"静态方法...写..............." ``` | 代码 | 作用 | 说明 | |------|------|------| | `new Student(101, "张三", ...)` | 创建 Student 对象 | 六参构造(上一课已学),name 赋值为"张三" | | `invokeAnyMethod(student, "read")` | 调私有**实例**方法 read | obj 传的是 **Student 对象** → 通用工具判定为实例方法,invoke 第一参数传对象 | | `invokeAnyMethod(student.getClass(), "write")` | 调私有**静态**方法 write | obj 传的是 **Class 对象** → 通用工具判定为静态方法,invoke 第一参数传 null | > **要点**: > - **main 方法没有直接写 getDeclaredMethod 三件套**——注释 2、3 是把"标准写法"留作对照,实际执行走第 4 步的**封装工具**:同样能调私有方法,但代码更简洁(一行调一个) > - **instanceof 判断的关键区别**:同一个工具方法,**传对象 → 实例方法,传 Class → 静态方法**——用 `student.getClass()` 传 Class 对象正是"主动告知工具:我要调静态方法" > - **main throws Exception**:`throws Exception` 一网打尽所有受检异常(NoSuchMethodException / IllegalAccessException / InvocationTargetException)——演示代码的偷懒写法,实际开发应精确处理 --- ## 4. 暴力反射调用私有实例方法 read():invoke 传对象 ### 概念 —— 标准写法:getDeclaredMethod + setAccessible(true) + invoke(对象) 注释 2 要求用**标准三件套**暴力反射调用私有实例方法 `read()`——`read()` 是 private 实例方法,必须 `getDeclaredMethod("read")` 拿到方法对象(getMethod 拿不到 private)→ `setAccessible(true)` 放开权限 → `invoke(对象)` 传调用该方法的对象: ```java // 来源:homework0813/MethodPrivateTest.java(注释 2 的标准写法,main 实际用封装工具等价实现) // 2. 暴力反射调用私有实例方法 read()(invoke 传对象 s) Class clazz = Student.class; Method readMethod = clazz.getDeclaredMethod("read"); // ① getDeclaredMethod 拿到 private 方法 readMethod.setAccessible(true); // ② 暴力反射:临时取消访问权限检查 Object result = readMethod.invoke(student); // ③ invoke 第一参数传"调用该方法的对象" student ``` ### 三件套拆解 | 步骤 | 代码 | 作用 | 说明 | |------|------|------|------| | ① | `clazz.getDeclaredMethod("read")` | 获取私有方法对象 | `read()` 无参,`getDeclaredMethod("read")` 即可;**getMethod 拿不到 private**,必须用 Declared 系列 | | ② | `readMethod.setAccessible(true)` | 暴力反射:临时取消访问权限检查 | 不调用则 invoke 私有方法抛 `IllegalAccessException` | | ③ | `readMethod.invoke(student)` | 调用私有实例方法 | **第一参数传对象 student**——实例方法属于对象,必须指明"调用哪个对象的方法";read() 内部用 this.name,输出"张三读......." | > **要点**: > - **私有实例方法三件套**:`getDeclaredMethod`(拿 private)+ `setAccessible(true)`(放开权限)+ `invoke(对象)`(传调用对象)——与 Field/Constructor 的暴力反射套路完全一致 > - **invoke 第一参数 = 调用方法的对象**:read() 是实例方法,内部用了 `this.name`,所以必须传 student 对象;**传错对象或传 null 会抛 IllegalArgumentException 或 NullPointerException** > - **调用私有方法 = 破坏封装**:read() 本来没有任何公开入口,反射硬生生调用成功了——这正是"反射破坏封装性"的体现,也是测试框架能测私有方法的原理 --- ## 5. 暴力反射调用私有静态方法 write():invoke 传 null ### 概念 —— 静态方法不属于对象,invoke 第一参数传 null 注释 3 要求暴力反射调用私有**静态**方法 `write()`——静态方法属于整个类、不属于任何对象,所以 `invoke` 第一参数**不传对象、传 null**(与 Field 操作静态属性传 null 规律统一): ```java // 来源:homework0813/MethodPrivateTest.java(注释 3 的标准写法) // 3. 暴力反射调用私有静态方法 write()(invoke 传 null) Method writeMethod = clazz.getDeclaredMethod("write"); // ① getDeclaredMethod 拿到 private 静态方法 writeMethod.setAccessible(true); // ② 暴力反射:临时取消访问权限检查 Object result = writeMethod.invoke(null); // ③ 静态方法 invoke 第一参数传 null ``` ### 私有实例方法 vs 私有静态方法对比 | 对比 | 私有实例方法 read() | 私有静态方法 write() | |------|---------------------|----------------------| | 权限 | private | private | | 是否 static | 否(实例方法) | 是(静态方法) | | 获取方式 | `getDeclaredMethod("read")` | `getDeclaredMethod("write")` | | 是否要 setAccessible(true) | **要**(private 必须暴力反射) | **要**(private 必须暴力反射) | | invoke 第一参数 | **传对象** `invoke(student)` | **传 null** `invoke(null)` | | 输出 | `张三读.......` | `静态方法...写...............` | > **要点**: > - **invoke 第一参数规律(再次强调)**:**实例方法传对象、静态方法传 null**——与 Field 操作静态属性传 null 是同一规律:静态成员属于整个类,不属于任何对象 > - **private 和 static 是两个独立维度**:write() 既 private 又 static——**private 决定了必须 getDeclaredMethod + setAccessible(true),static 决定了 invoke 传 null**,两件事都要做 > - **write() 内部不依赖任何对象状态**:它只打印固定字符串,所以 invoke(null) 也能正常执行——如果静态方法内部访问了实例字段反而会出错(静态上下文不能直接访问实例成员) --- ## 6. 通用工具 invokeAnyMethod:instanceof 判断静态/实例 + 动态计算参数类型 ### 概念 —— 把暴力反射封装成"一行调用任意方法"的通用工具 第 4、5 节的标准写法每次都要写三件套(getDeclaredMethod + setAccessible + invoke),非常啰嗦。`invokeAnyMethod` 把整个过程**封装成一个通用工具**:只要传入"目标对象(或 Class)+ 方法名 + 参数",它自动完成"计算参数类型 → 获取方法(含 private)→ 放开权限 → 调用"四步。**核心技巧有两个:`instanceof Class` 判断是静态还是实例方法、`args[i].getClass()` 动态计算参数类型数组**。 ```java // 来源:homework0813/MethodPrivateTest.java // 通用工具:反射调用任意权限方法(getDeclaredMethod + setAccessible + invoke) public static Object invokeAnyMethod(Object obj, String methodName, Object... args) throws Exception { // 1. 计算参数类型数组 Class[] argsClasses = new Class[args.length]; for (int i = 0; i < args.length; i++) { argsClasses[i] = args[i].getClass(); } // 判断,obj 到底是什么类型,如果 obj 属于 Class 类型,无论是哪个类的 Class 对象,都会进入到 if 语句中 if (obj instanceof Class) { // 强转,把 obj 转换成 Class 类型 Class clazz = (Class) obj; // 2. getDeclaredMethod(methodName, 参数类型...) 获取方法(含 private) Method method = clazz.getDeclaredMethod(methodName, argsClasses); // 3. setAccessible(true) 暴力反射放开权限 method.setAccessible(true); // 4. invoke(null, args) 调用并返回结果 —— 静态方法第一参数传 null return method.invoke(null, args); } // 如果 obj 不是 Class 类型,说明传入的是其他的对象,要调用的也是实例方法,而不是静态方法 Class clazz = obj.getClass(); // 2. getDeclaredMethod(methodName, 参数类型...) 获取方法(含 private) Method method = clazz.getDeclaredMethod(methodName, argsClasses); // 3. setAccessible(true) 暴力反射放开权限 method.setAccessible(true); // 4. invoke(obj, args) 调用并返回结果 —— 实例方法第一参数传对象 return method.invoke(obj, args); } ``` ### 工具执行流程 ``` invokeAnyMethod(student, "read") invokeAnyMethod(student.getClass(), "write") ↓ ↓ args 为空 → argsClasses 为空数组 args 为空 → argsClasses 为空数组 ↓ ↓ obj instanceof Class ? 否 obj instanceof Class ? 是(student.getClass() 是 Class 对象) ↓ ↓ clazz = obj.getClass() = Student.class clazz = (Class) obj = Student.class ↓ ↓ getDeclaredMethod("read", 无参) getDeclaredMethod("write", 无参) ↓ ↓ setAccessible(true) setAccessible(true) ↓ ↓ invoke(student, ) ← 实例方法传对象 invoke(null, ) ← 静态方法传 null ``` ### 两个核心技巧拆解 | 技巧 | 代码 | 作用 | 说明 | |------|------|------|------| | **① 动态计算参数类型** | `argsClasses[i] = args[i].getClass()` | 根据实际传入的参数**动态推导**参数类型数组 | 不写死 `String.class` 等——传什么参数就自动得到什么类型,方法有重载也能精确定位 | | **② instanceof 判断实例/静态** | `obj instanceof Class` | 判断 obj 是 Class 对象还是普通对象 | 传 `student.getClass()` 说明要调**静态方法**;传普通对象说明要调**实例方法** | | 静态方法分支 | `invoke(null, args)` | 静态方法调用 | obj 是 Class → 方法属于整个类 → invoke 传 null | | 实例方法分支 | `invoke(obj, args)` | 实例方法调用 | obj 是普通对象 → 方法属于该对象 → invoke 传 obj | ### 为什么用 `instanceof Class` 判断? `student.getClass()` 返回的是 `Class` 类型对象——它既是"用来拿方法元数据的类描述",又被当作"要调静态方法的信号"。工具里用 `obj instanceof Class` 判断:**obj 是 Class 类型 → 传进来的是"类",调静态方法;obj 不是 Class 类型 → 传进来的是"对象",调实例方法**。注释里专门解释:"如果 obj 属于 Class 类型,无论是哪个类的 Class 对象,都会进入到 if 语句中"——所以传 `student.getClass()` 和传 `Student.class` 效果一样。 > **要点**: > - **`args[i].getClass()` 动态计算参数类型**:上一课练习六的经典技巧——通用工具不能写死参数类型,**先根据实际参数用 `getClass()` 算出参数类型数组**,再传给 `getDeclaredMethod(methodName, argsClasses)`;`args` 是可变参数(`Object...`),长度为 0 时得到空数组 > - **`instanceof Class` 是"静态方法开关"**:`Class` 类型的 obj 走静态分支(invoke(null)),普通对象走实例分支(invoke(obj))——同一个工具两种用法,判断依据就是 obj 是不是 Class > - **两个分支代码几乎一样**:都是"getDeclaredMethod + setAccessible + invoke",**只有 invoke 第一参数不同**(null vs obj)——这也再次说明实例/静态方法的唯一差别就在"要不要传对象" > - **返回值 Object**:`invoke` 返回方法执行结果(Object 类型),工具把结果 return 给调用者——本课两个方法都是 void,所以返回 null,但工具设计上支持有返回值的方法 --- ## 7. MiniJUnitTest 演示:模拟 JUnit 单元测试框架(反射终极应用) ### 概念 —— 测试框架的底层原理:遍历方法 + 前缀识别 + 反射执行 JUnit 等测试框架的核心原理就是反射:**拿到类 → 遍历全部方法 → 找出测试方法 → 反射执行并统计结果**。真实 JUnit 用 `@Test` **注解**标记测试方法,MiniJUnit 用**方法名前缀 "test"** 识别——原理完全一致。整个框架只有 6 步: ```java // 来源:homework0813/MiniJUnitTest.java public class MiniJUnitTest { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { // 1. 反射创建被测 Student 对象(getConstructor().newInstance()) Class studentClass = Student.class; Student student = studentClass.getConstructor().newInstance(); // 2. getDeclaredMethods() 遍历 Student 全部方法 Method[] declaredMethods = studentClass.getDeclaredMethods(); // 3. 方法名以 "test" 开头 → 认定为测试方法 int count = 0, a = 0; for (Method m : declaredMethods) { String methodName = m.getName(); if (methodName.startsWith("test")) { // 4. 对每个测试方法:setAccessible(true) + invoke(obj) 执行 m.setAccessible(true); try { m.invoke(student); // 5. 捕获 InvocationTargetException → getCause() 打印真实异常 a++; // 执行成功 → 通过数 +1 } catch (IllegalAccessException e) { count++; // 权限异常 → 失败数 +1 } catch (InvocationTargetException e) { count++; // 被调方法内部抛异常(包装) → 失败数 +1 } } } // 6. 统计通过/失败个数 System.out.println("失败个数:" + count); System.out.println("成功个数:" + a); } } ``` ### 预期输出 ``` 测试:加法计算 测试:打招呼 null 失败个数:1 成功个数:2 ``` > 说明:`testAdd` 通过(输出"测试:加法计算")、`testHello` 通过(输出"测试:打招呼 null"——用无参构造创建对象,name 为 null)、`testFail` 故意抛异常判失败——所以统计"失败 1 个、成功 2 个"。 ### MiniJUnit 框架六步流程 ``` Student.class ← ① 拿 Class 对象(类名.class) ↓ getConstructor().newInstance() ← ② 反射创建被测对象(无参构造) ↓ getDeclaredMethods() ← ③ 遍历本类全部方法(含 private) ↓ methodName.startsWith("test") ← ④ 按前缀识别测试方法(真实 JUnit 用 @Test 注解) ↓ setAccessible(true) + invoke(student) ← ⑤ 暴力反射执行测试方法 ↓ 成功 a++ / 失败 count++ ← ⑥ 统计通过/失败个数 ``` > **要点**: > - **MiniJUnit 就是简化版 JUnit**:真实 JUnit 的 `@Test` 注解识别 → 底层就是"遍历方法 + 判断是否测试方法";MiniJUnit 用 `startsWith("test")` 替代注解,**原理一模一样** > - **测试方法都是 private**:MiniJUnit 能执行 private 的 testXxx 方法,靠的就是 `setAccessible(true)` 暴力反射——**测试框架破坏封装访问私有方法**是反射破坏封装性的典型正面应用 > - **main 抛出 4 个受检异常**:`NoSuchMethodException`(getConstructor 找不到无参构造)、`InvocationTargetException`(被调方法抛异常)、`InstantiationException`(实例化失败)、`IllegalAccessException`(权限问题)——反射常用受检异常的"全家福" --- ## 8. 反射创建被测对象:getConstructor().newInstance() ### 概念 —— 不知道类也能创建对象,靠 Class + Constructor 框架要测试一个类,**编译期并不知道被测类是谁**(可能是用户后来写的)——所以不能 `new`,必须反射创建对象:`getConstructor()` 拿到**无参构造方法**,再 `.newInstance()` 创建实例: ```java // 来源:homework0813/MiniJUnitTest.java(第 1 步) Class studentClass = Student.class; // ① 拿 Class 对象 Student student = studentClass.getConstructor().newInstance(); // ② getConstructor() 拿无参构造 → newInstance() 创建对象 ``` ### 代码拆解 | 代码 | 作用 | 说明 | |------|------|------| | `Student.class` | 获取 Student 的 Class 对象 | 方式一:类名.class(上一课:三种获取 Class 对象的方式) | | `studentClass.getConstructor()` | 获取**public 无参构造方法**对象 | 不传参数 → 匹配无参构造;Student 有无参构造 `public Student() {}` | | `.newInstance()` | **反射创建对象** | 等价于 `new Student()`——但运行时才知道创建的是哪个类 | > **要点**: > - **`getConstructor()` 拿的是 public 无参构造**:如果不传参数类型,默认找无参构造;Student 定义了无参构造,所以能成功——**如果类没有无参构造,这里会抛 NoSuchMethodException** > - **为什么用无参构造**:测试框架不知道被测类有哪些字段、怎么传参,所以**统一用无参构造创建对象**(对象字段都是默认值 null/0),再通过 setter 或反射赋测试数据 > - **上一条的连锁反应**:无参构造创建的对象 name 是 null → 执行 `testHello()` 输出"测试:打招呼 null"——这正是"字段默认值"的体现 > - **Constructor.newInstance() vs Class.newInstance()**:JDK9+ 推荐用 `Constructor.newInstance()`(可访问 private 构造、可传参),本课用 `getConstructor()` 系列(上一课 08-12 已学) --- ## 9. getDeclaredMethods() 遍历 + test 前缀识别测试方法 ### 概念 —— 遍历全部方法,用命名约定找出"测试方法" 测试框架的核心问题:**怎么知道哪些方法是测试方法?** 真实 JUnit 用 `@Test` 注解,MiniJUnit 用**命名约定**——方法名以 `test` 开头的就当作测试方法。遍历用 `getDeclaredMethods()`(拿本类全部方法,含 private): ```java // 来源:homework0813/MiniJUnitTest.java(第 2~3 步) Method[] declaredMethods = studentClass.getDeclaredMethods(); // ① 遍历本类全部方法(含 private) int count = 0, a = 0; // ② 失败计数器 count、成功计数器 a for (Method m : declaredMethods) { String methodName = m.getName(); if (methodName.startsWith("test")) { // ③ 方法名以 "test" 开头 → 认定为测试方法 // …… 执行测试(见第 10 节)…… } } ``` ### 代码拆解 | 代码 | 作用 | 说明 | |------|------|------| | `studentClass.getDeclaredMethods()` | 获取**本类声明的全部方法**数组 | 含 private(testXxx 都是 private),不含父类 Object 的方法 | | `m.getName()` | 获取方法名 | 如 `testAdd` / `testHello` / `testFail` / `getName` / `read` 等 | | `methodName.startsWith("test")` | 判断方法名是否以 test 开头 | **命名约定识别测试方法**——true 才进入执行逻辑 | | `int count = 0, a = 0` | 失败/成功计数器 | count 记失败、a 记成功(`a` 是 success 简写) | ### 为什么能遍历到 private 的 testXxx? `testAdd`/`testHello`/`testFail` 都是 **private** 方法——普通代码在类外部根本看不到它们。但 `getDeclaredMethods()` **包含本类声明的全部方法(含 private)**,所以反射能拿到这 3 个测试方法。这正是测试框架能测试"类内部私有逻辑"的基础:**私有方法无法从外部调用,但反射遍历 + 暴力反射可以**。 > **要点**: > - **命名约定 vs 注解**:JUnit 早期版本(JUnit 3)就是用"方法名以 test 开头"识别测试方法,后来 JUnit 4 才改用 `@Test` 注解——MiniJUnit 模拟的是**最古老的 JUnit 3 设计**,更直观地展示"框架如何识别测试方法" > - **getDeclaredMethods 不含父类方法**:只遍历 Student 本类声明的方法(含 3 个 testXxx),不会把 Object 的 toString/hashCode 等也遍历出来 > - **if 判断是过滤条件**:多个方法里只有 3 个 test 开头的方法进入执行分支——这就是"扫描 + 筛选"的框架思路 --- ## 10. setAccessible(true) + invoke 执行测试方法 ### 概念 —— 暴力反射执行 private 测试方法,异常决定成败 识别出测试方法后,执行也是**暴力反射三件套**:`setAccessible(true)` 放开权限 → `invoke(student)` 执行。**成败判定靠异常**:`invoke` 不抛异常 = 测试通过(a++);抛异常 = 测试失败(count++): ```java // 来源:homework0813/MiniJUnitTest.java(第 4~5 步) for (Method m : declaredMethods) { String methodName = m.getName(); if (methodName.startsWith("test")) { m.setAccessible(true); // ① 暴力反射:放开 private 测试方法的权限 try { m.invoke(student); // ② 执行测试方法(invoke 传被测对象) a++; // ③ 不抛异常 → 成功 +1 } catch (IllegalAccessException e) { count++; // ④ 权限异常 → 失败 +1 } catch (InvocationTargetException e) { count++; // ⑤ 被调方法内部抛异常(包装) → 失败 +1 } } } ``` ### 三种情况对照 | 测试方法 | invoke 结果 | 进入哪个分支 | 计数器 | |----------|-------------|--------------|--------| | `testAdd()` | 正常执行(1+1==2 不抛异常) | try 成功 | 成功 a++ | | `testHello()` | 正常执行(只是打印) | try 成功 | 成功 a++ | | `testFail()` | 内部 `throw new RuntimeException` → invoke **抛 InvocationTargetException** | catch InvocationTargetException | 失败 count++ | > **要点**: > - **测试框架的"断言"哲学**:测试方法内部写断言(if 条件不满足就 throw),invoke 执行时一旦抛异常就被捕获并判为失败——**"不抛异常 = 通过"是测试框架最基本的判定规则** > - **为什么要捕获异常而不是让程序崩溃**:任何一个测试失败都不应该中断整个测试套件——所以 try-catch 包住 invoke,**单个测试失败只计一次失败,继续执行下一个测试** > - **IllegalAccessException 也被计入失败**:理论上 setAccessible(true) 后不会抛权限异常,但防御性编程把两种异常都捕获计入失败——保证任何异常都影响统计而不影响流程 > - **invoke 第一参数传被测对象**:`m.invoke(student)` 的 student 是第 8 节反射创建的对象——测试方法都是实例方法,必须传对象 --- ## 11. InvocationTargetException 包装异常:getCause() 获取真实异常 ### 概念 —— 被调方法内部抛异常时,invoke 会"包装"而不是"直接抛" 这是反射调用与普通调用最隐蔽的差异:**普通代码 `s.testFail()` 会直接抛 RuntimeException;但反射 `invoke` 调用时,被调方法内部抛的异常会被包装成 `InvocationTargetException` 抛出**。因为 invoke 是"反射机制"调用的方法,它在被调方法外面包了一层——调用者必须通过 `e.getCause()` 才能拿到真正的异常。 ```java // 来源:homework0813/MiniJUnitTest.java(第 5 步:InvocationTargetException 捕获) try { m.invoke(student); a++; // 不抛异常 → 成功 } catch (IllegalAccessException e) { count++; // 权限异常 → 失败 } catch (InvocationTargetException e) { count++; // 被调方法内部抛异常(InvocationTargetException 包装) → 失败 // 如果要看真实异常:e.getCause() 返回真正的 RuntimeException("故意失败的方法") } ``` ### 异常包装流程 ``` testFail() 内部 throw new RuntimeException("故意失败的方法") ↓ m.invoke(student) 执行 testFail() ↓ 被调方法抛异常 → 反射机制把异常"包了一层" ↓ invoke 抛出 InvocationTargetException(不是直接抛 RuntimeException) ↓ catch (InvocationTargetException e) 捕获 → count++ 记为失败 ↓ 真实异常:e.getCause() ← 拿到真正的 RuntimeException("故意失败的方法") ``` | 对比 | 普通调用 | 反射调用(invoke) | |------|----------|---------------------| | 调用方式 | `student.testFail()` | `m.invoke(student)` | | 被调方法抛异常时 | **直接抛 RuntimeException** | **抛 InvocationTargetException(包装)** | | 如何拿到真实异常 | 直接 catch RuntimeException | `catch (InvocationTargetException e)` 后 `e.getCause()` | | 为什么不直接抛 | — | invoke 是反射机制,被调方法外有"包装层" | > **要点**: > - **InvocationTargetException 是"包装异常"**:它的名字直译是"调用目标异常"——被调目标(testFail)抛了异常,被 invoke 包了一层再抛出来 > - **getCause() 获取真实异常**:需要诊断失败原因时,用 `e.getCause()` 拿到真正的 RuntimeException(及异常消息)——注释 5 特意提醒"getCause() 打印真实异常" > - **MiniJUnit 只统计不计原因**:本课框架只 count++ 计失败数,没有打印 getCause() 详情——实际 JUnit 会把 getCause() 的异常栈打印出来,这就是**测试报告里"失败原因"的来源** > - **与 08-13 练习的呼应**:上一课练习里就有"把 read() 改成会抛异常的语句,观察 invoke 抛 InvocationTargetException"的实验——本课把这一现象用在了真实框架逻辑里 --- ## 12. 通过/失败统计:模拟测试报告输出 ### 概念 —— 框架的最后一步:汇总成功/失败个数 所有测试方法执行完后,输出统计结果——这就是简化版"测试报告": ```java // 来源:homework0813/MiniJUnitTest.java(第 6 步) System.out.println("失败个数:" + count); // 输出 失败个数:1 System.out.println("成功个数:" + a); // 输出 成功个数:2 ``` ### 预期输出 ``` 测试:加法计算 测试:打招呼 null 失败个数:1 成功个数:2 ``` ### 运行流程全景(MiniJUnit 完整执行) ``` 第 1 步:反射创建被测对象 → Student student(name=null) 第 2 步:遍历全部方法 → getDeclaredMethods() 第 3 步:筛选 test 前缀 → 命中 testAdd / testHello / testFail 第 4 步:逐个暴力反射执行: testAdd → 输出"测试:加法计算",1+1==2 不抛异常 → 成功 a=1 testHello → 输出"测试:打招呼 null"(name 为 null)→ 成功 a=2 testFail → 输出后直接 throw → invoke 抛 InvocationTargetException → 失败 count=1 第 5 步:统计输出 → 失败个数:1,成功个数:2 ``` ### 计数器拆解 | 变量 | 含义 | 最终值 | 对应测试方法 | |------|------|--------|--------------| | `a` | 成功个数 | 2 | testAdd(通过)、testHello(通过) | | `count` | 失败个数 | 1 | testFail(故意抛异常) | > **要点**: > - **两个计数器语义**:`a` 记成功(invoke 不抛异常)、`count` 记失败(IllegalAccessException 或 InvocationTargetException)——一正一负互补,相加等于测试方法总数 3 > - **"测试:打招呼 null"是合理的**:testHello 用反射创建的无参构造对象执行,name 是 null——这提示"测试对象需要先初始化数据",真实测试框架会提供 @BeforeEach 之类的初始化钩子 > - **模拟测试报告的局限**:真实 JUnit 还会输出每个测试的耗时、失败栈、@Test 注解解析等——MiniJUnit 只演示了**最核心的"扫描 + 执行 + 统计"闭环** --- ## 13. 知识点全景总结 | 知识点 | 核心内容 | 来源 | |--------|----------|------| | **项目结构** | `c260814` 项目 = `homework0813` 包(0813 作业进阶)——三个类:Student(反射目标实体类)/ MethodPrivateTest(暴力反射调用私有方法 + 通用工具)/ MiniJUnitTest(模拟 JUnit 单元测试框架) | homework0813/ | | **Student 三种权限字段** | private(id/name/age/gender)+ public(email/phone)+ public static(school)——上一课 Field 反射目标,本课复用 | homework0813/Student.java | | **Student 4 个构造方法** | public 无参 / private 二参 / public 四参 / public 六参——MiniJUnit 用 public 无参构造反射创建对象 | homework0813/Student.java | | **Student 私有方法 read/write** | `private void read()`(实例)+ `private static void write()`(静态)——MethodPrivateTest 暴力反射调用目标 | homework0813/Student.java | | **Student 测试方法 testAdd/testHello/testFail** | 3 个 `test` 前缀 private 方法——MiniJUnit 按命名约定识别的"模拟测试用例";testAdd 通过、testHello 通过、testFail 故意抛异常 | homework0813/Student.java | | **私有方法无公开入口** | read()/write()/testXxx 都是 private 且没有 getter/setter,普通代码无法调用——只有暴力反射能调用,这是本课"为什么需要反射"的场景 | homework0813/Student.java | | **暴力反射调用私有实例方法** | `getDeclaredMethod("read")`(拿 private)+ `setAccessible(true)`(放开权限)+ `invoke(对象)`(第一参数传调用该方法的对象)——三件套与 Field/Constructor 一致 | homework0813/MethodPrivateTest | | **暴力反射调用私有静态方法** | `getDeclaredMethod("write")` + `setAccessible(true)` + `invoke(null)`(静态方法第一参数传 null)——private 决定暴力反射、static 决定传 null,两个维度独立 | homework0813/MethodPrivateTest | | **invoke 第一参数规律** | **实例方法传对象、静态方法传 null**——与 Field 操作静态属性传 null 同一规律:静态成员属于整个类、不属于任何对象 | homework0813/MethodPrivateTest | | **通用工具 invokeAnyMethod** | 封装"计算参数类型 → 获取方法 → 放开权限 → 调用"四步——`invokeAnyMethod(student, "read")` 调实例方法、`invokeAnyMethod(student.getClass(), "write")` 调静态方法 | homework0813/MethodPrivateTest | | **instanceof Class 判断** | `obj instanceof Class` 判断 obj 是 Class 对象还是普通对象——Class → 静态方法分支 invoke(null);普通对象 → 实例方法分支 invoke(obj) | homework0813/MethodPrivateTest | | **args[i].getClass() 动态计算参数类型** | 通用工具不能写死参数类型,用 `args[i].getClass()` 根据实际参数动态算出 `Class[]` 再传给 getDeclaredMethod——可变参数 Object... 长度为 0 时得到空数组 | homework0813/MethodPrivateTest | | **反射创建被测对象** | `studentClass.getConstructor().newInstance()`——getConstructor() 拿 public 无参构造(不传参数类型默认找无参构造),newInstance() 创建对象;类没有无参构造会抛 NoSuchMethodException | homework0813/MiniJUnitTest | | **getDeclaredMethods() 遍历** | 获取本类声明的全部方法数组(含 private,不含父类 Object 的方法)——测试框架扫描测试方法的入口 | homework0813/MiniJUnitTest | | **test 前缀识别测试方法** | `methodName.startsWith("test")` 按命名约定识别测试方法——模拟 JUnit 3 的设计(JUnit 4+ 用 @Test 注解,原理相同) | homework0813/MiniJUnitTest | | **暴力反射执行测试方法** | `m.setAccessible(true)` + `m.invoke(student)`——private 测试方法靠暴力反射执行;invoke 不抛异常 = 通过(a++),抛异常 = 失败(count++) | homework0813/MiniJUnitTest | | **"不抛异常 = 通过"的判定哲学** | 测试方法内部写断言(if 条件不满足就 throw),invoke 执行时抛异常即判失败——测试框架最基本的判定规则 | homework0813/MiniJUnitTest | | **InvocationTargetException 包装异常** | 被调方法内部抛异常时,反射 invoke 不会直接抛原异常,而是**包装成 InvocationTargetException 抛出**;调用者需 `e.getCause()` 获取真实异常 | homework0813/MiniJUnitTest | | **InvocationTargetException 与普通调用对比** | 普通 `student.testFail()` 直接抛 RuntimeException;反射 `m.invoke(student)` 抛 InvocationTargetException(被调方法外有包装层) | homework0813/MiniJUnitTest | | **通过/失败统计** | 成功计数器 a、失败计数器 count——testAdd/testHello 通过(a=2),testFail 失败(count=1),输出"失败个数:1,成功个数:2" | homework0813/MiniJUnitTest | | **测试报告输出** | `System.out.println("失败个数:" + count)` / `System.out.println("成功个数:" + a)`——简化版测试报告 | homework0813/MiniJUnitTest | | **受检异常全家福** | MiniJUnit main throws `NoSuchMethodException`(无无参构造)+ `InvocationTargetException`(被调方法抛异常)+ `InstantiationException`(实例化失败)+ `IllegalAccessException`(权限) | homework0813/MiniJUnitTest | | **防御性异常捕获** | catch 里同时处理 IllegalAccessException 和 InvocationTargetException 都计入失败——保证单个测试失败不影响后续测试执行 | homework0813/MiniJUnitTest | | **方法反射综合应用闭环** | 获取方法(getDeclaredMethod(s))→ 暴力反射(setAccessible)→ 调用(invoke)→ 异常处理(InvocationTargetException.getCause())→ 统计——完整走完 Method 反射的全部环节 | homework0813/MethodPrivateTest + MiniJUnitTest | --- ## 14. 随堂练习要点 - **项目结构梳理(必做)**:说清 c260814 项目三个类各司其职——Student 提供被测对象与测试方法、MethodPrivateTest 演示暴力反射调用私有方法并封装通用工具、MiniJUnitTest 模拟 JUnit 测试框架;包名 homework0813 的含义(0813 作业进阶) - **Student 私有方法识别(必做)**:从 Student 类中找出所有 private 方法(read/write/testAdd/testHello/testFail 共 5 个),区分实例方法(read/testXxx)与静态方法(write)——思考为什么普通代码调用不了它们,只有反射能调 - **暴力反射三件套背诵(必做)**:`getDeclaredMethod("read")`(拿 private)+ `setAccessible(true)`(放开权限)+ `invoke(对象)`(实例方法传对象)——对比静态方法 `invoke(null)`,总结"private 决定暴力反射、static 决定传 null" - **invoke 第一参数规律实验**:把 `invokeAnyMethod(student.getClass(), "write")` 改成 `invokeAnyMethod(student, "write")`(传对象不传 Class)——观察工具走到实例方法分支、invoke(student) 调静态方法,思考结果是否仍能执行(静态方法不依赖对象,通常也能跑)——体会"传 Class 才是调用静态方法的正确信号" - **instanceof Class 理解(必做)**:思考通用工具为什么用 `obj instanceof Class` 判断——`student.getClass()` 和 `Student.class` 都是 Class 对象;传 Class → 静态方法分支,传普通对象 → 实例方法分支;画一张"调用流程图"说明两个分支的执行路径 - **args[i].getClass() 动态计算参数类型**:给 Student 新增一个有参方法(如 `testAdd(int a, int b)`),用 `invokeAnyMethod(student, "testAdd", 1, 2)` 调用——体会工具自动用 `args[i].getClass()` 算出参数类型数组(自动装箱后实际是 Integer.class) - **setAccessible(true) 注释掉实验(必做)**:把 MiniJUnit 中 `m.setAccessible(true)` 注释掉再运行——观察 3 个 private 测试方法全部抛 IllegalAccessException,统计"失败 3 个、成功 0 个"——直观体会暴力反射对私有方法的作用 - **getDeclaredMethods vs getMethods 对比**:把 MiniJUnit 中 `getDeclaredMethods()` 改成 `getMethods()` 再运行——观察 testXxx(private)遍历不到,成功/失败都是 0——验证"getMethods 只拿 public、拿不到 private 测试方法" - **InvocationTargetException 实验(必做)**:在 testFail 的 catch 分支里加 `e.getCause().printStackTrace()` 再运行——观察控制台打印出真正的 RuntimeException("故意失败的方法")——体会 getCause() 获取真实异常、测试报告"失败原因"的来源 - **MiniJUnit 扩展练习①:新增测试方法**:给 Student 新增 `testSub()`(减法测试,`2-1==1` 通过)和 `testDiv()`(故意 `1/0` 抛异常失败)——重新运行观察统计变为"成功 3、失败 2" - **MiniJUnit 扩展练习②:打印测试结果明细**:在 invoke 成功后加 `System.out.println(methodName + " 通过")`、失败后加 `System.out.println(methodName + " 失败:" + e.getCause())`——让模拟测试报告显示每个用例的名字和失败原因(贴近真实 JUnit) - **无参构造的重要性(必做)**:把 Student 的 `public Student() {}` 注释掉再运行 MiniJUnit——观察 `getConstructor().newInstance()` 抛 NoSuchMethodException——理解"反射创建对象为什么要求类有无参构造"(框架不知道传什么参数) - **测试对象初始化问题**:观察"测试:打招呼 null"的输出——反思:无参构造创建的对象字段为 null,真实测试框架如何解决初始化?(提示:@BeforeEach / @BeforeAll 钩子方法,或反射 setter 注入测试数据) - **防御性异常处理思考**:MiniJUnit 把 IllegalAccessException 和 InvocationTargetException 都计入失败——思考还有什么异常可能抛出(如 InvocationTargetException 的 getCause 是 Error)、框架如何处理才算健壮 - **与真实 JUnit 对比(必做)**:说出 MiniJUnit 与真实 JUnit 的 3 个差异——①测试方法识别:test 前缀 vs @Test 注解 ②没有 @BeforeEach/@AfterEach 初始化/清理钩子 ③失败时只统计个数、不打印失败原因和堆栈 - **通用工具复用(必做)**:把 invokeAnyMethod 抄下来作为自己的反射工具方法——思考它还能调用哪些方法(任意对象的任意方法:getter/setter/私有方法/静态方法),回顾上一课练习六的 invokeMethod 工具与本课工具的异同(本课多了 instanceof 判断静态/实例分支) - **Method 反射知识串联(本课完结)**:从 08-12 Constructor(创建对象)→ 08-13 Field(读写字段)+ Method(调用方法)→ 本课 Method 进阶应用(暴力反射私有方法 + 模拟测试框架)——完整回顾反射三大成员从"学 API"到"做框架"的进阶路径 --- ## 15. 拓展阅读 - **JUnit 的底层原理**:JUnit 5(Jupiter)通过 `@Test` 注解 + 反射实现测试执行——测试类被 `Class.forName` 加载 → 遍历方法找 `@Test` 注解 → `setAccessible(true)` + `invoke` 执行 → 用 `InvocationTargetException.getCause()` 捕获断言异常并生成测试报告——本课 MiniJUnit 正是这个链路的简化版 - **JUnit 3 与 JUnit 4/5 的演进**:JUnit 3 用"方法名以 test 开头"识别测试(本课模拟的就是 JUnit 3 设计);JUnit 4 引入 `@Test` 注解(注解驱动);JUnit 5 引入扩展模型(Extension)——理解"命名约定 → 注解 → 扩展模型"的演进,是理解测试框架历史的钥匙 - **断言(Assertion)机制**:真实 JUnit 用 `Assert.assertEquals(expected, actual)` 等断言方法——断言内部就是"判断条件,不满足就 throw AssertionError";反射 invoke 执行测试方法时,断言异常被包装成 InvocationTargetException,框架用 getCause() 取出断言信息作为"测试失败原因" - **生命周期钩子方法**:真实 JUnit 的 `@BeforeEach`/`@AfterEach`/`@BeforeAll`/`@AfterAll` 注解方法——框架在"每次测试前/后、全部测试前/后"反射调用这些钩子方法——本课"测试:打招呼 null"暴露的对象初始化问题,正是 @BeforeEach 要解决的 - **反射在框架中的"扫描 + 执行"模式**:本课 MiniJUnit 的"getDeclaredMethods 遍历 → 条件筛选 → invoke 执行"是反射框架的**通用范式**——Spring 的组件扫描(找 @Component 注解的类)、MyBatis 的 Mapper 解析(找接口方法)、参数解析器(找匹配参数)都是这个套路 - **InvocationTargetException 在框架中的应用**:任何"反射调用业务方法"的框架(Spring AOP 代理方法、RPC 框架调用远程服务方法、定时任务框架)都会遇到 InvocationTargetException——框架统一用 `getCause()` 拿到业务异常再处理,避免被"包装层"迷惑 - **getCause() 的链式异常**:`getCause()` 返回的异常可能还有自己的 cause(异常链)——框架(如 JUnit)打印失败栈时会遍历整条异常链 `while ((e = e.getCause()) != null)`,把每一层原因都展示出来 - **反射调用 vs 方法句柄(MethodHandle)**:JDK7+ 引入 `java.lang.invoke.MethodHandle`(方法句柄),性能优于反射 invoke,是"更高性能的反射"——Spring、Netty 等框架在热点路径上会用 MethodHandle 替代 Method.invoke - **反射调用 setter/getter 的框架应用**:Spring BeanUtils.copyProperties、MyBatis 结果映射、Jackson 反序列化都通过反射调用实体类的 setter 赋值——这就是实体类必须写标准 getter/setter 的原因;本课 Student 的 7 组 getter/setter 正是为此准备 - **Class.getConstructor vs getDeclaredConstructor**:MiniJUnit 用 `getConstructor()`(public 无参构造)——如果被测类的构造是 private,需要用 `getDeclaredConstructor()` + `setAccessible(true)`(上一课 08-12 已学);**框架统一要求无参构造**,就是为了避免处理各种构造器参数 - **"无参构造"约定在框架中的地位**:Spring Bean、MyBatis 实体、JUnit 测试类都要求无参构造——因为框架无法预知构造参数,统一用无参构造 + setter 注入是最通用的约定;这也是 JavaBean 规范(无参构造 + getter/setter)的由来 - **测试框架中的"隔离"思想**:为什么每个测试方法失败不中断整个套件?——因为测试之间应**相互独立**(一个测试失败不代表其他测试失效);try-catch 包住每个 invoke 正是"测试隔离"的实现 - **反射破坏封装性的辩证关系(最终版)**:从 08-12 的 Constructor(newInstance 创建对象)到 08-13 的 Field(get/set 读写字段)+ Method(invoke 调用方法)再到本课的"暴力反射私有方法 + 测试框架"——反射破坏封装的"负面"(能访问 private)恰恰是框架"正面"能力(测试私有逻辑)的来源:**普通业务代码遵守封装,框架用反射突破封装** - **反射 Method 知识全景(本课收官)**:Method 反射的全部技能点——获取方法(getMethod(s)/getDeclaredMethod(s))、方法信息(getName/getReturnType/getParameterTypes/getModifiers)、调用方法(invoke)、暴力反射(setAccessible)、异常处理(InvocationTargetException.getCause())、组合应用(通用调用工具 + 测试框架)——至此"反射三件套"(Constructor/Field/Method)全部学完并可组合成框架级工具 - **JDK 文档**:[`java.lang.Class`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html)(getConstructor/getDeclaredMethods 等反射入口)、[`java.lang.reflect.Method`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Method.html)(invoke/setAccessible/getName/getParameterTypes)、[`java.lang.reflect.InvocationTargetException`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/InvocationTargetException.html)(包装被调方法异常的反射专用异常,getCause 获取真实异常)、[JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)(@Test 注解与扩展模型、断言机制)