亲宝软件园·资讯

展开

SpringBoot this调用@Bean效果详解

愿做无知一猿 人气:0

在一个@Bean方法内,this调用同一个类的@Bean方法会有什么效果呢?

思考的起源

首先上代码:

public class BeanOne { 
}
public class BeanTwo {
    public BeanTwo(BeanOne beanOne){
    }
}
@Configuration
public class BeanConfigTest {
    @Bean
    @ConditionalOnMissingBean
    public BeanOne beanOne() {
        System.err.println("带有@ConditionalOnMissingBean的默认 BeanOne 产生------");
        return new BeanOne();
    }
    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo(this.beanOne());
    }
}

​ 可以看到上述三个类,其中BeanOne就是一个默认的Bean实现,标注了@ConditionalOnMissingBean代表它可以被覆盖;BeanTwo是一个使用BeanOne的类,类似于注入;BeanConfigTest就是用来注册这俩Bean的,可以看到在BeanTwo这个里面,直接使用了this.beanOne(),我一开始的想法就是,this调用,那始终调用的都是beanOne()这个方法呀,那不就代表着BeanOne不能被覆盖了。

​ 但是,当我将BeanOne加上@Component注解之后,运行程序,会发现,控制台根本没有输出我打印的那句话,那就可以猜测 this.beanOne()其实并不是简单的方法调用方法。

查找信息

阅读@Bean上的注释:

@Bean Methods in @Configuration Classes
Typically, @Bean methods are declared within @Configuration classes. In this case, bean methods may reference other @Bean methods in the same class by calling them directly. This ensures that references between beans are strongly typed and navigable. Such so-called 'inter-bean references' are guaranteed to respect scoping and AOP semantics, just like getBean() lookups would. These are the semantics known from the original 'Spring JavaConfig' project which require CGLIB subclassing of each such configuration class at runtime. As a consequence, @Configuration classes and their factory methods must not be marked as final or private in this mode. For example:
   @Configuration
   public class AppConfig {
       @Bean
       public FooService fooService() {
           return new FooService(fooRepository());
       }
       @Bean
       public FooRepository fooRepository() {
           return new JdbcFooRepository(dataSource());
       }
       // ...
   }

​ 简要概述就是:

Spring会对每个@Configuration标注的类进行CGLIB子类化,在一个Bean内使用方法调用另一个Bean,就像是getBean()查找一样。

​ 从它注释上的描述可以总结出,像this.beanOne()这类方法调用,其实就类似于getBean()去获取一个名叫beanOneBean。那么上面的输出结果就能解释了。

​ 上面这段注释下面,紧跟着一段注释,一起看一下:

@Bean Lite Mode
@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.
Bean methods in lite mode will be treated as plain factory methods by the container (similar to factory-method declarations in XML), with scoping and lifecycle callbacks properly applied. The containing class remains unmodified in this case, and there are no unusual constraints for the containing class or the factory methods.
In contrast to the semantics for bean methods in @Configuration classes, 'inter-bean references' are not supported in lite mode. Instead, when one @Bean-method invokes another @Bean-method in lite mode, the invocation is a standard Java method invocation; Spring does not intercept the invocation via a CGLIB proxy. This is analogous to inter-@Transactional method calls where in proxy mode, Spring does not intercept the invocation — Spring does so only in AspectJ mode.
For example:
   @Component
   public class Calculator {
       public int sum(int a, int b) {
           return a+b;
       }
       @Bean
       public MyBean myBean() {
           return new MyBean();
       }
   }

​ 简要概述就是:

@Component标注的类中,你使用@Bean标注的方法处于一种叫做lite模式下,lite模式中的Bean方法将被容器视为普通工厂方法,lite模式中的Bean,不支持Bean间的相互调用,如果相互调用,那么将会被视为标准的Java方法调用,Spring不会通过CGLIB为当前类生成子类。最后他说,这类似于内部 @Transactional方法调用,在代理模式下,Spring不会拦截调用;但是仅在AspectJ模式下,Spring会拦截调用。好像也是,标注@Transcational的方法是不能直接相互调用的。

​ 那用上面的例子试一下看看,是不是变成了普通Java方法调用了:

@Component
public class BeanOne {
}
public class BeanTwo {
    public BeanTwo(BeanOne beanOne){
    }
}
@Component
//@Configuration
public class BeanConfigTest {
    /**
     * @Bean 创建的默认是单例Bean
     */
    @Bean
    @ConditionalOnMissingBean
    public BeanOne beanOne() {
        System.err.println("带有@ConditionalOnMissingBean的默认 BeanOne 产生------");
        return new BeanOne();
    }
    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo(this.beanOne());
    }
}

​ 此时BeanOne上标注了@Component,但是打印了输出语句,可见其变成了普通方法调用。

更远一步

Bean的Full和Lite模式

​ 当@Bean方法在没有标注@Configuration注释的类中声明时,它们被称为Lite模式的Bean。例如:在@Component中声明的@Bean方法,甚至只是在一个非常普通的类中声明的Bean方法,都被认为是Lite版的配置类。和Full模式的@Configuration不同,Lite模式的@Bean方法不能声明Bean之间的依赖关系。因此,这样的@Bean方法不应该调用其他@Bean方法。每个这样的方法实际上只是一个特定Bean引用的工厂方法(factory-method),没有任何特殊的运行时语义。

​ 怎么确定一个Bean是不是Lite模式呢?

只要不标识@Configuration(proxyBeanMethods=true)其他都是lite模式。(当然,标注了的就是Full模式啦)

上述例子的spring-context版本是6.0.2,其中@Configuration的属性proxyBeanMethods的默认值是true。

​ 那么proxyBeanMethods为true和false有什么使用上的区别呢?

设置为false此时bean是lite模式:

此时运行时不再需要给对应类生成CGLIB子类,提高了运行性能,降低了启动时间,但是不能声明@Bean之间的依赖,也就是说不能通过方法调用来依赖其它Bean。

设置为true时为Full模式

此时配置类会被CGLIB增强(生成代理对象),放进IoC容器内的是代理,方法相互调用能够保证是同一个实例,都指向IoC内的那个单例,可以支持通过常规Java调用相同类的@Bean方法而保证是容器内的Bean,但是运行时会给该类生成一个CGLIB子类放进容器,有一定的性能、时间开销。

加载全部内容

相关教程
猜你喜欢
用户评论