javaspringspring-bootload-time-weaving

LoadTimeWeaving in Spring Boot 1.4.3+ not working after upgrade from 1.4.2


I have a working Spring Boot project with LoadTimeWeaving enabled. When I tell Gradle to use Spring Boot 1.4.3 (or higher) instead of 1.4.2 the application can no longer start, giving an error of the form:

Error starting ApplicationContext. [...] Caused by: java.lang.NoSuchMethodError: [path.to.entity.or.entity.super.class]._persistence_set(Ljava/lang/String;Ljava/lang/Object;)V

_persistence_set can also be _init() or something else.

The exception, to my understanding, just means that the entity (or the super class of an entity) was not properly woven and hence the methods that should be woven into the class cannot be called (like _persistence_set(), _init() etc).

I used the argument -verbose:class to start my project with, which then prints every class when it is loaded. Turns out with Spring Boot 1.4.2 (where everything is working) the entities are loaded after the LoadTimeWeaver is initialized, while with Spring Boot 1.4.3+ multiple abstract base entity classes are loaded before that point. This means that they are not woven when they are loaded as the weaver isn't initialized yet, but they will not be woven after weaver-initialization because they only get loaded once.

Now its not obvious why these base entity classes are suddenly loaded before the weaver is initialized. Ideas?


Solution

  • I tried to solve this for days. This is what I found after debugging how spring initializes the application context (and with that the beans, weaver etc).

    We had some @Configuration classes that were defining @Bean that were typed with entities. Like this:

    @Configuration
    public class UserModuleConfiguration {
    
        @Bean
        public BasePresenter<EUser> userPresenter() {
            return new BasePresenter<EUser>() {
            };
        }
    }
    

    When I removed the entity from the return type of that method, everything worked. Note that since there is more than one of these configurations providing BasePresenter for different entities, you have to use @Qualifier or just instantiate them in their own right. Something like this:

    @Configuration
    public class UserModuleConfiguration {
    
        @Bean("userPresenter")
        public BasePresenter<?> userPresenter() {
            return new BasePresenter<EUser>() {
            };
        }
    }
    

    or

    @Component
    public class UserPresenter<EUser> extends BasePresenter{
    
    }
    

    Internally, before starting to 'evaluate' configurations spring is determining what beans are available to determine if specific configurations have to be loaded or not (see @ConditionalOnMissingBean and the like) and apparently in the process of doing this spring loads some of the super classes of the entities. I just wanted to leave this here, because I really did not find anything about this being a possible cause and it took me ages to find it.