Freemarker的removeIntrospectionInfo在模型热交换后无法与DCEVM一起使用

5

我正在使用Freemarker和DCEVM+HotSwapManager代理。这基本上允许我在添加/删除方法时热交换类。

一切都像魅力一样工作,直到Freemarker将热交换的类用作模型。即使反射显示该方法存在(在调试会话期间进行检查),它仍会抛出freemarker.ext.beans.InvalidPropertyException:没有这样的bean属性。

我正在使用

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

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

我做错了什么?我只需要强制freemarker放弃对已经获取的模型类/类的任何内省。

有什么办法吗?

更新

示例代码

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也没有任何效果。


@ddekany:我遇到了和Martin一样的问题。我正在使用2.3.19版本,因此升级应该可以解决问题。但是,removeFromClassIntrospectionCache的Javadoc注释让我感到困惑:“如果类仍将被使用,则缓存条目将被静默重新创建”-这是否意味着当模板缓存包含有关所讨论的类的映射时,内省不会获取其重新加载的版本?谢谢! - plesatejvlk
我正在清理我设置为freemarker配置对象的唯一BeansWrapper。请查看带有示例的更新。 - Martin Macak
@plesatejvlk:这句话的重点在于它的后半部分:“所以这不是一个危险的操作”。因此,那件事对你在这个问题上没有影响。 - ddekany
1
你可以尝试将方法转储到 beanInfo = java.beans.Introspector.getBeanInfo(aClass) 而不是使用反射,然后使用 beanInfo.getMethodDescriptors() 列出方法。同样的,通过 beanInfo.getPropertyDescriptors() 来列出属性,因为我猜测在模板中你可能正在访问这些属性(但我还没有看到)。也许问题在于 DCEVM 没有清理 Java 的 BeanIntrospector 缓存。这只是一个猜测。 - ddekany
1
更新2:调用Introspector.flushCaches解决了问题。@ddekany,您能否发布一个常规答案,以便获得相应的信用? - Martin Macak
显示剩余4条评论
1个回答

3

BeansWrapper(以及DefaultObjectWrapper等)内省缓存依赖于java.beans.Introspector.getBeanInfo(aClass),而不是反射。(因为它将对象视为JavaBean。)java.beans.Introspector具有其自己的内部缓存,因此它可能会返回过时的信息,在这种情况下,BeansWrapper将根据该过时信息重新创建其自己的类内省数据。至于java.beans.Introspector的缓存,实际上是正确的,因为它建立在Java中类是不可变的假设之上。如果有什么东西打破了这个基本规则,它应该确保清除java.beans.Introspector的缓存(以及许多其他缓存...),否则不仅FreeMarker会出问题。例如,在JRebel中,他们付出了很多努力来清除各种缓存。我猜DCEVM没有这方面的资源。所以,似乎你必须自己调用Introspector.flushCaches()

更新:一段时间以来(Java 7,可能是6),java.beans.Introspector每个线程组都有一个缓存,因此您必须从所有线程组调用flushCaches()。而这一切实际上是实现细节,原则上随时可能会发生变化。不幸的是,Introspector.flushCaches()的JavaDoc没有警告您...


我刚发现了一件非常奇怪的事情。我部署了这个解决方案到我们运行在WLS上的应用程序中,结果它停止工作了。我发现Introspector在HotSwapAgent调用的线程和来自WLS WorkerThreadPool的线程上返回的值是不同的。有任何想法为什么会发生这种情况以及如何修复它吗? - Martin Macak
很有趣。我尝试了一种基于你的观察的解决方案,而你再次证明是正确的。你的结论是基于观察还是有其他材料支持?我最近在stackoverflow上为此开了另一个问题:https://dev59.com/PYjca4cB1Zd3GeqPuUn8#28796646 - Martin Macak
我刚刚查看了 Introspector 的源代码。 - ddekany

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接