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

时间:2010-09-01 15:08:35

标签: 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">

我之前忘了提到它: spring-agent 用于允许LTW,即InstrumentationLoadTimeWeaver


根据安迪克莱门特的建议,我决定检查AspectJ变压器是否甚至通过了课程。我在ClassPreProcessorAgent.transform(..)中放置了一个断点,似乎Lang类甚至都没有到达该方法,尽管它与其他类(Jetty的WebAppClassLoader的一个实例)由同一个类加载器加载。

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

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

  4. 下一条线索:我打开了-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;
    }
    

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

3 个答案:

答案 0 :(得分: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

}

现在,在读取所有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;
    }
}

然后,我有一个使用我们的自定义Something解析的类ControllerBeanDefinitionParser的bean。这触发了错误的注释检测过程,该过程触发了意外的类加载,这意味着AspectJ从未有机会编织Lang

解决方案是不覆盖getBeanClass(..),而是覆盖getBeanClassName(..),根据文档更可取:{/ p>

private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

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

// code to parse XML omitted for brevity

}

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

翅片。

答案 1 :(得分:4)

如果在-verbose / -debug输出中没有提到你的类,那就告诉我它没有被你认为的加载器加载。你能100%确定'Lang'不在层次结构中更高级别的类加载器的类路径中吗?当您触发断点时,哪个类加载器正在加载Lang?

另外,你没有提到AspectJ版本 - 如果你在1.6.7上,除了一个简单的aop.xml之外什么都有问题。你应该是1.6.8或1.6.9。

  

如何实际工作?

简单地说,为每个可能想编织代码的类加载器创建一个AspectJ weaver。在为类定义VM之前,会询问AspectJ是否要修改类的字节。 AspectJ通过有问题的类加载器查看它可以“看到”(作为资源)的任何aop.xml文件,并使用它们进行自我配置。配置完成后,它会根据指定编排方面,同时考虑所有include / exclude子句。

安迪克莱门特 AspectJ项目负责人

答案 2 :(得分:1)

选项1)Aspect 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”但它可能在那里(或一个bug)