- 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放进方法入参里即可)
总结
- 该模式下,配置类本身不会被CGLIB增强,放进IoC容器内的就是本尊
- 该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
- 该模式下,配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新实例而并非IoC容器内的单例
- 该模式下,配置类就是一普通类嘛,所以@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
总结
- 该模式下,配置类会被CGLIB增强(生成代理对象),放进IoC容器内的是代理
- 该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
- 该模式下,配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向IoC内的那个单例
- 该模式下,@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 配置类深度剖析
相关类说明
@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()
方法。
配置类的解析由
ConfigurationClassPostProcessor
引导,入口为postProcessBeanDefinitionRegistry()
。
配置类增强流程图
如果一个配置类是Full模式,那么它就需要被CGLIB字节码提升。增强动作委托给enhanceConfigurationClasses(beanFactory)
去完成。
Full 模式配置类会被 CGLIB 增强,生成代理对象。
生成增强子类字节码流程图
针对于Full模式配置类的字节码生成,委托给ConfigurationClassEnhancer
增强器去完成,最终得到一个CGLIB提升过的子类Class字节码对象。
字节码由
Enhancer
生成。
拦截器执行流程图
拦截器是完成增强实际逻辑的核心部件,因此它的执行流程需要引起重视。一共有两个“有用”的拦截器,分别画出。
BeanFactoryAwareMethodInterceptor
拦截
setBeanFactory()
方法,注入beanFactory
。
BeanMethodInterceptor
拦截
@Bean
方法,保证依赖注入和单例。