Method 对象可以绑定任何方法(包括私有),setAccessible(true) 是打开这扇门的钥匙
在动态代理的学习中,InvocationHandler.invoke()会收到一个 java.lang.reflect.Method对象。自然会引出疑问:
| 方法 | 能获取什么 | 能获取 private? | 能获取继承的? |
|---|---|---|---|
Class.getMethod(name, paramTypes...) |
本类声明 + 继承层次中的 public方法 | 不能 | 能 |
Class.getDeclaredMethod(name, paramTypes...) |
本类声明的所有方法(public/protected/包私有/private) | 能拿到 但直接调用会抛异常 |
不能 |
getDeclaredMethod可以拿到 private 方法,但如果你直接用 method.invoke(obj)调用它,Java 的访问控制检查会抛出 IllegalAccessException。
public class Secret {
private void hidden() {
System.out.println("私有方法被调用了");
}
public void visible() {
System.out.println("公开方法");
}
}
// ========== 对比 ==========
Secret obj = new Secret();
// getMethod:只能获取 public
Method m1 = Secret.class.getMethod("visible"); // 成功
Method m2 = Secret.class.getMethod("hidden"); // NoSuchMethodException
// getDeclaredMethod:能获取 private,但调不了
Method m3 = Secret.class.getDeclaredMethod("hidden"); // 成功拿到了
m3.invoke(obj); // IllegalAccessException!
调用 method.setAccessible(true)后,JVM 关闭对该 Method 对象的访问控制检查,之后就可以自由调用了:
public class Secret {
private void hidden() {
System.out.println("私有方法被调用了!");
}
private String secretData(String name) {
return "Hello, " + name + " — 这是秘密数据";
}
}
// ========== 反射突破私有 ==========
Secret obj = new Secret();
// 1. 获取私有方法
Method m = Secret.class.getDeclaredMethod("hidden");
// 2. 强制关闭访问检查
m.setAccessible(true);
// 3. 调用成功!
m.invoke(obj); // 输出: 私有方法被调用了!
// ========== 带参数和返回值的私有方法 ==========
Method m2 = Secret.class.getDeclaredMethod("secretData", String.class);
m2.setAccessible(true);
String result = (String) m2.invoke(obj, "Tony");
System.out.println(result); // 输出: Hello, Tony — 这是秘密数据
Method对象可以绑定任何方法(包括私有),但调用私有方法前必须调用 setAccessible(true)来绕过 Java 的访问控制检查。这也是反射强大(且危险)的体现。
可以把 Java 的访问控制想象成一扇门:
setAccessible(true)不是"把方法变成 public",它只是告诉 JVM:"别检查这个 Method 对象的访问权限,直接执行"。
setAccessible对 Field(字段)和 Constructor(构造器)同样有效。整套反射 API(java.lang.reflect包下的 AccessibleObject)都支持这个操作。
在动态代理的典型场景中,代理的是接口的方法:
public abstract,不存在私有方法问题InvocationHandler.invoke收到的 Method对象也是接口的 public 方法setAccessible(true)如果你在 InvocationHandler 的 invoke()方法里,想委托给目标对象的私有方法,完全可以做到:
Service target = new RealService();
Service proxy = (Service) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 假设目标类有一个同名的 private 方法做真正的逻辑
Method privateMethod = target.getClass()
.getDeclaredMethod(method.getName(), method.getParameterTypes());
privateMethod.setAccessible(true); // 突破私有
System.out.println("代理:调用私有方法 " + privateMethod.getName());
Object result = privateMethod.invoke(target, args); // 委托给私有方法
System.out.println("代理:私有方法调用完毕");
return result;
}
}
);
Java 的访问控制是一个"默认锁门,但给你钥匙"的设计:
| 层面 | 行为 | 目的 |
|---|---|---|
| 正常编译 & 运行时 | private 方法无法从外部直接调用 | 封装保护:防止误用,保证设计意图 |
| 反射 + setAccessible | 可以突破私有限制调用 | 框架需求:序列化、ORM、DI 容器、测试工具需要访问私有成员 |
如果反射完全不允许访问私有成员,那 Spring、Hibernate、Gson、JUnit 这些框架全都活不了——它们大量依赖反射给 private 字段注入值、调用 private 构造器、序列化 private 状态。
正常人每天走大门(正常调用),消防员紧急时走消防通道(反射)。Java 给你门上装了锁,但也给了消防队一把钥匙。
getMethod只能拿 public(含继承的),getDeclaredMethod能拿本类的所有方法(含 private),但直接调用会抛 IllegalAccessExceptionsetAccessible(true)是钥匙——告诉 JVM 跳过访问检查,之后就可以调用任意方法(包括 private)Method对象可以绑定任何方法(包括私有),但在调用私有方法前必须调用 setAccessible(true)来绕过 Java 的访问控制检查。这也是反射强大(且危险)的体现。