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

TestRestTemplateContextCustomizer and WebTestClientContextCustomizer can cause early FactoryBean instantiation #15898

Closed
darkMechanicum opened this issue Feb 12, 2019 · 5 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@darkMechanicum
Copy link

Hi!

Description

A assume there is a bug in spring-boot-test, related to TestRestTemplate BeanDefinition initialization.
To be precise, class TestRestTemplateContextCustomizer#TestRestTemplateRegistrar in it's method postProcessBeanDefinitionRegistry calls BeanFactoryUtils#beanNamesForTypeIncludingAncestors.
As beanNamesForTypeIncludingAncestors says, that's method invocation can cause FactoryBean initialization (and their dependencies, of course). And, in context of BeanDefinitionRegistryPostProcessor this, I guess, is incorrect behavior.

Reproducing

One file reproducer is as follows:

@Configuration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SingleFileReproducerTest.class,
        webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@EnableAutoConfiguration
public class SingleFileReproducerTest {

    @Test
    public void dummy() {}

    @Component
    public static class SimpleFactoryBean implements FactoryBean {
        public SimpleFactoryBean(ApplicationContext context) {}
        public Object getObject() { return new Object(); }
        public Class<?> getObjectType() { return Object.class; }
    }
}

Note, when webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT is commented out, test passes, as there is no need for TestRestTemplate.

Versions

  1. org.springframework.boot.spring-boot-starter:2.1.2.RELEASE
  2. org.springframework.boot.spring-boot-starter-web:2.1.2.RELEASE
  3. org.springframework.boot.spring-boot-starter-test:2.1.2.RELEASE
  4. junit.junit:4.12
  5. Java 1.8
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 12, 2019
@wilkinsona
Copy link
Member

wilkinsona commented Feb 12, 2019

Thanks for the minimal sample. We'll see what we can do, but I would recommend that you avoid injecting dependencies into a FactoryBean as it can cause some unexpected and hard to diagnose problems such as spring-projects/spring-framework#13893. I would also recommend that you provide as much type information as possible in your FactoryBean's signature and in the Class that's returned from getObjectType(). Returning Object.class and not parameterising the factory bean is causing the container to do more work to determine the type of the bean than will be produced by the factory.

@wilkinsona
Copy link
Member

WebTestClientRegistrar from WebTestClientContextCustomizer contains similar logic and will, presumably, cause a similar problem.

@wilkinsona wilkinsona changed the title TestRestTemplateContextCustomizer can instanciate FactoryBean too early TestRestTemplateContextCustomizer and WebTestClientContextCustomizer can cause early FactoryBean instantiation Feb 12, 2019
@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 12, 2019
@wilkinsona wilkinsona added this to the 2.1.x milestone Feb 12, 2019
@darkMechanicum
Copy link
Author

Thanks for advice about FactoryBean. As for example - I filled it with such error prone modifications as Object.class just for it's size.

By the way, I found this behavior while using custom JpaRepositoryFactoryBean. If I understand correctly, dependencies should be injected in FactoryBean by constructor? Or there should be no autowiring at all and one should create it manually, in @Bean method, for example?

@wilkinsona
Copy link
Member

wilkinsona commented Feb 13, 2019

The biggest benefit of minimising the dependencies of a factory bean are when that bean is a @Component and you are relying on the dependencies being autowired into the constructor. If you want to inject dependencies, I'd recommend using a @Bean method instead.

Interestingly, when using a @Bean method, Framework will end its attempt to determine the type created by the factory bean more aggressively which helps to avoid the problem you're seeing here. That difference in behaviour is a little surprising to me. I've opened spring-projects/spring-framework#22409 to explore that part of what's going on here.

@wilkinsona
Copy link
Member

There's a change coming in Framework (thanks, @jhoeller) but we should review our use of BeanFactoryUtils#beanNamesForTypeIncludingAncestors and try to avoid triggering eager initialisation nonetheless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

4 participants