I am using Spring\'s declarative transactions (the @Transactional annotation) in \"aspectj\" mode. It works in most cases exactly like it should, but for one it doesn\'t. We can
OK, I have solved the problem. Essentially, it is a Spring problem in conjunction with some custom extensions. If anyone comes across something similar, I will try to explain step by step what is happening.
First of all, we have a custom BeanDefintionParser
in our project. This class had the following definition:
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
}
Now, the problem occurs after all bean definition have been read and BeanDefinitionRegistryPostProcessor
begins to kick in. At this stage, a class called ConfigurationClassPostProcessor
starts looking through all bean definitions, to search for bean classes annotated with @Configuration
or that have methods with @Bean
.
In the process of reading annotations for a bean, it uses the AnnotationMetadata
interface. For most regular beans, a subclass called AnnotationMetadataVisitor
is used. However, when parsing the bean definitions, if you have overriden the getBeanClass()
method to return a class instance, like we had, instead a StandardAnnotationMetadata
instance is used. When StandardAnnotationMetadata.hasAnnotatedMethods(..)
is invoked, it calls Class.getDeclaredMethods()
, which in turn causes the class loader to load all classes used as parameters in that class. Classes loaded this way are not correctly unloaded, and thus never weaved, since this happens before the AspectJ transformer registered.
Now, my problem was that I had a class like so:
public class Something {
private Lang lang;
public void setLang(Lang lang) {
this.lang = lang;
}
}
Then, I had a bean of class Something
that was parsed using our custom ControllerBeanDefinitionParser
. This triggered the wrong annotation detection procedure, which triggered unexpected class loading, which meant that AspectJ never got a chance to weave Lang
.
The solution was to not override getBeanClass(..)
, but instead override getBeanClassName(..)
, which according to the documentation is preferable:
private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected String getBeanClassName(Element element) {
return element.getAttribute("class");
}
// code to parse XML omitted for brevity
}
Lesson of the day: Do not override getBeanClass
unless you really mean it. Actually, don't try to write your own BeanDefinitionParser unless you know what you're doing.
Fin.