Spring配置类为什么要分Full和Lite模式

发布时间 2023-03-24 10:45:08作者: 程序员自由之路

本文基于Spring 5.2.15-RELEASE

关于Spring配置类的Full模式和Lite模式,如果没有仔细阅读过源码或者官方文档的话,估计很多人都不知道这个概念。所以我们先来解释下这两个概念。

概念解释

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

DataSourceConfig就是一个在Spring中非常常见的配置类,这个配置类本身也会被注册成Spring容器中的一个Bean。上面这种配置方式默认的就是Full模式。为什么默认是Full模式呢?其实秘密就藏在@Configuration这个注解中。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	@AliasFor(annotation = Component.class)
	String value() default "";
    // 默认会通过Cglib对产生Bean的方法进行增强
	boolean proxyBeanMethods() default true;
}

这个注解有一个属性proxyBeanMethods。这个属性的值默认是true,会通过Cglib对这个配置类进行增强,增强后这个配置类就显得比较“重”,因此叫Full模式。如果我们将这个属性设置成false的话,这个配置类产生的Bean就是原始的对象,比较“轻量级”,叫Lite模式。因此,Full模式和Lite模式最本质的区别是:配置类本身的Bean对象会不会被Cglib增强。

这里再强调一个概念:Full模式和Lite模式都是说DataSourceConfig这个配置类是Full模式或者Lite模式。

Lite模式

上面提到,将@Configuration的proxyBeanMethods属性设置成false就Lite模式的配置类,其实还有很多其他的场景也属于Lite模式。整理下来,大致有以下这些场景:

  1. 类上标注有@Component注解
  2. 类上标注有@ComponentScan注解
  3. 类上标注有@Import注解
  4. 类上标注有@ImportResource注解
  5. 若类上没有任何注解,但类内存在@Bean方法
  6. 标注有@Configuration(***proxyBeanMethods = false***)

自Spring5.2(对应Spring Boot 2.2.0)开始,内置的几乎所有的@Configuration配置类都被修改为了@Configuration(proxyBeanMethods = false),以此来降低启动时间,为Cloud Native继续做准备。

优点

  • 运行时不再需要给对应类生成CGLIB子类,提高了运行性能,降低了启动时间
  • 可以把该配置类当作一个普通类使用喽:也就是说@Bean方法 可以是private、可以是final

缺点

  • 不能声明@Bean之间的依赖,也就是说不能通过方法调用来依赖其它Bean
@Configuration(proxyBeanMethods = false)
public class DataSourceConfig {
 
	...
	@Bean
    public DataSource dataSource() {
    	...
        return dataSource;
    }
    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager() {
        // 此处的dataSource()调用就是一次普通的方法调用,不会引用Spring IOC容器中的单列Bean
        return new DataSourceTransactionManager(dataSource());
    }
	...
}

这个问题可以这样解决

@Configuration(proxyBeanMethods = false)
public class DataSourceConfig {
 
	...
	@Bean
    public DataSource dataSource() {
    	...
        return dataSource;
    }
    @Bean(name = "transactionManager")
    // 通过方法参数引用Spring容易中的Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource;
    }
	...
}

Full模式

标注有@Configuration或者@Configuration(***proxyBeanMethods = true***)的类被称为Full模式的配置类。

优点

  • 可以支持通过常规Java调用相同类的@Bean方法而保证是容器内的Bean;

缺点

  • 运行时会给该类生成一个CGLIB子类放进容器,有一定的性能、时间开销;
  • 正因为被代理了,所以@Bean方法 不可以是private、不可以是final

简单总结

Spring的配置类分成Full和Lite两种模式。

在Lite模式下

  • 配置类本身不会被CGLIB增强,放进IoC容器内的就是配置类本身;
  • 对于内部类是没有限制的:可以是Full模式或者Lite模式;
  • 配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新实例而并非IoC容器内的单例
  • 配置类就是一普通类嘛,所以内部的@Bean方法可以使用private/final等进行修饰

在Full模式下

  • 配置类会被CGLIB增强(生成代理对象),放进IoC容器内的是代理对象;
  • 对于内部类是没有限制:可以是Full模式或者Lite模式;
  • 该模式下,配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向IoC内的那个单例;
  • 该模式下,@Bean方法不能被private/final等进行修饰(因为方法需要被重写,所以不能私有和final。defualt/protected/public都可以哦)

从上面的介绍可以看出来,Lite模式很大程度上是为了减少启动开销,提升程序的启动速度。所以如果你对程序的启动速度很敏感,就使用Lite模式,但是一定要记住此时的配置类已经不是经过Cglib增强过的类了。

参考