Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Behaviour of lite @Bean methods in Spring 5

Tags:

java

spring

From the Spring 5 docs

When @Bean methods are declared within classes that are not annotated with @Configuration they are referred to as being processed in a 'lite' mode. Bean methods declared in a @Component or even in a plain old class will be considered 'lite', with a different primary purpose of the containing class and an @Bean method just being a sort of bonus there. For example, service components may expose management views to the container through an additional @Bean method on each applicable component class. In such scenarios, @Bean methods are a simple general-purpose factory method mechanism.

Unlike full @Configuration, lite @Bean methods cannot declare inter-bean dependencies. Instead, they operate on their containing component’s internal state and optionally on arguments that they may declare. Such an @Bean method should therefore not invoke other @Bean methods; each such method is literally just a factory method for a particular bean reference, without any special runtime semantics. The positive side-effect here is that no CGLIB subclassing has to be applied at runtime, so there are no limitations in terms of class design (i.e. the containing class may nevertheless be final etc).

The @Bean methods in a regular Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that @Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects; such methods are not invoked with normal Java semantics but rather go through the container in order to provide the usual lifecycle management and proxying of Spring beans even when referring to other beans via programmatic calls to @Bean methods. In contrast, invoking a method or field in an @Bean method within a plain @Component class has standard Java semantics, with no special CGLIB processing or other constraints applying.

I would have expected that the following code throws an exception / bean1.bean2 to be null and that the init method would not be executed. However, the code below runs fine and prints:

Should never be invoked
Expected null but is ch.litebeans.Bean2@402bba4f

So for me it looks like lite beans behave the same as beans constructed from an @Configuration annotated class. Can someone point out in which scenario this is not the case?

.

package ch.litebeans;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        Bean1 bean1 = ctx.getBean(Bean1.class);
        System.out.println("Expected null but is " + bean1.getBean2());
    }
}

package ch.litebeans;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = {"ch.litebeans"})
public class ApplicationConfig {}

package ch.litebeans;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class Factory1 {
    @Bean
    public Bean1 getBean1(Bean2 bean2){
        return new Bean1(bean2);
    }
}

package ch.litebeans;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class Factory2 {
    @Bean(initMethod = "init")
    public Bean2 getBean2(){
        return new Bean2();
    }
}

package ch.litebeans;

public class Bean1 {
    private Bean2 bean2;
    public Bean1(Bean2 bean2){
        this.bean2 = bean2;
    }
    public Bean2 getBean2(){
        return bean2;
    }
}

package ch.litebeans;

public class Bean2 {
    public void init(){
        System.out.println("Should never be invoked");
    }
}

EDIT:

Based on the explanation of Mike Hill I added an example demonstrating the difference:

public class BeanLiteRunner {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(MyComponent.class,
                MyConfiguration.class);
        MyComponent.MyComponentBean1 componentBean1 = acac.getBean(MyComponent.MyComponentBean1.class);
        MyComponent.MyComponentBean1 componentBean2 = acac.getBean(MyComponent.MyComponentBean1.class);

        MyConfiguration.MyConfigurationBean1 configurationBean1 = acac.getBean(MyConfiguration
                .MyConfigurationBean1.class);
        MyConfiguration.MyConfigurationBean1 configurationBean2 = acac.getBean(MyConfiguration
                .MyConfigurationBean1.class);
    }
}

@Component
public class MyComponent {

    @Bean
    public MyComponent.MyComponentBean1 getMyComponentBean1(){
        return new MyComponent.MyComponentBean1(getMyComponentBean2());
    }

    @Bean
    public MyComponent.MyComponentBean2 getMyComponentBean2(){
        return new MyComponent.MyComponentBean2();
    }


    public static class MyComponentBean1{
        public MyComponentBean1(MyComponent.MyComponentBean2 myComponentBean2){

        }
    }

    public static class MyComponentBean2{
        public MyComponentBean2(){
            System.out.println("Creating MyComponentBean2");
        }
    }
}

@Configuration
public class MyConfiguration {
    @Bean
    public MyConfigurationBean1 getMyConfigurationBean1(){
       return new MyConfigurationBean1(getMyConfigrationBean2());
    }

    @Bean
    public MyConfigurationBean2 getMyConfigrationBean2(){
        return new MyConfigurationBean2();
    }


    public static class MyConfigurationBean1{
        public MyConfigurationBean1(MyConfigurationBean2 myConfigurationBean2){}
    }

    public static class MyConfigurationBean2{
        public MyConfigurationBean2(){
            System.out.println("Creating MyConfigrationBean2");
        }
    }
}

The output is as expected

> Creating MyComponentBean2 
> Creating MyComponentBean2 
> Creating MyConfigrationBean2
like image 778
Ueli Hofstetter Avatar asked Jun 27 '18 18:06

Ueli Hofstetter


People also ask

Can we use @bean inside @component?

@Component is a class level annotation whereas @Bean is a method level annotation and name of the method serves as the bean name. @Component need not to be used with the @Configuration annotation where as @Bean annotation has to be used within the class which is annotated with @Configuration.

What is Lite mode in Spring?

"Lite"-mode does not proxy these methods, meaning that each bean2() method call will actually directly reference the method implementation and will not return the bean that is in the context. Rather, it will create a new separate instance that is not tracked by the context (as is the default Java behavior).

What is the use of @bean in Spring boot?

One of the most important annotations in spring is the @Bean annotation which is applied on a method to specify that it returns a bean to be managed by Spring context. Spring Bean annotation is usually declared in Configuration classes methods. This annotation is also a part of the spring core framework.

What are beans in the concept of Spring or what are Spring beans?

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.


1 Answers

This is not a bug. Spring's lite bean definitions are automatically added to the context during the component scan. Bean definitions using a factory method (i.e., all @Bean-defined beans) will automatically attempt to autowire parameters using the current context. See ConstructorResolver#instantiateUsingFactoryMethod for more details.

The referenced documentation is perhaps not entirely clear. The primary differentiation between "lite" and "full" bean configurations is strictly the proxying that is done in "full" (@Configuration) mode. Take for example what would happen if we instead defined our beans in a @Configuration class:

@Configuration
public MyConfiguration {

    @Bean
    public Bean1 bean1() {
        return new Bean1(bean2());
    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}

The call to bean2() from within bean1() will actually reference the singleton bean2 instance from the context rather than directly calling the bean2() method as implemented. In fact, it doesn't matter how many bean2() method calls are made from within that configuration class -- the real bean2 method will only be executed one time.

"Lite"-mode does not proxy these methods, meaning that each bean2() method call will actually directly reference the method implementation and will not return the bean that is in the context. Rather, it will create a new separate instance that is not tracked by the context (as is the default Java behavior).

like image 182
Mike Hill Avatar answered Sep 20 '22 14:09

Mike Hill