usr-local-ΕΨΗΕΛΩΝ
usr-local-ΕΨΗΕΛΩΝ

Reputation: 26874

Listening to class reload in Java

For performance reasons, I have a class that stores a Map whose key is a Class<?> and its value is function of that class's fields. The map is populated during code execution according to the type of the calling object. The above is a 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);
    }

}

During development, thanks to the help of Jrebel, I often make modifications to classes by changing entire properties or just their names. I can continue development just fine. However, if I already put a value into the cache it will be stale forever.

What I am asking here is if it is possible to intercept the event that a class in the classpath has changed. Very broad... But my specific problem is very simple: since I have such a need only during development, I just want to wipe that cache in case any class in my classpath changes.

How can I accomplish this? I don't need to do anything special than intercepting the event and simply wiping the cache

Upvotes: 0

Views: 666

Answers (2)

Murka
Murka

Reputation: 361

JRebel has a plugin API that you can use to trigger code on class reloads. The tutorial complete with example application and plugin available here: https://manuals.zeroturnaround.com/jrebel/advanced/custom.html

The JRebel plugin is a self-contained jar built against the JRebel SDK, which is attached to the running application via the JVM argument -Drebel.plugins=/path/to/my-plugin.jar. The JRebel agent attached to the application will load and start plugins from this argument.
If the application is not started with the JRebel agent, the plugin is simply not loaded.

In your example you want to register a ClassEventListener that will clear the Cache.fieldsList map. As it is a private field, you need to access it via reflection or add a get/clear method via a ClassBytecodeProcessor

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 ...
}

And to clear the map

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));
  }
}

However a better option is to only clear/recalculate the single class entry on class reload if possible. The example didn't display whether the info computed from one class depended on superclass infos, but if this is true, the JRebel SDK has methods to register a reload listener on the class hierarchy as well.

Upvotes: 3

Holger
Holger

Reputation: 298153

There is an existing class ClassValue which already does the job for you:

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);
    }
}

When you call get, it will call computeValue if this is the first call for this specific Class argument or return the already existing value otherwise. It does already care thread safety and for allowing classes to get garbage collected. You don’t need to know when class unloading actually happens.

Upvotes: 0

Related Questions