My logo
Published on

Spring-配置类cglib代理

Spring 配置类 CGLIB 代理

目录


配置类 CGLIB 代理

@Configuration
@ComponentScan("jinggo.spring.example.enhancer.bean")
@Slf4j(topic = "e")
public class Appconfig {
	@Bean
	public X x(){
		log.debug("Appconfig#x() invoke");
		return  new X();
	}


	@Bean
	public Y y(){
		x();
		return  new Y();
	}
}

注意:
上面代码整个spring容器实例化后X并没有被创建两遍;也就是说即使你写的代码存在一定得问题, 但是spring会帮你规避这些问题;因为spring把Appconfig这个类进行了代理;——你看到的方法逻辑 不等于实际执行的逻辑;spring底层的做法是调用@Bean方法的时候首先通过beanfactory去 getBean;如果返回的是null则调用父类的方法去创建一个bean;如果返回的bean不等于null;则直接 返回改bean。


全配置类和半配置类

Spring 配置类分为 Full 模式和 Lite 模式:

模式标注方式特点
Full@Configuration(proxyBeanMethods = true)支持 @Bean 方法间依赖,类会被 CGLIB 增强,方法不能是 private/final
Lite@Component/@ComponentScan/@Import不支持 @Bean 方法间依赖,类不会被 CGLIB 增强,方法可为 private/final

Lite 模式

当@Bean方法在没有使用@Configuration注释的类中声明时,它们被称为在Lite模式下处理。它包括:在@Component中声明的@Bean方法,甚至只是在一个非常普通的类中声明的Bean方法,都被认为是Lite版的配置类。@Bean方法是一种通用的工厂方法(factory-method)机制。

和Full模式的@Configuration不同,Lite模式的@Bean方法不能声明Bean之间的依赖关系。因此,这样的@Bean方法不应该调用其他@Bean方法。每个这样的方法实际上只是一个特定Bean引用的工厂方法(factory-method),没有任何特殊的运行时语义。

何时为Lite模式

官方定义为:在没有标注@Configuration的类里面有@Bean方法就称为Lite模式的配置。透过源码再看这个定义是不完全正确的,而应该是有如下case均认为是Lite模式的配置类:

  • 类上标注有@Component注解
  • 类上标注有@ComponentScan注解
  • 类上标注有@Import注解
  • 类上标注有@ImportResource注解
  • 若类上没有任何注解,但类内存在@Bean方法

以上case的前提均是类上没有被标注@Configuration,在Spring 5.2之后新增了一种case也算作Lite模式:

  • 标注有@Configuration(proxyBeanMethods = false) ,注意:此值默认是true哦,需要显示改为false才算是Lite模式
  • 自Spring5.2(对应Spring Boot 2.2.0)开始,内置的几乎所有的@Configuration配置类都被修改为了@Configuration(proxyBeanMethods = false),目的何为?答:以此来降低启动时间,为Cloud Native继续做准备。

优缺点

优点:

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

缺点:

  • 不能声明@Bean之间的依赖,也就是说不能通过方法调用来依赖其它Bean (其实这个缺点还好,很容易用其它方式“弥补”,比如:把依赖Bean放进方法入参里即可)

总结

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

Full 模式

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

何时为Full模式

  • 标注有@Configuration注解的类被称为full模式的配置类。自Spring5.2后这句话改为下面这样我觉得更为精确些:
  • 标注有@Configuration或者@Configuration(proxyBeanMethods = true)的类被称为Full模式的配置类 (当然喽,proxyBeanMethods属性的默认值是true,所以一般需要Full模式我们只需要标个注解即可)

优缺点

优点:

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

缺点:

  • 运行时会给该类生成一个CGLIB子类放进容器,有一定的性能、时间开销(这个开销在Spring Boot这种拥有大量配置类的情况下是不容忽视的,这也是为何Spring 5.2新增了proxyBeanMethods属性的最直接原因) 正因为被代理了,所以@Bean方法 不可以是private、不可以是final

总结

  1. 该模式下,配置类会被CGLIB增强(生成代理对象),放进IoC容器内的是代理
  2. 该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
  3. 该模式下,配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向IoC内的那个单例
  4. 该模式下,@Bean方法不能被private/final等进行修饰(很简单,因为方法需要被复写嘛,所以不能私有和final。defualt/protected/public都可以哦),否则启动报错(其实IDEA编译器在编译器就提示可以提示你了):

动态生成的属性

Spring 通过 CGLIB 代理增强配置类时,会动态添加 $$beanFactory 属性:

// 代理对象会添加 $$beanFactory 属性
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));

setBeanFactory 方法

代理类实现了 EnhancedConfiguration 接口,间接实现了 BeanFactoryAware,需要实现 setBeanFactory 方法:

public interface BeanFactoryAware extends Aware {
	void setBeanFactory(BeanFactory beanFactory) throws BeansException;
}

该方法会被拦截器增强,通过反射填充 $$beanFactory 属性。


ImportAwareBeanPostProcessor(核心)

ImportAwareBeanPostProcessor 这个后置处理器为什么会出现在 List 当中是因为 spring 在执行 ConfigurationClassPostProcessor#postProcessorBeanFacory 方法中添加了它。

spring-cglib


Spring 配置类深度剖析

相关类说明

  • @Configuration:标注 Full 模式配置类,自Spring 5.2.0版本后它加了个proxyBeanMethods属性来显示控制Full模式还是Lite模式,默认是true表示Full模式
  • @Bean:标注工厂方法,表示方法生成一个由Spring容器管理的BeanConfigurationClassPostProcessor:用于引导处理@Configuration配置类的后置处理器。注意:它只是引导处理,并不是实际处理
  • ConfigurationClassUtils:内部工具类。用于判断组件是否是配置类,又或是Full模式/Lite模式,然后在bd元数据里打上标记,它还会处理一件小事:获取@Configuration配置类上标注的@Order排序值并放进bd里
  • BeanMethod:内部使用的类。用于封装标注有@Bean注解的方法
  • ConfigurationClass:内部使用的类。每一个@Configuration配置类都会被封装为它,内部会包含多个@Bean方法(BeanMethod)
  • ConfigurationClassParser:解析@Configuration配置类,最终以ConfigurationClass对象的形式展示,并且填充它:因为一个配置类可以@Import导入另外一个(或者N多个)其它配置类,所以需要填充
  • ConfigurationClassBeanDefinitionReader:内部使用的类。读取给定的已经解析好的Set集合,把里面的bd信息注册到BeanDefinitionRegistry里去(这里决定了bd的有序和无序相关问题)
  • ConfigurationClassEnhancer:内部使用的类。配置类增强器,用于对@Configuration类(Full模式)使用CGLIB增强,生成一个代理子类字节码Class对象
  • EnhancedConfiguration:被增强器增强过的配置类,都会自动的让实现此接口(实际是个BeanFactoryAware)接口
  • SpringNamingPolicy:使用CGLIB生成字节码类名名称生成策略 -> 名称中会有BySpringCGLIB字样
  • BeanFactoryAwareMethodInterceptor:CGLIB代理对象拦截器。作用:拦截代理类的setBeanFactory()方法,给对应属性赋值
  • BeanMethodInterceptor:CGLIB代理对象拦截器。作用:拦截所有@Bean方法的执行,以支持可以通过直接调用@Bean方法来管理依赖关系(当然也支持FactoryBean模式)

配置类解析流程图

配置类的解析均是交由ConfigurationClassPostProcessor来引导。在Spring Framework里(非Spring Boot)里,它是BeanDefinitionRegistryPostProcessor处理器的唯一实现类, 用于引导处理@Configuration配置类。解析入口是postProcessBeanDefinitionRegistry()方法,实际处理委托给了processConfigBeanDefinitions()方法。

spring-cglib

配置类的解析由 ConfigurationClassPostProcessor 引导,入口为 postProcessBeanDefinitionRegistry()


配置类增强流程图

如果一个配置类是Full模式,那么它就需要被CGLIB字节码提升。增强动作委托给enhanceConfigurationClasses(beanFactory)去完成。

spring-cglib

Full 模式配置类会被 CGLIB 增强,生成代理对象。


生成增强子类字节码流程图

针对于Full模式配置类的字节码生成,委托给ConfigurationClassEnhancer增强器去完成,最终得到一个CGLIB提升过的子类Class字节码对象。

生成增强子类字节码流程图

字节码由 Enhancer 生成。


拦截器执行流程图

拦截器是完成增强实际逻辑的核心部件,因此它的执行流程需要引起重视。一共有两个“有用”的拦截器,分别画出。

BeanFactoryAwareMethodInterceptor

eanFactoryAwareMethodInterceptor拦截流程图

拦截 setBeanFactory() 方法,注入 beanFactory

BeanMethodInterceptor

BeanMethodInterceptor拦截流程图

拦截 @Bean 方法,保证依赖注入和单例。


参考资料