Freemarker removeIntrospectionInfo does not work with DCEVM after model hotswap

dcevm, freemarker, hotswap

Solution

`BeansWrapper` (and `DefaultObjectWrapper`'s, etc.) introspection cache relies on `java.beans.Introspector.getBeanInfo(aClass)`, not on reflection. (That's because it treats objects as JavaBeans.) `java.beans.Introspector` has its own internal cache, so it can return stale information, and in that case `BeansWrapper` will just recreate its own class introspection data based on that stale information. As of `java.beans.Introspector`'s caching, it's in fact correct, as it builds on the assumption that classes in Java are immutable. If something breaks that basic rule, it should ensure that `java.beans.Introspector`'s cache is cleared (and many other caches...), or else it's not just FreeMarker that will break. At JRebel for example they made a lot of effort to clear all kind of caches. I guess DCEVM doesn't have the resources for that. So then, it seems you have to call `Introspector.flushCaches()` yourself.

Update: For a while (Java 7, maybe 6) `java.beans.Introspector` has one cache per thread group, so you have call `flushCaches()` from all thread groups. And this all is actually implementation detail that, in principle, can change any time. And sadly, the JavaDoc of `Introspector.flushCaches()` doesn't warn you...

Problem

I am using Freemarker and DCEVM+HotSwapManager agent. This basically allows me to hotswap classes even when adding/removing methods. Everything works like charm until Freemarker uses hotswapped class as model. It's throwing freemarker.ext.beans.InvalidPropertyException: No such bean property on me even though reflection shows that the method is there (checked during debug session). I am using ``` final Method clearInfoMethod = beanWrapper.getClass().getDeclaredMethod("removeIntrospectionInfo", Class.class); clearInfoMethod.setAccessible(true); clearInfoMethod.invoke(clazz); ``` to clear the cache, but it does not work. I even tried to obtain classCache member field and clear it using reflection but it does not work too. What am I doing wrong? I just need to force freemarker to throw away any introspection on model class/classes he has already obtained. Is there any way? UPDATE Example code Application.java ``` // Application.java public class Application { public static final String TEMPLATE_PATH = "TemplatePath"; public static final String DEFAULT_TEMPLATE_PATH = "./"; private static Application INSTANCE; private Configuration freemarkerConfiguration; private BeansWrapper beanWrapper; public static void main(String[] args) { final Application application = new Application(); INSTANCE = application; try { application.run(args); } catch (InterruptedException e) { System.out.println("Exiting"); } catch (IOException e) { System.out.println("IO Error"); e.printStackTrace(); } } public Configuration getFreemarkerConfiguration() { return freemarkerConfiguration; } public static Application getInstance() { return INSTANCE; } private void run(String[] args) throws InterruptedException, IOException { final String templatePath = System.getProperty(TEMPLATE_PATH) != null ? System.getProperty(TEMPLATE_PATH) : DEFAULT_TEMPLATE_PATH; final Configuration configuration = new Configuration(); freemarkerConfiguration = configuration; beanWrapper = new BeansWrapper(); beanWrapper.setUseCache(false); configuration.setObjectWrapper(beanWrapper); try { final File templateDir = new File(templatePath); configuration.setTemplateLoader(new FileTemplateLoader(templateDir)); } catch (IOException e) { throw new RuntimeException(e); } final RunnerImpl runner = new RunnerImpl(); try { runner.run(args); } catch (RuntimeException e) { e.printStackTrace(); } } public BeansWrapper getBeanWrapper() { return beanWrapper; } } ``` RunnerImpl.java ``` // RunnerImpl.java public class RunnerImpl implements Runner { @Override public void run(String[] args) throws InterruptedException { long counter = 0; while(true) { ++counter; System.out.printf("Run %d\n", counter); // Application.getInstance().getFreemarkerConfiguration().setObjectWrapper(new BeansWrapper()); Application.getInstance().getBeanWrapper().clearClassIntrospecitonCache(); final Worker worker = new Worker(); worker.doWork(); Thread.sleep(1000); } } ``` Worker.java ``` // Worker.java public class Worker { void doWork() { final Application application = Application.getInstance(); final Configuration freemarkerConfiguration = application.getFreemarkerConfiguration(); try { final Template template = freemarkerConfiguration.getTemplate("test.ftl"); final Model model = new Model(); final PrintWriter printWriter = new PrintWriter(System.out); printObjectInto(model); System.out.println("-----TEMPLATE MACRO PROCESSING-----"); template.process(model, printWriter); System.out.println(); System.out.println("-----END OF PROCESSING------"); System.out.println(); } catch (IOException e) { e.printStackTrace(); } catch (TemplateException e) { e.printStackTrace(); } } private void printObjectInto(Object o) { final Class<?> aClass = o.getClass(); final Method[] methods = aClass.getDeclaredMethods(); for (final Method method : methods) { System.out.println(String.format("Method name: %s, public: %s", method.getName(), Modifier.isPublic(method.getModifiers()))); } } } ``` Model.java ``` // Model.java public class Model { public String getMessage() { return "Hello"; } public String getAnotherMessage() { return "Hello World!"; } } ``` This example does not work at all. Even changing BeansWrapper during runtime won't have any effect.

Original source

Related problems