暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

@Configuration的Lite模式和Full模式

糖爸的架构师之路 2021-06-24
2430
写在前面

清明时节雨纷纷,这句话真的准,在我的印象里,自从清明节有了小长假,几乎每个清明节假期都会下雨,所以好多小伙伴本来有出游的打算,因为天气原因也只好放弃。但是没关系,虽然不能出门,但是在家里也可以做点有意义的事情,比如来看看我的文章, 一起讨论讨论技术,尽管也挺无聊,但是至少还算有意义,而且保持学习的习惯也是一个程序员赖以生存的根本。
最初的Spring只支持xml方式配置Bean,即在spring的配置文件中声明要向Ioc容器中注册的Bean,并设置相关属性,如下:

但是从Spring 3.0起支持了一种更优的方式:基于Java类的配置方式,这一下子让我们Javaer可以从标签语法里解放了出来。毕竟作为Java程序员,我们擅长的是写Java类,而非用标签语言去写xml文件。


基本概念

Spring新的配置体系中,向Ioc容器注册组件除了之前用到的xml配置之外,还提供了非常多的注解可以用,例如
  • @Import
  • @Component
  • @ComponentScan

...

这其中就包括了@Configuration、通常配合@Configuration一起出现的还有@Bean,这里需要注意的是:

@Configuration用来标注类

@Bean用来标注方法

用@Configuration注解标注的类表明其主要目的是作为bean定义的源。此外,@Configuration类允许通过调用同一类中的其他@Bean method方法来定义bean之间的依赖关系。

@Bean注解标注在方法上,用于将该方法返回的对象交给Spring Ioc容器来管理,对象id默认为方法名称。所以,对于熟悉Spring的<beans/> XML配置的人来说,@Bean注解的作用与<bean/>元素相同。
需要注意的是,我们通常会把@Bean标注的方法写在@Configuration标注的类里面来配合使用。所以如果使用这两个注解,我们可以通过下面的方式来注册容器组件

简单粗暴理解:@Configuration标注的类等同于一个xml文件,@Bean标注的方法等同于xml文件里的一个<bean/> 标签


Lite模式和Full模式

Full模式和Lite模式均是针对于Spring配置类而言的,和xml配置文件无关。值得注意的是:判断是Full模式 or Lite模式的前提是,首先你得是个容器组件。至于一个实例是如何“晋升”成为容器组件的,本文就不展开讨论了,这属于Spring的基础知识。

Lite模式

首先来看一下官方文档,文档地址如下,并且我把和这两种模式相关的解释以截图的方式贴出来,如下

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

这里简单的总结一下,官方定义在没有标注@Configuration的类里面有@Bean方法就称为Lite模式的配置。不过这里需要注意,通过源码再看这个定义是不完全正确的,而应该是有如下情况均认为是Lite模式的配置类:

  • Class上标注有@Component注解
  • Class上标注有@ComponentScan注解
  • Class上标注有@Import注解
  • Class上标注有@ImportResource注解
  • 若类上没有任何注解,存在@Bean方法
以上情况的前提均是类上没有被标注@Configuration,不过在Spring 5.2之后新增了一种情况也算作Lite模式:
@Configuration(proxyBeanMethods = false)
注意:此值默认是true哦,需要显示改为false才算是Lite模式,下面是针对@Configuration中的proxyBeanMethod()属性官方给出的解释

那Lite模式到底是什么意思?下面我用代码来解释一下:配置类MyConfiguration01,这里将proxyBeanMethod设置为false,即Lite模式,注册User类型的Bean命名为user01

配置类MyConfiguration02,不标注任何类注解,但是通过@Bean向容器中注册了Pet类型的Bean命名为pet01,注意,这里方法的权限标识为private

在主配置类中,首先从Ioc容器中获取
MyConfiguration01对象,并打印该对象的类型,之后从容器中获取对象user01,最后通过MyConfiguration01#user01()再获取一个User类型的对象定义为user02,到这里我们就会思考两个问题
  • user01和user02是否为同一个对象?
  • MyConfiguration对象的真实类型是什么?

结果如下

通过上面代码我们可以得出结论

  • MyConfiguration01并没有被CGLib增强,也就是说,它并不是一个代理对象
  • 通过MyConfiguration01#user01()获取的对象user02,并不是Ioc容器中的对象
  • @Bean可以在普通类中使用,但是该类必须是Ioc容器中的对象,并且标注的方法可以是private、final
所以,我们可以总结一下Lite模式的优缺点
优点:

  • 运行时不再需要给对应类生成CGLIB子类,每次获取对象并不会优先去获取Ioc容器中的对象,提高了运行性能,降低了服务的启动和加载时间

  • 配置类可以当作一个普通类使用:也就是说@Bean方法可以是private、final
缺点:

  • 不能声明@Bean之间的依赖,也就是说不能通过方法调用来依赖其它Bean


小总结

  • 配置类本身不会被CGLIB增强,放进容器内的就是原始类型
  • 配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新实例而并非IoC容器内的单例
  • 配置类可以是普通类,所以@Bean方法可以使用private/final/static等进行修饰

Full模式


在常见的场景中,@Bean方法都会在标注有@Configuration的类中声明,以确保总是使用“Full模式”,这么一来,交叉方法引用会被重定向到容器的生命周期管理,所以就可以更方便的管理Bean依赖。

标注有@Configuration注解的类并且proxyBeanMethod=true被称为full模式的配置类。我们依然通过代码来观察一下Full模式和之前的Lite模式有什么不同,这里代码我就不贴出来了,和之前一样,只不过将

MyConfiguration01中的proxyBeanMethod属性修改为true,再次观察打印结果

看出什么玄机了么?是的,你没看错,和之前不同的是
  • MyConfiguration01变成了代理对象
  • user01和user02是同一个对象
所以,我们再次总结下Fite的优缺点
优点:

  • 可以支持通过常规Java调用相同类的@Bean方法而保证是容器内的Bean,这有效规避了在“Lite模式”下操作时难以跟踪的细微错误。特别对于萌新程序员,这个特点很有意义
缺点:

  • 运行时会给该类生成一个CGLIB子类放进容器,有一定的性能、时间开销(这个开销在Spring Boot这种拥有大量配置类的情况下是不容忽视的,这也是为何Spring5.2新增了proxyBeanMethods属性的最直接原因)

  • 正因为被代理了,所以@Bean方法不可以是private、final,也就是说必须是可以被继承的方法,具体原因可以去看CGLib的相关知识,这里不做过多赘述。

小总结

  • 配置类会被CGLIB增强(生成代理对象),放进IoC容器内的是代理
  • 配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向IoC内的那个单例
  • @Bean方法不能被private/final等进行修饰,否则编译报错
应用场景

说了这么多,有很多小伙伴可能还是一头雾水,觉得这个东西是用来干嘛的?具体的使用场景是什么?下面我们就来讨论一下

首先举个小栗子,在SpringBoot中集成过数据库配置的小伙伴应该都知道,我们在配置数据库时,代码可能像下面这样

    @Configuration
    public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
    // ...
    return dataSource;
    }
    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource());
    }
    }

    在准备DataSourceTransactionManager这个Bean时调用了dataSource()方法,当调用dataSource()方法时产生的数据源实例,和我们在容器中注册的数据源实例是不是同一个呢?如果不是同一个,那么事务管理器管理的不就是一个“全新数据源”么?何谈事务呢?所以这里的答案显而易见,通过外部调用获取的数据源实例其实就是注册到容器中的数据源实例,通过这个例子我们能知道什么呢?没错,Full模式和Lite模式的应用场景最主要的就是要解决组件依赖的问题。当容器中的组件需要依赖其他组件的实例时,如果存在外部调用,Full模式就可以帮助我们每次将依赖的组件指向容器中的组件实例,即每次获取的组件实例都是同一个,即Ioc容器中的实例。所以上面的例子中,如果设置为Lite模式,即

    @Configuration(proxyBeanMethod=false)(默认为true),当被外部调用时,就会创建一个全新的数据源实例,这样显然是行不通的。


    最佳实践

    那这两种模式到底要怎么应用呢?我个人建议
    • 配置类组件之间无依赖关系时使用Lite模式这样做可以加快容器启动速度,减少判断

    • 配置类组件之间有依赖关系时使用Full模式这样做可以保证当方法被调用时,得到之前单实例的组件
    自Spring5.2(对应Spring Boot 2.2.0)开始,内置的几乎所有的@Configuration配置类都被修改为了
    @Configuration(proxyBeanMethods = false)

    从这里也可以看出,Spring底层也是想通过这种方式来降低服务启动的开销。

    今天的内容就到这里啦,之后我会再开一篇这个内容的文章,旨在通过源码的方式来了解一下Spring针对这两种模式有哪些巧妙的设计,敬请期待吧~
    文章转载自糖爸的架构师之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论