我在“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
被击中。我相信应该为所有加载的类调用该方法,无论他们使用什么类加载器。这开始看起来像:
Lang
下一条线索:我打开了-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是否可以出于不可知的原因缓存类实例?
答案 0 :(得分:7)
首先,我们的项目中有一个自定义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)