tangguo

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

java

我在“ aspectj”模式下使用Spring的声明式事务(@Transactional批注)。在大多数情况下,它的工作原理与应有的情况完全相同,但有一种情况却没有。我们可以调用它Lang(因为这就是它的实际名称)。

我已经能够找出问题的原因是加载时间的编织者。通过在aop.xml中打开调试和详细日志记录,它列出了所有编织的类。Lang确实在日志中根本没有提到有问题的类。

然后,我在的顶部放置一个断点Lang,导致EclipseLang加载类时挂起线程。在LTW编织其他类时会遇到此断点!因此,我猜想它要么尝试编织Lang并失败并且不输出,要么其他类具有一个引用,迫使它Lang在实际上没有机会编织之前进行加载。

但是我不确定如何继续调试它,因为我无法以较小的比例复制它。有什么建议吗?

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

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

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

根据Andy Clement的建议,我决定检查AspectJ转换器是否曾经通过该类。我在中设置了一个断点,尽管该类由与其他类(Jetty的WebAppClassLoader的一个实例)相同的类加载器加载,但该类ClassPreProcessorAgent.transform(..)似乎Lang从未达到该方法。

然后,我继续在中设置一个断点InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..)。连一个都没有被击中Lang。而且我相信应该为所有已加载的类调用该方法,无论它们使用的是哪种类加载器。它开始看起来像:

  1. 我的调试有问题。Lang当Eclipse报告它已被加载时,可能未加载
  2. Java错误?牵强,但我想它确实发生了。
    下一条线索:我打开电源-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是否可能由于不可知的原因在缓存类实例?


阅读 321

收藏
2020-10-26

共1个答案

小编典典

好,我已经解决了问题。本质上,与一些自定义扩展一起,这是一个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定义,以搜索带有注释@Configuration或具有方法的Bean类@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使用我们的custom解析的类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

}

每日经验教训:getBeanClass除非您真的是认真的,否则请不要覆盖。实际上,除非您知道自己在做什么,否则不要尝试编写自己的BeanDefinitionParser。

2020-10-26