直接调用Method.invoke需通过访问路径、修饰符、参数类型、模块权限四层校验;getMethod查public及继承链方法,getDeclaredMethod查当前类所有声明方法(含private);JDK9+需--add-opens授权模块;setAccessible(true)仅对非public生效且不能绕过模块边界;invoke参数须装箱、传Object[]、解包InvocationTargetException;Method应缓存复用,避免重复查找。

直接调用 Method.invoke 是可行的,但不加区分地使用会触发 IllegalAccessException、IllegalArgumentException 或包装在 InvocationTargetException 里的原始异常,且 JDK 9+ 下默认拒绝跨模块访问 —— 这不是“写对了就跑通”的事,而是得按访问路径、修饰符、参数类型、模块权限四层校验才能稳。
getMethod vs getDeclaredMethod:选错就找不到方法
用错获取方式是第一个高频失败点。两者行为差异极关键:
-
getMethod("getName")只查 public 方法,且会沿继承链向上找(比如Student类没重写getName,它也能拿到父类Person的 public 方法) -
getDeclaredMethod("getGrade", int.class)查当前类声明的所有方法(含private/protected),但不包括父类的任何方法 - 如果目标方法是
private,必须用getDeclaredMethod,否则抛NoSuchMethodException - 若方法签名含泛型擦除后类型(如
List<String>实际参数类型是List.class),传参时也得用擦除后的Class对象,否则匹配失败
setAccessible(true) 不是万能钥匙,JDK 9+ 需显式开模块
即使调用了 method.setAccessible(true),在 JDK 9 及以后仍可能报 InaccessibleObjectException —— 模块系统已将反射访问视为“需授权操作”。
- 启动 JVM 时必须加
--add-opens java.base/java.lang=ALL-UNNAMED(若要访问java.lang下私有成员) - 若目标类在自定义模块中,还需对应写
--add-opens your.module/your.package=ALL-UNNAMED -
setAccessible(true)对public方法无实际作用,仅对非 public 生效;但它不能绕过模块边界,只能解除同一模块内或已授权模块的封装限制 - 某些安全策略(如
SecurityManager启用时)会彻底禁用setAccessible,此时抛SecurityException
invoke() 的参数和返回值处理容易踩坑
Method.invoke 看似简单,但参数数组、null 安全、异常包装三处极易出错:
- 第一个参数是目标对象(
static方法传null),第二个是Object...参数数组 —— 即使方法无参,也要传new Object[0],传null会抛NullPointerException - 基本类型参数不能直接传原始值(如
int),必须装箱(Integer),否则invoke无法匹配方法签名 - 返回值是
Object,需手动强转;若方法返回void,invoke返回null,不是Void实例 - 所有被调用方法抛出的异常,都会被包进
InvocationTargetException;必须用e.getCause()解包才能拿到原始异常(比如NullPointerException或业务异常)
性能敏感场景下,避免重复查找 Method 对象
每次调用都走 clazz.getMethod(...) + method.invoke(...),开销远高于直接调用 —— JIT 无法内联,且反射链本身有缓存冷启动成本。
-
Method对象是线程安全的,可复用;建议在初始化阶段(如 Spring Bean 构造时)缓存常用Method引用 - JDK 内部有 inflation 机制:前几次调用走 JNI,之后生成字节码版
MethodAccessor,但阈值(默认 15)不可控,不建议依赖 - 若需高频调用(如 JSON 序列化器),应考虑用
MethodHandle(JDK 7+)或运行时生成代理类(如 ByteBuddy),而非反复invoke - 注意缓存的
Method若来自不同类加载器下的同名类,会导致ClassNotFoundException或IllegalAccessError,务必确保类加载器上下文一致
真正难的不是“怎么调”,而是判断该不该用、在哪一层做缓存、如何应对模块隔离、以及是否值得为灵活性牺牲确定性错误提示和性能 —— 这些决策点,往往在第一次 InvocationTargetException 抛出来之前就被忽略了。

















