模型热插拔后,Freemarker removeIntrospectionInfo 不适用于 DCEVM

Freemarker removeIntrospectionInfo does not work with DCEVM after model hotswap

我正在使用 Freemarker 和 DCEVM+HotSwapManager 代理。这基本上允许我在 adding/removing 方法时热交换 classes。

在 Freemarker 使用热交换 class 作为模型之前,一切都像魅力一样。它抛出 freemarker.ext.beans.InvalidPropertyException: No such bean 属性 即使反射显示该方法存在(在调试会话期间检查)。

我正在使用

final Method clearInfoMethod = beanWrapper.getClass().getDeclaredMethod("removeIntrospectionInfo", Class.class);
clearInfoMethod.setAccessible(true);
clearInfoMethod.invoke(clazz);

清除缓存,但不起作用。我什至尝试获取 classCache 成员字段并使用反射清除它,但它也不起作用。

我做错了什么? 我只需要强制 freemarker 放弃对他已经获得的模型 class/classes 的任何自省。

有什么办法吗?

更新

示例代码

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!";
    }
}

这个例子根本行不通。即使在运行时更改 BeansWrapper 也不会产生任何影响。

BeansWrapper(和 DefaultObjectWrapper 等)内省缓存依赖于 java.beans.Introspector.getBeanInfo(aClass),而不是反射。 (那是因为它将对象视为 JavaBean。)java.beans.Introspector 有自己的内部缓存,因此它可以 return 陈旧的信息,在这种情况下 BeansWrapper 将重新创建它的拥有 class 基于该陈旧信息的内省数据。至于 java.beans.Introspector 的缓存,它实际上是正确的,因为它建立在 Java 中的 classes 是不可变的假设之上。如果有什么东西违反了这个基本规则,它应该确保 java.beans.Introspector 的缓存被清除(以及许多其他缓存......),否则不仅仅是 FreeMarker 会破坏。例如,在 JRebel,他们付出了很多努力来清除各种缓存。我想 DCEVM 没有这方面的资源。那么,看来还是得自己打电话给Introspector.flushCaches()

更新:有一段时间(Java 7,也许 6)java.beans.Introspector 每个线程有一个缓存 group,所以你调用 flushCaches() 来自所有线程组。而这一切实际上都是实施细节,原则上可以随时更改。遗憾的是,Introspector.flushCaches() 的 JavaDoc 没有警告您...