Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Java config same Bean reference

Looking through question Autowire a bean within Spring's Java configuration I got a question.

@Configuration
public class Config {

   @Bean
   public RandomBean randomBean(){
       return new RandomBean();
   }

   @Bean
   public AnotherBean anotherBean(){
       return new AnotherBean(randomBean()); // this line
   }

}

How Spring guarantees that method randomBean() will return the same reference as one which was injected into AnotherBean?

Is it achieved via proxies?

On the other hand, doing it with providing dependencies as method parameters is quiet obvious:

@Configuration
public class Config {

   @Bean
   public RandomBean randomBean(){
       return new RandomBean();
   }

   @Bean
   public AnotherBean anotherBean(RandomBean randomBean){
       return new AnotherBean(randomBean);
   }

}

Edit: finally, I found this behavior described in Further information about how Java-based configuration works internally topic.

like image 529
Andrii Abramov Avatar asked Jun 13 '17 09:06

Andrii Abramov


People also ask

Can we have same bean id in Spring?

Spring beans are identified by their names within an ApplicationContext. Thus, bean overriding is a default behavior that happens when we define a bean within an ApplicationContext which has the same name as another bean. It works by simply replacing the former bean in case of a name conflict.

Can we use @bean without @configuration?

@Bean methods may also be declared within classes that are not annotated with @Configuration. For example, bean methods may be declared in a @Component class or even in a plain old class. In such cases, a @Bean method will get processed in a so-called 'lite' mode.

Can we have two beans with same class with different bean id in Spring?

If you define two beans of same class, without different bean id or qualifiers ( identifier) , Spring container will not be able to understand which bean to be loaded , if you try to access the bean by passing the classname and you will get NoUniqueBeanDefinitionException as there are two qualifying TestBean.


2 Answers

There is only one "randomBean" because the default scope is "singleton".(To force Spring to produce a new bean instance each time one is needed, you should declare the bean's scope attribute to be prototype)

singleton

This scopes the bean definition to a single instance per Spring IoC container (default).

prototype

This scopes a single bean definition to have any number of object instances.

Spring guarantees that method randomBean() will return the same reference as one which was injected into AnotherBean By using proxies.

In order to generate proxies, Spring uses a third party library called CGLIB.

  1. Spring enhances classes by generating a CGLIB subclass which interacts with the Spring container to respect bean scoping semantics for methods.

  2. Each such bean method will be overridden in the generated subclass, only delegating to the actual bean method implementation if the container actually requests the construction of a new instance.

  3. Otherwise, a call to such an bean method serves as a reference back to the container, obtaining the corresponding bean by name.

see org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor

// To handle the case of an inter-bean method reference, we must explicitly check the
// container for already cached instances.

// First, check to see if the requested bean is a FactoryBean. If so, create a subclass
// proxy that intercepts calls to getObject() and returns any cached bean instance.
// This ensures that the semantics of calling a FactoryBean from within @Bean methods
// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
if (factoryContainsBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName) && factoryContainsBean(beanName)) {
    Object factoryBean = this.beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
    if (factoryBean instanceof ScopedProxyFactoryBean) {
        // Pass through - scoped proxy factory beans are a special case and should not
        // be further proxied
    }
    else {
        // It is a candidate FactoryBean - go ahead with enhancement
        return enhanceFactoryBean(factoryBean.getClass(), beanName);
    }
}

If you want to get two different beans, you should change the default scope by @Scope

@Configuration
public class Config {

   @Bean
   @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
   public RandomBean randomBean(){
       return new RandomBean();
   }

   @Bean
   public AnotherBean anotherBean(){
       return new AnotherBean(randomBean()); // this line
   }

}
like image 61
lihongxu Avatar answered Oct 04 '22 21:10

lihongxu


According to Spring documentation injection of inter-bean possible only when @Bean method declared within @Configuration. It uses CGLIB and all @Configuration classes are subclassed by it.

Please, check this reference https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-java-bean-annotation, section '7.12.4 Using the @Configuration annotation'. You will find answer on your question from original source.

like image 44
eg04lt3r Avatar answered Oct 04 '22 23:10

eg04lt3r