Description
Chip Killmar opened SPR-12688 and commented
During the creation of one of our beans, we call getBeansWithAnnotation(Class<? extends Annotation> annotationType)
to get a map of lazy-loaded annotated beans. This method instantiates these annotated beans using getBean(String beanName)
.
In our configuration, we're dynamically registering Spring beans using a post processor bean. The post processor bean receives a callback whenever one of these annotated beans is created, and dynamically registers new bean definitions.
The problem is that dynamic registration modifies List<String> beanDefinitionNames
while Spring is iterating over it, causing a java.util.ConcurrentModificationException
to be thrown:
Caused by: java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansWithAnnotation(DefaultListableBeanFactory.java:566)
at com.homeaway.TestConfiguration.testBean(TestConfiguration.java:43)
at com.homeaway.TestConfiguration$$EnhancerBySpringCGLIB$$c0d0df0c.CGLIB$testBean$0(<generated>)
at com.homeaway.TestConfiguration$$EnhancerBySpringCGLIB$$c0d0df0c$$FastClassBySpringCGLIB$$7a010a97.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:309)
at com.homeaway.TestConfiguration$$EnhancerBySpringCGLIB$$c0d0df0c.testBean(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
... 36 more
Spring 3.2.2.RELEASE doesn't exhibit the same behavior because it creates a local set of bean names and iterates over that:
// 3.2.2.RELEASE, DefaultListableBeanFactory.java
public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
Set<String> beanNames = new LinkedHashSet<String>(getBeanDefinitionCount());
beanNames.addAll(Arrays.asList(getBeanDefinitionNames()));
beanNames.addAll(Arrays.asList(getSingletonNames()));
Map<String, Object> results = new LinkedHashMap<String, Object>();
for (String beanName : beanNames) {
if (findAnnotationOnBean(beanName, annotationType) != null) {
results.put(beanName, getBean(beanName));
}
}
return results;
}
// 4.1.4.RELEASE, DefaultListableBeanFactory.java
@Override
public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
Map<String, Object> results = new LinkedHashMap<String, Object>();
for (String beanName : this.beanDefinitionNames) {
BeanDefinition beanDefinition = getBeanDefinition(beanName);
if (!beanDefinition.isAbstract() && findAnnotationOnBean(beanName, annotationType) != null) {
results.put(beanName, getBean(beanName));
}
}
for (String beanName : this.manualSingletonNames) {
if (!results.containsKey(beanName) && findAnnotationOnBean(beanName, annotationType) != null) {
results.put(beanName, getBean(beanName));
}
}
return results;
}
As a workaround for Spring 4.1.4.RELEASE, I've implemented a separate version of getBeansWithAnnotation(Class<? extends Annotation> annotationType
as a custom method outside of Spring using the logic in 3.2.2.RELEASE.
I've attached an isolated test configuration and unit test that demonstrates the problem.
Affects: 4.1.4
Attachments:
- spr-12688.tar.gz (2.52 kB)
Issue Links:
- DefaultListableBeanFactory should allow efficient access to current bean names [SPR-12404] #17012 DefaultListableBeanFactory should allow efficient access to current bean names
- ConcurrentModificationException when executing AutowireCapableBeanFactory.createBean [SPR-13493] #18071 ConcurrentModificationException when executing AutowireCapableBeanFactory.createBean