-
Notifications
You must be signed in to change notification settings - Fork 41.5k
Description
After upgrading Spring Boot from 2.1 to 2.2 our app failed to start.
Previous working configuration:
- Spring Boot: 2.1.12.RELEASE
- Spring Framework: 5.1.13.RELEASE
- EclipseLink: 2.7.6 (Latest)
New failing configuration:
- Spring Boot: 2.2.4.RELEASE
- Spring Framework: 5.2.3.RELEASE
- EclipseLink: 2.7.6 (Latest)
The symptom - error similar to this:
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
com.company.core.domain.DomainSpecificEntityA._persistence_get_propertyB(DomainSpecificEntityA.java)
The following method did not exist:
'void com.company.core.domain.DomainSpecificEntityA._persistence_checkFetched(java.lang.String)'
These methods are normally generated during dynamic weaving performed by EclipseLink (JPA implementation).
org.eclipse.persistence.internal.jpa.weaving.PersistenceWeaver
is that class that performs this weaving.
This PersistenceWeaver
is registered in org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo#addTransformer
during container entity manager factory creation.
Comparing the list of classes that got instrumented beween Spring Boot 2.1 and 2.2 revealed that the list was shorter in version 2.2.
The difference was due to the fact that some of the JPA entity classes were already loaded by the classloader, earlier than PersistenceWeaver
's class transformation was added to the classloader.
I tracked down the location where these classes were getting loaded first and the stack trace was like this:
> java.lang.Class#getDeclaredConstructors // once this executes the entity class is available in current classloader, excluding it from later instrumentation
> org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider#findConstructorBindingAnnotatedConstructor
> org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider#getBindConstructor(org.springframework.boot.context.properties.bind.Bindable<?>, boolean)
> org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod#forType
> org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator#validate
> ...
The bean that was getting validated was similar to this:
// This class is registered as a Spring Bean:
public class ProjectManagerImp implements ProjectManager { ... }
// This is our custom interface extending our another (more generic) interface
public interface ProjectManager extends Service<Project> { ... }
// This is JPA entity extending an abstract JPA MappedSuperclass.
// Both of these get loaded into the classloader, excluding them from further instrumentation
public class Project extends PersistentDomainObjectWithMetaData { ... }
Workaround
Removing org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator
made our app start and work as expected. We simply added this to our configuration:
@Bean
public BeanDefinitionRegistryPostProcessor offendingValidatorRemovingBeanDefinitionRegistryPostProcessor() {
return new BeanDefinitionRegistryPostProcessor() {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.removeBeanDefinition("org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator");
}
};
}
Additional info
- Git history of
ConfigurationPropertiesBeanDefinitionValidator
- The issue with which
ConfigurationPropertiesBeanDefinitionValidator
was added in Spring Boot 2.2.0.RC1: Wrong semantic for immutable @ConfigurationProperties contributed via @Import #17831 - A similar (fixed) bug in Spring Data JPA: Entity classes loaded before EclipseLink LTW is initialized - maybe a similar fix is needed here too?