在 Java 中收听 class 重新加载

Listening to class reload in Java

出于性能原因,我有一个 class 存储一个 Map,其键是一个 Class<?>,它的值是 class 字段的函数。根据调用对象的类型,在代码执行期间填充映射。上面是一个generalization/simplification

public class Cache {

    private static final Map<Class<?>, String> fieldsList = ...;


    //Synchronization omitted for brevity
    public String getHqlFor(Class<?> entity){
        if (!fieldsList.containsKey(entity))
            fieldsList.put(entity,createHql(entity));
        return fieldsList.get(entity);
    }

}

在开发过程中,感谢 Jrebel 的帮助,我经常通过更改整个属性或仅更改它们的名称来修改 classes。我可以继续开发就好了。但是,如果我已经将一个值放入缓存中,它将永远失效。

这里想问的是,是否可以拦截class路径中的某个class发生变化的事件。非常广泛......但我的具体问题非常简单:因为我只在开发期间有这样的需求,所以我只想擦除该缓存以防 any class 在我的class路径更改。

我怎样才能做到这一点?除了拦截事件并简单地 擦除 缓存

之外,我不需要做任何特别的事情

已有 class ClassValue 已经为您完成工作:

public class Cache {

    private final ClassValue<String> backend = new ClassValue<String>() {
        @Override
        protected String computeValue(Class<?> entity) {
            return createHql(entity);
        }
    };

    public String getHqlFor(Class<?> entity){
        return backend.get(entity);
    }
}

当您调用 get 时,如果这是第一次调用此特定 Class 参数,它将调用 computeValue,否则将调用 return 已经存在的值。它确实已经关心线程安全并允许 classes 收集垃圾。您不需要知道 class 卸载实际发生的时间。

JRebel 有一个插件 API,您可以使用它在 class 重新加载时触发代码。本教程包含示例应用程序和插件,可在此处获取:https://manuals.zeroturnaround.com/jrebel/advanced/custom.html

JRebel 插件是针对 JRebel SDK 构建的独立 jar,它通过 JVM 参数 -Drebel.plugins=/path/to/my-plugin.jar 附加到 运行 应用程序。附加到应用程序的 JRebel 代理将从该参数加载和启动插件。
如果应用程序不是使用 JRebel 代理启动的,则根本不会加载插件。

在您的示例中,您想要注册一个 ClassEventListener 来清除 Cache.fieldsList 映射。由于它是私有字段,您需要通过反射访问它或通过 ClassBytecodeProcessor

添加 get/clear 方法
public class MyPlugin implements Plugin {
  void preinit() {
    ReloaderFactory.getInstance().addClassReloadListener(new ClassEventListenerAdapter(0) {
      @Override
      public void onClassEvent(int eventType, Class<?> klass) throws Exception {
        Cache.clear();
      }
    });
  }
  // ... other methods ...
}

并清除地图

public class CacheCBP extends JavassistClassBytecodeProcessor {
  public void process(ClassPool cp, ClassLoader cl, CtClass ctClass) {
    ctClass.addMethod(CtMethod.make("public static void clear() { fieldsList.clear(); }", ctClass));
  }
}

然而,如果可能的话,更好的选择是在 class 重新加载时仅 clear/recalculate 单个 class 条目。该示例没有显示从 class 计算的信息是否依赖于 superclass 信息,但如果这是真的,JRebel SDK 有方法在 class 上注册一个重新加载侦听器层次结构也是如此。