java – Spring 5中lite @Bean方法的行为

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.

我原以为下面的代码会将异常/ bean1.bean2抛出为null,并且不会执行init方法.但是,下面的代码运行正常并打印:

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

所以对我来说,看起来像lite bean的行为与从@Configuration带注释的类构造的bean相同.有人可以指出在哪种情况下情况并非如此?

.

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");
    }
}

编辑:

根据迈克希尔的解释,我添加了一个展示差异的例子:

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");
        }
    }
}

输出符合预期

> Creating MyComponentBean2 
> Creating MyComponentBean2 
> Creating MyConfigrationBean2
最佳答案
这不是一个错误.在组件扫描期间,Spring的lite bean定义会自动添加到上下文中.使用工厂方法的Bean定义(即所有@ Bean定义的bean)将自动尝试使用当前上下文自动装配参数.有关详细信息,请参见ConstructorResolver#instantiateUsingFactoryMethod.

引用的文档可能并不完全清楚. “lite”和“full”bean配置之间的主要区别严格来说是在“完整”(@Configuration)模式下完成的代理.举例来说,如果我们在@Configuration类中定义我们的bean会发生什么:

@Configuration
public MyConfiguration {

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

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

从bean1()内部调用bean2()实际上将从上下文中引用singleton bean2实例,而不是直接调用实现的bean2()方法.实际上,在该配置类中进行了多少bean2()方法调用并不重要 – 真正的bean2方法只会执行一次.

“Lite”-mode不代理这些方法,这意味着每个bean2()方法调用实际上都会直接引用方法实现,并且不会返回上下文中的bean.相反,它将创建一个不被上下文跟踪的新的单独实例(默认的Java行为).

转载注明原文:java – Spring 5中lite @Bean方法的行为 - 代码日志