Skip to content

InjectionPoint not propagated by AutowiredAnnotationBeanPostProcessor's cached argument resolution [SPR-14400] #18971

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

Closed
spring-projects-issues opened this issue Jun 25, 2016 · 2 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Jun 25, 2016

Sebastian Staack opened SPR-14400 and commented

When there is a prototype bean with a field annotated with @Autowired whose value is a prototype bean created in dependence of its injection point it seams that the the injection point isn't propagated.

Test setup:

public class MyBeanImpl implements MyBean {

    @Autowired
    private Logger logger;

    @PostConstruct
    public void init() {
        logger.info("Hello World");
    }

}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MyTest {

    @Configuration
    static class TestConfiguration {

        @Bean
        @Scope(SCOPE_PROTOTYPE)
        public Logger createLogger(InjectionPoint injectionPoint) {
            return LogManager.getLogger(injectionPoint.getMember().getDeclaringClass());
        }

        @Bean
        @Scope(SCOPE_PROTOTYPE)
        public MyBean createMyBean() {
            return new MyBeanImpl();
        }
    }

    @Autowired
    private MyBean beanA;

    @Autowired
    private MyBean beanB;


    @Test
    public void test() {

    }

    public static void main(String[] args) {
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TestConfiguration.class)) {
            ctx.getBean(MyBean.class);
            ctx.getBean(MyBean.class);
        }
    }
}

If I execute the test then I get the following log:

16:36:53.997 [main] INFO  org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [test.MyTest]: no resource found for suffixes {-context.xml}.
16:36:54.092 [main] INFO  org.springframework.test.context.support.AbstractDelegatingSmartContextLoader - AnnotationConfigContextLoader detected default configuration classes for context configuration [ContextConfigurationAttributes@255b53dc declaringClass = 'test.MyTest', classes = '{class test.MyTest$TestConfiguration}', locations = '{}', inheritLocations = true, initializers = '{}', inheritInitializers = true, name = [null], contextLoaderClass = 'org.springframework.test.context.ContextLoader'].
16:36:54.105 [main] INFO  org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
16:36:54.109 [main] INFO  org.springframework.test.context.support.DefaultTestContextBootstrapper - Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
16:36:54.110 [main] INFO  org.springframework.test.context.support.DefaultTestContextBootstrapper - Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
16:36:54.112 [main] INFO  org.springframework.test.context.support.DefaultTestContextBootstrapper - Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
16:36:54.112 [main] INFO  org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6295d394, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@475e586c, org.springframework.test.context.support.DirtiesContextTestExecutionListener@657c8ad9]16:36:54.302 [main] INFO  org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@27ae2fd0: startup date [Sat Jun 25 16:36:54 CEST 2016]; root of context hierarchy
16:36:54.757 [main] INFO  test.MyBeanImpl - Hello World
16:36:54.758 [main] INFO  test.MyTest - Hello World
16:36:54.764 [Thread-1] INFO  org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@27ae2fd0: startup date [Sat Jun 25 16:36:54 CEST 2016]; root of context hierarchy

The lines 16:36:54.757 and 16:36:54.758 in the log show that the InjectionPoint of the Logger field differs, because the injection point's member declaring class is used to instantiate the logger. I would expect that they don't differ because both times the same bean is instantiated.

After a closer look I recognized that the second logger is instantiated for the test class MyTest, so I assume that the injection point isn't populated recursively. To prove my assumption I created the method MyTest#main() where I setup an ApplicationContext by hand. When I execute this method I get the following log:

16:56:43.372 [main] INFO  org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@436e852b: startup date [Sat Jun 25 16:56:43 CEST 2016]; root of context hierarchy
16:56:44.071 [main] INFO  test.MyBeanImpl - Hello World
16:56:44.077 [main] INFO org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@436e852b: startup date [Sat Jun 25 16:56:43 CEST 2016]; root of context hierarchy
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'createMyBean': Injection of autowired dependencies failed; nested exception is java.lang.IllegalStateException: No current InjectionPoint available for method 'createLogger' parameter 0
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:356)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:325)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:220)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:333)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1088)
	at test.MyTest.main(MyTest.java:50)
	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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.IllegalStateException: No current InjectionPoint available for method 'createLogger' parameter 0
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:830)
	at org.springframework.beans.factory.support.ConstructorResolver.resolvePreparedArguments(ConstructorResolver.java:784)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:415)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1123)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1018)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:325)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.resolvedCachedArgument(AutowiredAnnotationBeanPostProcessor.java:533)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.access$200(AutowiredAnnotationBeanPostProcessor.java:118)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:562)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:350)
	... 14 more

This log confirmed my assumption from above, because now there isn't a parent injection point and the thrown error state that there isn't an current injection point at all. I guess the that the method AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject must be fixed to populate the injection point of its field via ConstructorResolver#setCurrentInjectionPoint().

I have attached a maven project so that you are able to reproduce the observed behavior.


Affects: 4.3 GA

Attachments:

Issue Links:

Referenced from: commits e15f7ef

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

This was caused by AutowiredAnnotationBeanPostProcessor's optimization for pre-resolved target bean names: We went with a straight getBean call then which does not expose InjectionPoint metadata for such repeated calls. I've replaced this with a somewhat fancier resolveShortcut mechanism on DependencyDescriptor itself, called during the regular resolveDependency algorithm within the factory's {{InjectionPoint} exposure.

This will be available in the upcoming 4.3.1.BUILD-SNAPSHOT. Feel free to give it an early try...

@spring-projects-issues
Copy link
Collaborator Author

Sebastian Staack commented

I have checked my example against the current 4.3.1.BUILD-SNAPSHOT and can approve that the fix works.

Thanks for the quick fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Projects
None yet
Development

No branches or pull requests

2 participants