AspectJ加载时间weaver没有检测到所有类

wax*_*ing 13 java aop aspectj spring-aop

我在"aspectj"模式下使用Spring的声明式事务(@Transactional注释).它在大多数情况下都可以完全像它应该的那样工作,但对于其中一个它没有.我们可以称之为Lang(因为这就是它实际上所称的).

我已经能够确定加载时间织布机的问题.通过打开aop.xml中的debug和verbose日志记录,它列出了所有正在编织的类.Lang根本没有在日志中提到有问题的类.

然后我在顶部放置了一个断点Lang,导致Eclipse在Lang加载类时挂起线程.当LTW编织其他类时,这个断点被击中!所以我猜测它要么编织也要Lang失败并且不输出,或者其他一些类有一个引用强制它Lang在实际有机会编织它之前加载.

我不确定如何继续调试这个,因为我无法以较小的规模重现它.有关如何继续的任何建议?


更新:其他线索也欢迎.例如,LTW实际上如何运作?似乎有很多魔法发生.是否有任何选项可以从LTW获得更多的调试输出?我目前有:

<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">
Run Code Online (Sandbox Code Playgroud)

我忘了汤姆之前提到它:弹簧剂被用来允许LTW,即InstrumentationLoadTimeWeaver.


根据Andy Clement的建议,我决定检查AspectJ变压器是否甚至通过了这门课程.我放了一个断点ClassPreProcessorAgent.transform(..),看起来这个Lang类甚至都没有到达那个方法,尽管它被与其他类(Jetty的WebAppClassLoader的一个实例)相同的类加载器加载.

然后我接着断了一个断点InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..).甚至没有一个被击中Lang.我相信应该为所有加载的类调用该方法,无论他们使用什么类加载器.这开始看起来像:

  1. 我的调试有问题.可能Lang在Eclipse报告时没有加载
  2. Java bug?牵强附会,但我想它确实发生了.

接下来的线索:我打开了-verbose:class,看起来好像Lang 过早加载 - 可能是在变压器添加到Instrumentation之前.奇怪的是,我的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;
}
Run Code Online (Sandbox Code Playgroud)

特别是对类的metadata.hasAnnotatedMethods()调用getDeclaredMethods(),它加载该类中所有方法的所有参数类.我猜这可能不是问题的结束,因为我认为这些类应该被卸载.JVM是否可以出于不可知的原因缓存类实例?

wax*_*ing 7

好的,我已经解决了这个问题.从本质上讲,它与一些自定义扩展相结合是一个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

}
Run Code Online (Sandbox Code Playgroud)

现在,所有的bean定义已经阅读并后出现问题BeanDefinitionRegistryPostProcessor就开始踢.在这个阶段,一个叫班ConfigurationClassPostProcessor开始翻阅所有的bean定义,以搜索与注释的bean类@Configuration或与方法@Bean.

在读取bean的注释的过程中,它使用AnnotationMetadata接口.对于大多数常规bean,使用了一个被调用的子类AnnotationMetadataVisitor.但是,在解析bean定义时,如果您重写了getBeanClass()方法以返回类实例,就像我们一样,而是使用StandardAnnotationMetadata实例.当StandardAnnotationMetadata.hasAnnotatedMethods(..)被调用时,它调用Class.getDeclaredMethods(),这又导致的类加载器加载用作该类参数的所有类.装载这样的类没有被正确卸载,因而从未编织,因为这个注册的AspectJ的变压器之前发生.

现在,我的问题是我有一个这样的课程:

public class Something {
    private Lang lang;
    public void setLang(Lang lang) {
        this.lang = lang;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,我有一个Something使用我们的自定义解析的类bean 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

}
Run Code Online (Sandbox Code Playgroud)

当天的课程:getBeanClass除非你真的想要,否则不要覆盖它.实际上,除非你知道自己在做什么,否则不要尝试编写自己的BeanDefinitionParser.

鳍.