我在“ aspectj”模式下使用Spring的声明式事务(@Transactional批注)。在大多数情况下,它的工作原理与应有的情况完全相同,但有一种情况却没有。我们可以调用它Lang(因为这就是它的实际名称)。
Lang
我已经能够找出问题的原因是加载时间的编织者。通过在aop.xml中打开调试和详细日志记录,它列出了所有编织的类。Lang确实在日志中根本没有提到有问题的类。
aop.xml
然后,我在的顶部放置一个断点Lang,导致Eclipse在Lang加载类时挂起线程。在LTW编织其他类时会遇到此断点!因此,我猜想它要么尝试编织Lang并失败并且不输出,要么其他类具有一个引用,迫使它Lang在实际上没有机会编织之前进行加载。
Eclipse
但是我不确定如何继续调试它,因为我无法以较小的比例复制它。有什么建议吗?
更新:也欢迎其他线索。例如,LTW实际如何工作?似乎发生了很多魔术。是否有任何选项可以从LTW获得更多调试输出?我目前有:
<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">
我忘记了要提到它:spring-agent用于允许LTW,即InstrumentationLoadTimeWeaver。
spring-agent
LTW
InstrumentationLoadTimeWeaver
根据Andy Clement的建议,我决定检查AspectJ转换器是否曾经通过该类。我在中设置了一个断点,尽管该类由与其他类(Jetty的WebAppClassLoader的一个实例)相同的类加载器加载,但该类ClassPreProcessorAgent.transform(..)似乎Lang从未达到该方法。
WebAppClassLoader
ClassPreProcessorAgent.transform(..)
然后,我继续在中设置一个断点InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..)。连一个都没有被击中Lang。而且我相信应该为所有已加载的类调用该方法,无论它们使用的是哪种类加载器。它开始看起来像:
InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..)
-verbose:class
Instrumentation
这意味着Spring是新的犯罪嫌疑人。在ConfigurationClassPostProcessor加载类中似乎要进行一些检查。这可能与我的问题有关。
ConfigurationClassPostProcessor
这些行ConfigurationClassBeanDefinitionReader导致Lang读取该类:
ConfigurationClassBeanDefinitionReader
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是否可能由于不可知的原因在缓存类实例?
metadata.hasAnnotatedMethods()
getDeclaredMethods()
好,我已经解决了问题。本质上,与一些自定义扩展一起,这是一个Spring问题。如果有人遇到类似的问题,我将尝试逐步解释正在发生的事情。
首先,我们BeanDefintionParser在项目中有一个自定义项。此类具有以下定义:
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。
BeanDefinitionRegistryPostProcessor
释@Configuration
@Bean
在读取bean的注释的过程中,它使用AnnotationMetadata接口。对于大多数常规bean,使用一个称为的子类AnnotationMetadataVisitor。但是,在解析Bean定义时,如果您已重写getBeanClass()方法以返回类实例(如我们以前的方法),则将StandardAnnotationMetadata使用实例。当StandardAnnotationMetadata.hasAnnotatedMethods(..)被调用时,它调用Class.getDeclaredMethods(),这又导致的类加载器加载用作该类参数的所有类。以这种方式加载的类未正确卸载,因此从不进行编织,因为这是在AspectJ转换器注册之前发生的。
AnnotationMetadata
AnnotationMetadataVisitor
getBeanClass()
StandardAnnotationMetadata
StandardAnnotationMetadata.hasAnnotatedMethods(..)
Class.getDeclaredMethods()
现在,我的问题是我上了这样的课:
public class Something { private Lang lang; public void setLang(Lang lang) { this.lang = lang; } } 然后,我有了一个Something使用我们的custom解析的类bean ControllerBeanDefinitionParser。这触发了错误的注释检测过程,从而触发了意外的类加载,这意味着AspectJ从未有过编织的机会Lang。
Something
custom
bean ControllerBeanDefinitionParser
解决方案是不重写getBeanClass(..),而是重写getBeanClassName(..),根据文档,这是更可取的:
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。
getBeanClass