Freemarker removeIntrospectionInfo не работает с DCEVM после горячей замены модели

Я использую 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 во время выполнения не даст никакого эффекта.


person Martin Macak    schedule 25.02.2015    source источник
comment
Предполагая, что вы вызываете метод на BeansWrapper, который действительно используется, он должен работать. Но removeFromClassIntrospectionCache(Class clazz) - это общедоступный метод, почему вы так его называете? Если это старый FreeMarker, сначала попробуйте обновить.   -  person ddekany    schedule 25.02.2015
comment
@ddekany: Я столкнулся с той же проблемой, что и Мартин. Я использую версию 2.3.19, поэтому обновление должно помочь. Однако комментарий Javadoc для removeFromClassIntrospectionCache меня озадачивает: ...If the class will be still used, the cache entry will be silently re-created - означает ли это, что когда кеш шаблона содержит сопоставление для рассматриваемого класса, самоанализ не получит его перезагруженную версию? Спасибо!   -  person plesatejvlk    schedule 26.02.2015
comment
Я очищаю единственный BeansWrapper, который я установил для объекта конфигурации freemarker. См. Обновление с примером.   -  person Martin Macak    schedule 26.02.2015
comment
@plesatejvlk: Смысл этого предложения - вторая его половина: так что это не опасная операция. Так что это не влияет на вас в этом выпуске.   -  person ddekany    schedule 26.02.2015
comment
Где вы сбрасываете методы, что если вы используете beanInfo = java.beans.Introspector.getBeanInfo(aClass) вместо прямого отражения, а затем перечислите методы с помощью beanInfo.getMethodDescriptors(). Также свойства с beanInfo.getPropertyDescriptors(), потому что я предполагаю, что вы действительно получаете доступ к свойствам в шаблоне (еще не видели его). Возможно, проблема в том, что DCEVM не очищает BeanIntrospector кеш Java. Просто дикая догадка.   -  person ddekany    schedule 26.02.2015
comment
Проверено прямо сейчас, и BeanInfo для конкретного класса модели обновляется сразу после горячей замены DCEVM.   -  person Martin Macak    schedule 26.02.2015
comment
ОБНОВИТЬ!!!!! Я также проверил дескрипторы propertyDescriptors, чтобы убедиться, что они НЕ обновляются. Это может быть проблемой?   -  person Martin Macak    schedule 26.02.2015
comment
ОБНОВЛЕНИЕ 2: вызов Introspector.flushCaches решил проблему. @ddekany, не могли бы вы опубликовать обычный ответ, чтобы получить за это признание?   -  person Martin Macak    schedule 26.02.2015
comment
Я счастлив, что это сработало (но все равно обновите FreeMarker ...) Ответ опубликован.   -  person ddekany    schedule 27.02.2015


Ответы (1)


Кэш интроспекции BeansWrapperDefaultObjectWrapper и т. Д.) Полагается на java.beans.Introspector.getBeanInfo(aClass), а не на отражение. (Это потому, что он обрабатывает объекты как JavaBeans.) java.beans.Introspector имеет свой собственный внутренний кеш, поэтому он может возвращать устаревшую информацию, и в этом случае BeansWrapper просто воссоздает данные интроспекции своего собственного класса на основе этой устаревшей информации. Что касается кеширования java.beans.Introspector, это на самом деле правильно, поскольку оно основано на предположении, что классы в Java неизменяемы. Если что-то нарушает это основное правило, это должно гарантировать, что кеш java.beans.Introspector очищен (и многие другие кеши ...), иначе сломается не только FreeMarker. Например, в JRebel они приложили много усилий для очистки всех видов кешей. Думаю, у DCEVM нет для этого ресурсов. Итак, похоже, вы должны сами позвонить Introspector.flushCaches().

Обновление: какое-то время (Java 7, может быть, 6) java.beans.Introspector имеет один кеш на группу потока, поэтому у вас есть вызов flushCaches() из всех групп потоков. И все это фактически детали реализации, которые, в принципе, могут измениться в любой момент. И, к сожалению, JavaDoc Introspector.flushCaches() вас не предупреждает ...

person ddekany    schedule 26.02.2015
comment
Я только что обнаружил очень странную вещь. Я развернул это решение в нашем приложении, которое работает на WLS, и оно перестало работать. Я обнаружил, что Introspector возвращает другие значения в потоке, который вызывается HotSwapAgent и потоком из WLS WorkerThreadPool. Есть идеи, почему это происходит и как это исправить? - person Martin Macak; 27.02.2015
comment
Это интересно. Я попробовал решение, основанное на ваших наблюдениях, и вы снова оказались правы. Был ли ваш вывод основан на наблюдениях или есть какие-то материалы к этому? Недавно я открыл еще один вопрос SO на stackoverflow.com/questions/28768348/ - person Martin Macak; 01.03.2015
comment
Я только что посмотрел Introspector исходный код. - person ddekany; 01.03.2015