AspectJ加载时间weaver不会检测到所有类
问题内容:
我在“ aspectj”模式下使用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
。
根据Andy Clement的建议,我决定检查AspectJ转换器是否曾经通过该类。我在中设置了一个断点,尽管该类由与其他类(Jetty的WebAppClassLoader
的一个实例)相同的类加载器加载,但该类ClassPreProcessorAgent.transform(..)
似乎Lang从未达到该方法。
然后,我继续在中设置一个断点InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..)
。连一个都没有被击中Lang。而且我相信应该为所有已加载的类调用该方法,无论它们使用的是哪种类加载器。它开始看起来像:
- 我的调试有问题。Lang当Eclipse报告它已被加载时,可能未加载
- 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是否可能由于不可知的原因在缓存类实例?
问题答案:
好,我已经解决了问题。本质上,与一些自定义扩展一起,这是一个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。