Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Could not autowire @Configurable bean when running testng tests with two contexts [SPR-16227] #20775

Closed
spring-issuemaster opened this issue Nov 22, 2017 · 7 comments

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Nov 22, 2017

cda opened SPR-16227 and commented

Environment: Spring boot 1.5.8.RELEASE, TestNg 6.11

Use case:
Aspects are weaved at compile time.
I run a set of tests, having some classes annotated with @SpringBootTest, and one with @WebMvcTest.

From my investigation:
Tests run fine with first application context made for tests with @SpringBootTest, but reaching the class with @WebMvcTest, a new context is created, with a new bean factory.
When configuring the new context, a AnnotationBeanConfigurerAspect bean is requested from SpringConfiguredConfiguration.beanConfigurerAspect():

public AnnotationBeanConfigurerAspect beanConfigurerAspect() {
     return AnnotationBeanConfigurerAspect.aspectOf();
}

AnnotationBeanConfigurerAspect.aspectOf() resturns the same instance (singleton pattern) as the previous context was using:
(decompiled code)

public static AnnotationBeanConfigurerAspect aspectOf() {
    if (ajc$perSingletonInstance == null) {
        throw new NoAspectBoundException("org_springframework_beans_factory_aspectj_AnnotationBeanConfigurerAspect", ajc$initFailureCause);
    } else {
        return ajc$perSingletonInstance;
    }
}

After returning the same AnnotationBeanConfigurerAspect instance, the old context bean factory from the BeanConfigurerSupport member is replaced with the newly created one:

public void setBeanFactory(BeanFactory beanFactory) {
     this.beanConfigurerSupport.setBeanWiringInfoResolver(new AnnotationBeanWiringInfoResolver());
     this.beanConfigurerSupport.setBeanFactory(beanFactory);
}

Testing continues for other @SpringBootTest classes, and it fails autowiring @Configurable object, due to replaced bean factory, with the following stack trace:

Error creating bean with name 'pse.shop.product.ProductPersister': Unsatisfied dependency expressed through field 'descriptionService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'pse.shop.product.description.DescriptionService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'pse.shop.product.ProductPersister': Unsatisfied dependency expressed through field 'descriptionService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'pse.shop.product.description.DescriptionService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:386)
	at org.springframework.beans.factory.wiring.BeanConfigurerSupport.configureBean(BeanConfigurerSupport.java:141)
	at org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect.configureBean(AnnotationBeanConfigurerAspect.aj:63)
	at org.springframework.beans.factory.aspectj.AbstractDependencyInjectionAspect.ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(AbstractDependencyInjectionAspect.aj:88)
	at pse.shop.product.ProductPersister.<init>(ProductPersister.java:54)
	at pse.shop.product.ProductService.saveProduct_aroundBody0(ProductService.java:52)
	at pse.shop.product.ProductService$AjcClosure1.run(ProductService.java:1)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96cproceed(AbstractTransactionAspect.aj:66)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect$AbstractTransactionAspect$1.proceedWithInvocation(AbstractTransactionAspect.aj:72)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(AbstractTransactionAspect.aj:70)
	at pse.shop.product.ProductService.saveProduct(ProductService.java:52)
	at pse.shop.persistance.ProductBuilder.build_aroundBody0(ProductBuilder.java:99)
	at pse.shop.persistance.ProductBuilder$AjcClosure1.run(ProductBuilder.java:1)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96cproceed(AbstractTransactionAspect.aj:66)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect$AbstractTransactionAspect$1.proceedWithInvocation(AbstractTransactionAspect.aj:72)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(AbstractTransactionAspect.aj:70)
	at pse.shop.persistance.ProductBuilder.build(ProductBuilder.java:78)
	at pse.shop.product.price.PriceHistoryPersistanceTest.testGetBetweenDate_aroundBody6(PriceHistoryPersistanceTest.java:77)
	at pse.shop.product.price.PriceHistoryPersistanceTest$AjcClosure7.run(PriceHistoryPersistanceTest.java:1)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96cproceed(AbstractTransactionAspect.aj:66)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect$AbstractTransactionAspect$1.proceedWithInvocation(AbstractTransactionAspect.aj:72)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
	at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(AbstractTransactionAspect.aj:70)
	at pse.shop.product.price.PriceHistoryPersistanceTest.testGetBetweenDate(PriceHistoryPersistanceTest.java:77)
	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:498)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:108)
	at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:209)
	at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:175)
	at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:221)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:657)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:869)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1193)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:126)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
	at org.testng.TestRunner.privateRun(TestRunner.java:744)
	at org.testng.TestRunner.run(TestRunner.java:602)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:380)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:375)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:340)
	at org.testng.SuiteRunner.run(SuiteRunner.java:289)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1301)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1226)
	at org.testng.TestNG.runSuites(TestNG.java:1144)
	at org.testng.TestNG.run(TestNG.java:1115)
	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.runTests(TestNGTestClassProcessor.java:129)
	at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.stop(TestNGTestClassProcessor.java:88)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	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:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
	at com.sun.proxy.$Proxy1.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:120)
	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:498)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
	at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'pse.shop.product.description.DescriptionService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1493)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
	... 82 more

Affects: 4.3.12

0 votes, 5 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Nov 23, 2017

Stéphane Nicoll commented

Spring Boot issues (the use of slice test annotation) are not handled in this issue tracker. Rather than the analysis with bits of code, please share a sample that we can run as it is the most effective way to make sure we're on the same page. Once you have that, create an issue in the Spring Boot tracker

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 26, 2018

Andy Wilkinson commented

I think this should be re-opened as the problem occurs without Spring Boot being involved. Here's a fork of the sample which reproduces the problem without Spring Boot's involvement: https://github.com/wilkinsona/spring-boot-issue-11123.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Apr 26, 2018

Andy Wilkinson commented

I believe that spring-projects/spring-boot#11461, while exhibiting a different symptom, has the same underlying cause where the Framework's using a stale bean factory.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Jul 9, 2018

Łukasz Świątek commented

I encounterd the same problem and one of my team mates, came up with custom test listener to bypass this problem

 

public class ReinitConfigurableAspectTestExecutionListener extends AbstractTestExecutionListener {

    private static final Logger log = LoggerFactory.getLogger(ReinitConfigurableAspectTestExecutionListener.class);

    @Configurable
    public static class TestClass implements BeanFactoryAware {

        BeanFactory bf;

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.bf = beanFactory;
        }

    }

    @SuppressWarnings("resource")
    @Override
    public void prepareTestInstance(TestContext testContext) throws Exception {
        try {
            ApplicationContext appCtx = testContext.getApplicationContext();
            AnnotationBeanConfigurerAspect configurableAspect = appCtx.getBean(AnnotationBeanConfigurerAspect.class);
            TestClass testBean1 = new TestClass();
            int originalhc = System.identityHashCode(testBean1.bf);

            if (appCtx instanceof ConfigurableApplicationContext) {
                ConfigurableApplicationContext configurableAppCtx = (ConfigurableApplicationContext) appCtx;
                ConfigurableListableBeanFactory bf = configurableAppCtx.getBeanFactory();
                configurableAspect.setBeanFactory(bf);
                int targethc = System.identityHashCode(bf);
                TestClass testBean2 = new TestClass();
                int newhc = System.identityHashCode(testBean2.bf);
                if (newhc != targethc) {
                    log.error("Switch FAIL : {} => {} (is {})", originalhc, targethc, newhc);
                } else {
                    log.info("Switch OK : {} => {}", originalhc, newhc);
                }
            } else {
                log.warn("Test application context not an instance of of ConfigurableApplicationContext, was :"
                                                + appCtx.getClass());
            }
        } catch (NoSuchBeanDefinitionException exc) {
            log.warn("No AnnotationBeanConfigurerAspect bean defined in context, skipping");
        }
    }

} 

while this is just a bypass, it might be usefull when next person googles this problem

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Aug 9, 2018

cda commented

Also found an issue with transaction manager that seems related, using the same environment. This causes inconsistent entity manager behavior (dual context vs single context).

The odd thing I found while debugging is that transaction manager cache contains an invalid transaction manager:

TransactionAspectSupport.java(line 380) (spring-tx-4.3.14.RELEASE)

PlatformTransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
   defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
-> if (defaultTransactionManager == null) {
      defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
      this.transactionManagerCache.putIfAbsent(
            DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
   }
}

In the above code, when reaching the marked line, defaultTransactionManager contains a different transaction manager from this.beanFactory.getBean(PlatformTransactionManager.class), because the beanFactory has been changed.

I think this issue should be rename as it is broader.

I am currently having second thoughts about using Spring with AspectJ as this is the third issue I discover.

@cdalexndr

This comment has been minimized.

Copy link

@cdalexndr cdalexndr commented Apr 9, 2019

As a workaround for transaction manager issue above, I added the following to the workaround of Łukasz Świątek:

AnnotationTransactionAspect transactionAspect = bf.getBean( AnnotationTransactionAspect.class );
transactionAspect.destroy(); //clears cache and resets bean factory
transactionAspect.setBeanFactory( bf );
@sbrannen

This comment has been minimized.

Copy link
Member

@sbrannen sbrannen commented Apr 9, 2019

Closing this as a duplicate of #11019 and #10789.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.