AspectJ Время загрузки ткач не обнаруживает все классы - PullRequest
13 голосов
/ 01 сентября 2010

Я использую декларативные транзакции Spring (аннотацию @Transactional) в режиме аспекта. В большинстве случаев он работает точно так же, как и должен, но для одного - нет. Мы можем назвать это Lang (потому что так оно и называется).

Мне удалось точно определить проблему с ткачом времени загрузки. Включив отладку и подробное ведение журнала в aop.xml, он перечисляет все классы, которые были сплетены. Проблемный класс Lang действительно не упоминается в журналах вообще.

Затем я ставлю точку останова на вершину Lang, в результате чего Eclipse приостанавливает поток при загрузке класса Lang. Эта точка останова достигнута, пока LTW ткут другие классы! Поэтому я предполагаю, что он либо пытается соткать Lang, но не выводит его, либо у какого-то другого класса есть ссылка, которая заставляет его загружать Lang, прежде чем он действительно получит шанс соткать его.

Однако я не уверен, как продолжать отлаживать это, поскольку я не могу воспроизвести его в меньшем масштабе. Любые предложения о том, как продолжить?


Обновление: Другие подсказки также приветствуются. Например, как на самом деле работает LTW? Кажется, что происходит много магии. Есть ли варианты, чтобы получить еще больше отладочной информации от LTW? У меня сейчас есть:

<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">

Я забыл упомянуть об этом раньше: spring-agent используется для разрешения LTW, то есть InstrumentationLoadTimeWeaver.


На основании предложений Энди Клемента я решил проверить, прошел ли когда-либо преобразователь AspectJ класс. Я установил точку останова в ClassPreProcessorAgent.transform(..), и кажется, что класс Lang даже не достигает этого метода, несмотря на то, что он загружается тем же загрузчиком классов, что и другие классы (экземпляр WebAppClassLoader Jetty).

Затем я установил точку останова в InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..). Даже этот не был поражен за Lang. И я считаю, что метод должен вызываться для всех загруженных классов, независимо от того, какой загрузчик классов они используют. Это начинает выглядеть так:

  1. Проблема с моей отладкой. Возможно Lang не загружается в то время, когда Eclipse сообщает, что это
  2. Ошибка Java? Придурковат, но, полагаю, это произойдет.

Следующая подсказка: я включил -verbose:class, и кажется, что Lang - это , загружаемая преждевременно - возможно, до того, как трансформатор будет добавлен в Инструментарий. Как ни странно, моя точка останова Eclipse не перехватывает эту загрузку.

Это означает, что Spring - новый подозреваемый. в ConfigurationClassPostProcessor, похоже, есть некоторая обработка, которая загружает классы для их проверки. Это может быть связано с моей проблемой.


Эти строки в ConfigurationClassBeanDefinitionReader заставляют читать класс Lang:

else if (metadata.isAnnotated(Component.class.getName()) ||
        metadata.hasAnnotatedMethods(Bean.class.getName())) {
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    return true;
}

В частности, metadata.hasAnnotatedMethods() вызывает getDeclaredMethods() для класса, который загружает все классы параметров всех методов в этом классе. Я предполагаю, что это, возможно, не конец проблемы, потому что я думаю, что классы должны быть выгружены. Может ли JVM кэшировать экземпляр класса по неизвестным причинам?

Ответы [ 3 ]

7 голосов
/ 07 сентября 2010

ОК, я решил проблему.По сути, это проблема Spring в сочетании с некоторыми пользовательскими расширениями.Если кто-нибудь сталкивается с чем-то похожим, я постараюсь шаг за шагом объяснить, что происходит.

Прежде всего, у нас есть пользовательский BeanDefintionParser в нашем проекте.Этот класс имел следующее определение:

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    protected Class<?> getBeanClass(Element element) {
        try {
            return Class.forName(element.getAttribute("class"));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class " + element.getAttribute("class") + "not found.", e);
        }
    }

// code to parse XML omitted for brevity

}

Теперь проблема возникает после того, как все определения бина прочитаны и BeanDefinitionRegistryPostProcessor начинает срабатывать. На этом этапе класс с именем ConfigurationClassPostProcessor начинает просматриватьвсе определения bean-компонентов, для поиска классов bean-компонентов, аннотированных @Configuration или имеющих методы с @Bean.

В процессе чтения аннотаций для bean-компонента используется интерфейс AnnotationMetadata.Для большинства обычных bean-компонентов используется подкласс AnnotationMetadataVisitor.Однако при синтаксическом анализе определений бина, если вы переопределили метод getBeanClass() для возврата экземпляра класса, как у нас, вместо этого используется экземпляр StandardAnnotationMetadata.Когда вызывается StandardAnnotationMetadata.hasAnnotatedMethods(..), он вызывает Class.getDeclaredMethods(), что, в свою очередь, заставляет загрузчик классов загрузить все классы, используемые в качестве параметров в этом классе.Классы, загруженные таким образом, выгружаются неправильно и, следовательно, никогда не переплетаются, поскольку это происходит до регистрации преобразователя AspectJ.

Теперь моя проблема заключалась в том, что у меня был такой класс:

public class Something {
    private Lang lang;
    public void setLang(Lang lang) {
        this.lang = lang;
    }
}

Затем у меня был бин класса Something, который был проанализирован с помощью нашего пользовательского ControllerBeanDefinitionParser.Это вызвало неправильную процедуру обнаружения аннотаций, которая вызвала неожиданную загрузку классов, что означало, что у AspectJ никогда не было шанса переплетать Lang.

Решением было не переопределить getBeanClass(..), а вместо переопределить getBeanClassName(..), что в соответствии с документацией является предпочтительным:

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    protected String getBeanClassName(Element element) {
        return element.getAttribute("class");
    }

// code to parse XML omitted for brevity

}

Урок дня: не переопределяйте getBeanClass, если вы действительно не имеете это в виду.На самом деле, не пытайтесь написать свой собственный BeanDefinitionParser, если вы не знаете, что делаете.

Fin.

4 голосов
/ 07 сентября 2010

Если ваш класс не указан в выводе -verbose / -debug, это говорит о том, что он не загружается загрузчиком, который, как вы думаете, является.Можете ли вы быть на 100% уверены, что «Lang» не находится на пути к классам загрузчика классов выше в иерархии?Какой загрузчик классов загружает Lang в тот момент, когда вы запускаете свою точку останова?

Кроме того, вы не упоминаете версию AspectJ - если вы используете 1.6.7, у которой были проблемы с ltw для чего угодно, кроме тривиальных действий.xml.Вы должны быть на 1.6.8 или 1.6.9.

Как на самом деле работает ltw?

Проще говоря, ткач AspectJ создается для каждого загрузчика классов, который может потребоватьсяткать код.AspectJ спрашивают, хочет ли он изменить байты для класса, прежде чем он будет определен для виртуальной машины.AspectJ просматривает любые файлы aop.xml, которые он может «видеть» (как ресурсы) через рассматриваемый загрузчик классов, и использует их для самостоятельной настройки.После настройки он вплетает аспекты, как указано, с учетом всех предложений include / exclude.

Энди Клемент
Руководитель проекта AspectJ

1 голос
/ 01 сентября 2010

Вариант 1) Аспект J с открытым исходным кодом. Взломайте его и посмотрите, что происходит.

Вариант 2) Переименуйте свой класс в Bang, посмотрите, начнет ли он работать

Я бы не удивился, если бы там было жесткое кодирование, чтобы пропустить "lang", хотя я не могу сказать, почему.

Редактировать -

Увидеть подобный код в источнике

        if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object
            superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class);
            superclassname = Utility.compactClassName(superclassname, false);

} else {
            superclassname = "java.lang.Object";
        }

Похоже, они пытаются пропустить переплетение java.lang.stuff .... ничего не вижу только для "lang", но это может быть (или ошибка)

...