得知引用类,很有意思sun.reflect.Reflection

本文介绍了一个有趣的Java反射应用实例,通过使用sun.reflect.Reflection类中的getCallerClass方法来确定调用者,实现类之间的关联。

原文地址:

http://www.javaspecialists.co.za/archive/newsletter.do?issue=087&locale=zh_CN

sun.reflect.Reflection

几个礼拜前,我和我一位叫Niko Brummer的朋友聊天。他是一位音响检验方面的专家,热爱水上运动,并且喜欢把他的理论知识应用到运动中。Niko十分愿意随时与你谈论他在Java(和海洋)方面的最新发现。

我们聊天的时候,Niko提到了一个我未曾听闻的、叫作“Reflection”的类。这个类是Sun JVM一起附带的,可以在sun.reflect.*包里找到它。其中最有用的方法是: Class getCallerClass(int i). 这个类能告诉你在我们当前的调用堆栈中有哪些类。

让我们来看一个例子,是做汤时需要用到的一些类。(假设你想为几百人做一道美味的蔬菜汤,请看一下 我们网站上的这个菜谱 ;-) 我们希望土豆(Potato)有一个指向它所在汤(Soup)的一个引用,并且我们希望汤(Soup)明白它自己包含哪些土豆(Potato)。请记住,这个解决方法只是众多可能的一种,并且只是作为一个演示如何使用这个Reflection类的例子。

 java.util.*;
 Soup {
   List potatos =  ArrayList();

   add(Potato p) {
    potatos.add(p); p.setSoup();
  }

   String toString() {
      + potatos + ;
  }
}


 sun.reflect.Reflection;

 Potato {
   id;
   Soup soup;

   Potato( id) {
    .id = id; }

   setSoup(Soup soup) {
    .soup = soup;
     (Reflection.getCallerClass() !=
    Soup.) {
      soup.add(); }
  }

   Soup getSoup() {
     soup; }

   String toString() {
      + id; }
}
  

Potato.setSoup是个有趣的方法,它检查调用它的类是否是Soup。getCallerClass采用堆栈层次作为参数,这个例子中是2。

我们可以试着做一道汤(Soap),加入一些土豆(Potato),然后把汤和一个无关联的 Potato建立关系(Association)。

 SoupTest {
   main(String[] args) {
    Soup soup =  Soup(); soup.add(
    Potato()); soup.add( Potato());
    soup.add( Potato()); Potato p4 = 
    Potato(); soup.add(p4); p4.setSoup(soup); // redundant code
    Potato p5 =  Potato(); p5.setSoup(soup);
    System.out.println( + soup);
  } 
}
  

java -showversion SoupTest执行这段代码的结果:

java version "1.4.2_04" Java(TM) 2 Runtime Environment, Standard Edition
(build 1.4.2_04-b05) Java HotSpot(TM) Client VM (build 1.4.2_04-b05, mixed
mode)

soup = Soup {potatos=[Potato 1, Potato 2, Potato 3, Potato 4, Potato 4, Potato
5]}
在使用 JDK 11.0.17 启动或编译项目时,如果出现与 `sun.reflect.Reflection.getCallerClass(3)` 相关的错误,通常是因为代码中直接或间接调用了该方法。从 JDK 7u40 开始,Oracle 已经弃用 `Reflection.getCallerClass(int)` 方法,并且在后续版本中逐步限制其可用性[^1]。尽管 JDK 8 及更早版本允许通过 `-Djdk.reflect.allowGetCallerClass` 参数启用该方法,但在 JDK 9 及以后的模块化版本中,此方法已被彻底删除,调用它将导致 `UnsupportedOperationException`。 ### 错误原因分析 1. **JDK 版本升级后的兼容性问题** JDK 9 引入了模块系统(JPMS),对内部 API 的访问进行了严格限制。`sun.reflect.Reflection.getCallerClass(int)` 属于非标准、内部使用的 API,在模块化后被完全移除,任何尝试调用它的行为都会抛出异常。 2. **第三方库或框架依赖旧有反射机制** 如果项目依赖某些较老的库(如某些 AOP 框架、日志工具或序列化组件),它们可能仍在使用 `getCallerClass(int)` 获取调用者信息。这类库在 JDK 11 上运行时会触发 `UnsupportedOperationException`。 3. **自定义类加载逻辑问题** 若项目中有自定义的类加载器或安全检查逻辑,并试图通过 `getCallerClass(3)` 获取上下文类加载器或判断调用栈信息,则会在 JDK 11 中失败。 ### 解决方案 #### 1. 替换替代方法获取调用者类信息 Java 提供了标准 API 来替代 `getCallerClass(int)` 的功能: ```java StackWalker stackWalker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); Class<?> callerClass = stackWalker.walk(frames -> frames .skip(1) // 跳过当前帧 .findFirst() .map(StackWalker.StackFrame::getDeclaringClass) .orElseThrow()); ``` 此方式利用 `StackWalker` 安全地遍历调用栈并获取调用者类引用,适用于 JDK 9 及以上版本。 #### 2. 升级或替换依赖库 检查项目中是否引入了使用 `getCallerClass(int)` 的第三方库,建议升级到最新版本以支持 JDK 11+。例如,某些日志框架(如 Log4j)和 AOP 工具(如 CGLIB、AspectJ)在早期版本中存在此类问题,但已在新版本中修复。 #### 3. 使用 JVM 参数绕过模块限制(不推荐长期使用) 虽然 JDK 11 不再支持 `-Djdk.reflect.allowGetCallerClass` 参数恢复 `getCallerClass(int)` 的功能,但可以通过添加 JVM 启动参数临时开放内部 API 访问权限: ```bash --add-opens java.base/sun.reflect=ALL-UNNAMED ``` 此参数允许未命名模块访问 `sun.reflect` 包中的类,从而缓解部分兼容性问题。但应注意,这种方式仅用于过渡期,不应作为长期解决方案。 #### 4. 修改源码避免使用非标准 API 若错误源于项目自身代码中调用了 `getCallerClass(int)`,应尽快修改源码,改用标准 API 或重构设计,避免依赖内部实现细节。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值