本章节咱们来探索Spring中一个常用的注解@Configuration
。咱们先来了解一下该注解的效果是:用来界说当时类为装备类
。
那啥是装备类啊,有啥用啊。这个咱们得结合实际运用场景来说,通常状况下。加了@Configuration
的装备类内部,都会包括一个或多个@Bean注解的办法
。
为了简化界说,在后续咱们称@Bean注解的办法为工厂办法。
装备类的奥妙就在这儿,Spring会确保屡次调用@Bean标示的工厂办法,不会重复发生新的方针,始终是同一个
,这也贯彻了Spring的单例哲学。
屡次调用创立办法,发生的竟然是同一个方针,这貌似违反了编程的基础原理。怎样可能,一定是Spring做了什么,那就跟随着贰师兄的脚步一同解开@Configuration
的神秘面纱吧。
这儿咱们很简略发生一个误解,以为只要在加了@Configuration的装备类中,运用@Bean,才干将自界说创立的方针放入Spring容器中。其实不然,在Spring中:万物皆为装备类。
在任何可以被Spring办理的类
(比如加了@Component的类)中,界说@Bean办法,都能将自界说方针放入到Spring容器,@Bean自身的才干和@Configuration无关哦
。
1. @Configuration的效果
咱们先经过一个简略的案例,看一下@Configuration的效果。
需求特别阐明的是:
@Configuration继承了@Component
,这意味着@Configuration具有@Component的全部功用,这也正是只加@Configuration,也能被Spring扫描并处理的原因。
- 状况一:被@Component标识
@Component
public class MarkedByComponent {
@Bean
public A a(){
return new A();
}
@Bean
public B b(){
a();
return new B();
}
}
// 输出信息:
// A created...
// A created...
// B created...
- 状况二:被@Configuration标识
@Configuration
public class MarkedByConfiguration {
@Bean
public A a(){
return new A();
}
@Bean
public B b(){
a();
return new B();
}
}
// 输出信息:
// A created...
// B created...
-
结论
经过上述输出咱们发现:在屡次调用a()的状况下,被@Component标识的类,A会被创立屡次。而被@Configuration标识的装备类,A只会被创立一次。此刻咱们可以斗胆猜想:在@Configuration标识的装备类中,重复调用@Bean标识的工厂办法,Spring会对创立的方针进行缓存。仅在缓存中不存在时,才会经过工厂办法创立方针。后续重复调用工厂办法创立方针,先去缓存中找,不直接创立方针
。
这儿小伙伴可能有个疑问,a()只在b()被调用一次,为什么说屡次呢,其实除了在b()中声明式调用,因为a()被@Bean标识,Spring也会主动调用一次的哦。
2. @Configuration的原理剖析
上一章节,咱们经过一个简略的案例,了解@Configuration的效果:使@Bean标识的工厂办法,不会重复创立bean方针
。一同也斗胆猜想了,Spring对@Configuration标识的装备类,做了缓存处理,然后让@Bean工厂办法,有了"幂等的才干"
。本章节咱们一同探索一下,这个增强的缓存逻辑,是怎样做到的?
要对一个类的功用进行增强。署理
这个词是不是现已在脑海中呼之欲出了,是的,便是署理。Spring对@Configuration标识的类做了署理,然后进行功用的增强。
当然,现在咱们离直接剖析署理功用还有点遥远,究竟步子不能迈的太大,不然简略扯着蛋。一步步来,首要咱们先看看Spring是怎样解析@Configuration注解的,究竟需求先找出来,才干署理增强嘛。
2.1 @Configuration解析
对@Configuration的解析进程是在spring扫描类的时分进行的
。这儿需求介绍一下,Spring把加了@Configuration注解的称为全装备类,其他的称为半装备类,两者判别标准是:是否加了@Configuration注解。
// ConfigurationClassParser.java
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
//... 省掉部分非相关代码
// 解析是否加了@ComponentScan, 并进行扫描
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// 1: 解析ComponentScan装备信息,完成扫描(扫描出来的类在beanDefinitionMap中)
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 2: 遍历扫描出来的类,查看是不是装备类(装备类需求持续递归解析)
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 要点:判别类是不是装备类, 并标示类特点信息(是否为全装备类、@Order值等)
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 3 假如扫描出来的类,是装备类,还需求递归处理扫描出来的装备类
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
//... 省掉部分非相关代码
}
//ConfigurationClassUtils.java
// 全装备类符号值
public static final String CONFIGURATION_CLASS_FULL = "full";
// 半装备类符号值
public static final String CONFIGURATION_CLASS_LITE = "lite";
public static boolean checkConfigurationClassCandidate(
BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
//... 省掉部分非相关代码
// 2.1 从注解信息中, 获取@Configuration的注解信息(如存在,符号为为全装备类)
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
// 有@Component,@ComponentScan,@Import,@ImportResource注解,被符号成半装备类
else if (config != null || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
//... 省掉部分非相关代码
}
咱们整理一下这部分代码的逻辑:
- 获取装备类上
@ComponentScan(basePackages="xxx")
注解,交给组件componentScanParser
解析,该组件会扫描出对应包途径下需求Spring处理的类,并封装成BeanDefinition
,一同回来扫描的类界说信息。 - 遍历扫描出的类,查看是否为装备类,装备类需求持续解析(装备类可能有需求处理的Spring功用)。
- 在判别是否为装备类时,也会给类打上标签,加了@Configuration的符号为
全装备类
,其他的符号为半装备类
。标识的办法便是在BeanDefinition
的attributes
特点中加入XX.configurationClass:full
标识。
判别是否为装备类时,不仅仅仅仅加了
@Configuration
的为装备类,加了@Component
,@ComponentScan
,@Import
,@ImportResource
等注解的,也是装备类,仅仅为半装备类罢了。上述咱们说@Configuration的效果是符号为装备类,这儿看其实是不精确的,精确说应该是符号为全装备类。可是这是更内部的逻辑,通常来说,@Configuration符号的便是装备类,其他符号的为一般类。
总结一下,在扫描阶段,Spring会对扫描出来的类进行全装备类仍是半装备类的标识
。当然这儿也仅仅是标识出来,并没有运用,这是在给后边生成署理方针做准备。
这儿咱们不得不吐槽一下,作为业界标杆的Spring,在办法命名上也如此混乱,在checkConfigurationClassCandidate()这个表明check动作的办法里,做了很多BeanDefinition特点解析赋值的操作,简直是在混淆视听啊有没有。看来命名确实是编程界最大的难题啊。
2.2 全装备类增强
在上一章节,咱们剖析了Spring对标示了@Configuration装备类的查找和解析进程,总结起来便是:判别类上是否有@Configuration注解,有便是全装备类,没有便是半装备类。关于全装备的增强,咱们也反复提到过,是凭借署理来完成的。详细的的调用逻辑咱们一同来看一下。
//ConfigurationClassPostProcessor.java
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//...省掉部分代码
// 对全装备类进行增强
this.enhanceConfigurationClasses(beanFactory);
}
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
// 1: 查找需求增强的装备类
// 遍历现已注册多的beanName
for (String beanName : beanFactory.getBeanDefinitionNames()) {
// ...省掉部分代码
// 1.2 查找全装备类,放入configBeanDefs
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
// ...省掉部分代码
// 将契合条件的,放入configBeanDefs中
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
// 2: 对全装备类进行增强
// 2.1 创立一个装备类的增强器
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
Class<?> configClass = beanDef.getBeanClass();
// 2.2 对类进行增强,运用cglib单例
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
// 2.3 修正BeanClass为加强类,这儿之所以不运用加强方针,是因为还需求将加强类交给spring实例化
beanDef.setBeanClass(enhancedClass);
}
}
}
咱们整理一下这部分代码的逻辑:
- 查找需求增强的装备类,这儿直接找出类
BeanDefinition
的attributes
特点中XX.configurationClass
标识值为full
的即可。 - 对装备类生成署理,进行功用增强。
- 修正
BeanDefinition
的beanClass特点为署理类,后续Spring在发生实例时,运用的便是署理类了。
这儿有个要点,便是生成署理类,Spring采用的是CGLIB增强的办法,下面咱们顺藤摸瓜,看看Spring是怎样操作的。
2.3 生成署理类
// ConfigurationClassEnhancer.java
// 回调器列表
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
// 回调过滤器
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);
//为原始类生成署理类
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
// 1: newEnhancer() 生成生成CGLIB实例
// createClass() 生成署理类
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
// 2: 回来增强后的类
return enhancedClass;
}
// 生成CGLIB实例
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
// 设置接口为EnhancedConfiguration
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
// 给署理类生成一个$$beanFactory字段
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
// 设置回调过滤器为EnhancedConfiguration,能依据传入
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
// 生成署理类
private Class<?> createClass(Enhancer enhancer) {
Class<?> subclass = enhancer.createClass();
// 添加阻拦器
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
return subclass;
}
咱们直接来看代码,这儿其实很多运用了CGLIB相关的常识,感兴趣的小伙伴可以自行百度补课,不补也不要紧,这儿会带咱们简略了解一下:
- 先是创立了一个增强器
Enhancer
,为增强器设置了相关特点,咱们看一下中心特点:- superclass: 父类,也便是被署理类,CGLIB的原理是为被署理类生成子类,然后完成功用增强。
- interfaces: 完成的接口,这儿硬性指定为
EnhancedConfiguration
。(后续会运用该标识) - namingPolicy:设置署理类的名称战略,默以为
BySpringCGLIB
,这也是Spring生成的署理类类型包括xxBySpringCGLIB的原因。 - strategy:生成战略,这是设置的BeanFactoryAwareGeneratorStrategy,默许逻辑是给署理类动态添加
$$beanFactory
字段(后续会运用)。 - callbackFilter:回调过滤器,在CGLib回调时可以设置不同办法履行不同的回调逻辑,或许根本不履行回调。回调过滤器的功用便是对履行办法挑选适宜的阻拦器。这儿一定要差异清楚阻拦器和回调过滤器的功用:
- 阻拦器
Callback
:在调用方针办法时,CGLib会调用阻拦器进行调用阻拦,来完成增强的署理逻辑,当然里边可以对应原始办法(在父类中)。 - 回调过滤器
CallbackFilter
:为履行办法挑选阻拦器,CGLIB可以设置多个阻拦器,然后依据详细履行的办法再进行挑选分发。
- 阻拦器
- 凭借增强器
Enhancer
,生成署理类,并注册阻拦器。 - 回来署理类。
2.3.1 阻拦器的挑选
在上述进程,咱们现已知道Spring为标示了@Configuration装备类生成了署理类,并指定了回调过滤器
和阻拦器
,一同将生成方针的类型修正为了署理类。那Spring在实例化的时分,实例化的便是署理方针
了。在装备类中工厂办法(@Bean标示的办法)调用的时分,会先经过回调过滤器挑选适宜的阻拦器,然后再由阻拦器进行阻拦、并完成功用增强
。
//ConfigurationClassEnhancer.ConditionalCallbackFilter.java
// 依据办法信息,挑选阻拦器
@Override
public int accept(Method method) {
for (int i = 0; i < this.callbacks.length; i++) {
Callback callback = this.callbacks[i];
// 要点:阻拦器的挑选, BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor继承了ConditionalCallback,NoOp.INSTANCE 没有继承
if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {
return i;
}
}
throw new IllegalStateException("No callback available for method " + method.getName());
}
//ConfigurationClassEnhancer.BeanFactoryAwareMethodInterceptor.java
// 匹配逻辑: 只处理setBeanFactory办法
@Override
public boolean isMatch(Method candidateMethod) {
return isSetBeanFactory(candidateMethod);
}
public static boolean isSetBeanFactory(Method candidateMethod) {
return (candidateMethod.getName().equals("setBeanFactory") &&
candidateMethod.getParameterCount() == 1 &&
BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}
//ConfigurationClassEnhancer.`BeanMethodInterceptor.java
// 匹配逻辑: 只处理办法上加了@Bean注解的,而且不是setBeanFactory()
@Override
public boolean isMatch(Method candidateMethod) {
return (candidateMethod.getDeclaringClass() != Object.class &&
!BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}
经过上述源码,咱们可以快速找到回调过滤器挑选阻拦器的逻辑: if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method))
,咱们剖析一下这个判别的条件,翻译过来便是:
- 假如是ConditionalCallback类型的阻拦器,直接经过回调器isMatch()来判别。
- 假如不是ConditionalCallback类型的阻拦器,则直接判别为匹配成功。
初看可能有点懵,咱们结合传递进来的阻拦器来看,阻拦器列表是:BeanMethodInterceptor
、BeanFactoryAwareMethodInterceptor
、NoOp.INSTANCE
,其间前两个都是ConditionalCallback
类型的,最终一个则是默许的空完成。一同传递的回调过滤器列表,是个有序数组,所以会优先匹配BeanMethodInterceptor
和BeanFactoryAwareMethodInterceptor
,假如这两个都不能匹配,则默许匹配到空完成NoOp.INSTANCE
。
关于
BeanMethodInterceptor
、BeanFactoryAwareMethodInterceptor
的匹配逻辑,在代码注释中现已有明确的阐明,比较明晰,咱们留意看哦。
2.3.2 阻拦器的功用
经过上述的诲人不倦的烦琐,咱们现已知道,Spring为标示了@Configuration装备类生成了署理类,在调用装备类的办法时,会先经过回调过滤器挑选阻拦器,然后由阻拦器对办法进行增强。一同也清楚主要是靠BeanMethodInterceptor
和BeanFactoryAwareMethodInterceptor
这两个阻拦器器发挥效果,本章节咱们一同看一下这两个阻拦器的功用。
在剖析这两个阻拦器的功用之前,咱们先来回想一下@Configuration装备类的效果,让加了@Bean的办法有了"幂等的才干"
,不会重复创立方针。
这儿小伙伴需求留意哦,@Bean注解自身的才干便是把咱们自己发生的方针,放入到Spring容器中,便于咱们依靠注入,这个@Configuration无关,在半装备类下该功用也是正常的哦。@Configuration的加持,仅仅使其有了幂等性哦。
现在咱们现已知道@Configuration的效果,完成原理咱们也清楚,经过CGLIB生成署理子类,详细的完成咱们猜想是:把@Bean工厂办法生成的方针先放入到Spring容器中缓存,重复调用的时分,从缓存中直接获取
。实际上也确实如此,Spring便是这么做的。
OK,现在一切都水落石出,只剩下最终一层神秘的面纱没有解开,便是Spring怎样做到的。要想达到这样的效果,其实有两个问题需求先处理:
- 对@Bean的工厂办法发生的方针,可以去Spring容器中查重,不存在则生成方针,存则在直接获取的。
- 注入Spring容器,也便是
beanFactory
,因为需求到容器中查重、获取,所以需求先容器方针。
Spring正是用BeanMethodInterceptor
、BeanFactoryAwareMethodInterceptor
这两个阻拦器,来处理这两个问题的。咱们先来看注入Spring容器beanFactory的完成。
2.3.2.1 给署理类注入bean容器
为了完成@Bean办法的”幂等”,咱们需求依靠beanFactory,可是没有啊,怎样办呢,要求程序员运用@Autowired BeanFactory
手动放一个?手动是不可能,这辈子都是不可能的,主要是丢不起这个人。手动不可,那就主动放进来吧,这也便是为什么Spring在出产的署理类中,强行加了一个beanFactory特点的原因。
- 为署理类添加beanFactory特点
//ConfigurationClassEnhancer.java
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
// ...省掉其他字段的赋值
// 给署理类生成一个$$beanFactory字段
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
return enhancer;
}
private static class BeanFactoryAwareGeneratorStrategy extends
ClassLoaderAwareGeneratorStrategy {
public BeanFactoryAwareGeneratorStrategy(@Nullable ClassLoader classLoader) {
super(classLoader);
}
@Override
protected ClassGenerator transform(ClassGenerator cg) throws Exception {
// 动态添加字段"$$beanFactory"
ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
@Override
public void end_class() {
declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
super.end_class();
}
};
return new TransformingClassGenerator(cg, transformer);
}
}
经过源码,咱们可以看到,在生成子类字节码的时分,就凭借CGLIB的才干,给署理类动态添加了一个特点$$beanFactory
,其类型正是BeanFactory
类型,这便是主动放入的。
-
运用阻拦器BeanFactoryAwareMethodInterceptor给beanFactory特点赋值
上述进程仅仅添加了特点
$$beanFactory
,还没有赋值呢。其实在特点填充的时分,会进行赋值。咱们一同看一下是怎样赋值的。
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
// 给$$beanFactory字段赋值
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 署理类中需求存在字段: $$beanFactory
Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
Assert.state(field != null, "Unable to find generated BeanFactory field");
// 经过反射API,为该字段注入spring容器beanFactory
field.set(obj, args[0]);
if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
// 假如注解装备类的父类完成了BeanFactoryAware,传入参数BeanFactory,并履行父类办法
return proxy.invokeSuper(obj, args);
}
return null;
}
}
经过源码,咱们可以看到,直接经过反射给$$beanFactory字段赋值,那么在后续其他流程中,就可以正常运用了,so happy。
其实这儿并不happy,原理和完成,咱们都知道了,可是咱们有没有疑问,这个阻拦器中直接从参数args[0]获取了beanFactory,为什么?传递进来的参数到底是什么?又是什么时分回调这个阻拦器的。笔者在网上搜了不少文章,如同并没有人提及或许说清这个事情,可能确实有点难度吧,贰师兄细心研讨了下,限于篇幅和对Spring全体系统的约束,本节咱们不打开,结尾写个选读吧,咱们可以自行决定是否应战一下哦。
2.3.2.2 确保@Bean工厂办法”冥等”
// 测验代码
@Configuration
public class MarkedByConfiguration {
@Bean
public A a(){
return new A();
}
@Bean
public B b(){
a();
return new B();
}
}
咱们依据测验案例中代码b()->a()
来看一下阻拦器BeanMethodInterceptor
是怎样确保幂等的。咱们仍是先看源码,有个全体印象:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
// 从增强后的注解装备类实例中,获取BeanFactory(也便是成员变量$$beanFactory)
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
// ① 判别当时调用到的办法,是否是正在履行创立的@Bean工厂办法
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 假如当时履行的是工厂办法来实例化bean,那就履行父类中的办法
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// ② 假如当时履行的办法不是工厂办法,此刻就去spring容器中获取@Bean对应的bean实例
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
// 获取履行中的@Bean办法,在履行@Bean办法的创立时,会将当时@Bean办法放入ThreadLocal中,这儿再取出来
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
// 判别履行的@Bean办法,和正在回调的办法是否相同
return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}
}
这儿咱们只剖析中心办法,榜首步便是 if (isCurrentlyInvokedFactoryMethod(beanMethod))
这个判别逻辑:
- 获取当时履行的工厂办法(@Bean办法)。在Spring履行b()工厂办法时,就会
将b()放入到ThreadLocal中
,标识该工厂办法正在创立中。 - 匹配
当时正在履行的工厂办法,和当时履行的办法,是否是同一个办法
。这儿需求留意差异,比如在履行b()时,履行到榜首行a()的调用了,进入a()办法后,当时履行的办法是a(),正在履行的@Bean工厂办法仍然是b(),两者必定不是同一个办法,回来的便是false。
了解了这个办法的判别逻辑,下面咱们再来剖析履行b()的详细流程:
- 履行到b办法时,
将b()放入到ThreadLoacl中,标识正在创立
。 - 履行b(),因为此刻b()所在的装备类,现已为署理类实例了,所以会履行阻拦器的回调,且b()是@Bean修饰的工厂办法,回调过滤器会挑选
BeanMethodInterceptor
,并履行intercept()阻拦逻辑。 - 履行isCurrentlyInvokedFactoryMethod()判别,也便是①的代码,此刻ThreadLocal中是b(),正在履行的也是b(),
二者相同,条件成立
,需求履行②处的代码。 -
开端履行父类中的办法
,也便是原始的,咱们自己编写的办法。留意:此刻现已从阻拦器的办法中跳出来了,履行的是原始的代码。依据履行的次序,首要履行的便是a()的创立。 - 履行a(),因为此刻a()所在的装备类,也是署理类实例,且为被@Bean标识的工厂,所以
也会被BeanMethodInterceptor阻拦,此刻也会履行isCurrentlyInvokedFactoryMethod()判别,可是因为此刻ThreadLocal中获取到的办法仍是b(),而正在履行的办法是a(),二者不同
,需求履行③处的代码。 - ③处的代码逻辑,主要是
去容器中获取bean实例
,假如不存在,会先创立bean,放入容器后回来。这不是咱们讨论的中心问题,咱们假设a()之前现已履行过了,在容器中是存在的,此刻直接回来了a方针,并没有再次创立。 - 持续履行原始a()的第二行及后边的代码,无关咱们的剖析,可以忽略。
咱们在代码中加入注释,让咱们加深一下印象:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
// 从增强后的注解装备类实例中,获取BeanFactory(也便是成员变量$$beanFactory)
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
// ① 判别当时调用到的办法,是否是正在履行创立的@Bean工厂办法
// 1.1: b()履行时,现已将y放入ThreadLocal中,这儿获取到的是b,条件成立
// 2.1 履行到b()调用a()了,a()也被署理了,也会履行到这儿,此刻ThreadLocal中是y,条件不成立,履行②
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 1.2 履行父类中的办法,也便是@Bean工厂办法的逻辑,会调用a()办法呢
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// ② 假如当时履行的办法不是工厂办法,此刻就去spring容器中获取@Bean对应的bean实例
// 2.2 去Spring容器中查找a方针,不存在会实例化的,这个可以回来
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
}
在上面的流程剖析时。咱们发现,该阻拦器确实做到了不会重复创立@Bean工厂办法发生的bean,中心原理是对@Bean办法的每次调用都会阻拦,然后先去容器查重,存在直接回来。正是这个操作,确保了@Bean工厂办法的幂等。
至此,咱们现已理解了在@Configuration装备类的加持下,为什么@Bean工厂办法可以坚持幂等的隐秘。期望小伙伴们能有所收获,这是对贰师兄最大的安慰哈。
3. 增强代码的调用(选读)
3.1 BeanFactoryAwareMethodInterceptor的调用
上述在提到署理类中,动态添加的特点$$beanFactory
是经过阻拦器BeanFactoryAwareMethodInterceptor反射赋值的。而且在阻拦器BeanMethodInterceptor直接运用了。可是没有提提及BeanFactoryAwareMethodInterceptor的被调用时机和调用参数,原因是需求小伙伴对Spring的生命周期和Bean的生命周期需求比较熟悉的了解。咱们需求弥补对应的常识。
关于Bean的生命周期,聊透Spring bean的生命周期有着比较详细的剖析。可是对Spring的生命周期,还没有来得及剖析,感兴趣的小伙伴,可以留言催更哦。
- 注册BeanPostProcessor,触发阻拦器
在上述的剖析中,咱们现已知道,关于全装备类生成署理子类的逻辑在ConfigurationClassPostProcessor#postProcessBeanFactory()
中,该办法除了生成署理类外,还手动添加了ImportAwareBeanPostProcessor这个Bean的后置处理器,该后置处理器的效果便是调用setBeanFactory办法(会被阻拦器BeanFactoryAwareMethodInterceptor阻拦,进而履行阻拦器的内容),最终为特点$$beanFactory赋值
。
//ConfigurationClassPostProcessor.java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// ...其他代码
// 对全装备类进行增强
enhanceConfigurationClasses(beanFactory);
// 手动添加一个BeanPostProcessor,用来设置setBeanFactory
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
private static class ImportAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
@Override
public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
// 调用setBeanFactory()
if (bean instanceof EnhancedConfiguration) {
((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
}
return pvs;
}
}
咱们可以看到,该后置处理器postProcessProperties()的逻辑便是调用setBeanFactory(),而且只调用类型是EnhancedConfiguration子类的哦。小伙伴们还记不记得,Spring为全装备类生成的署理类,手动加了完成EnhancedConfiguration接口的哦,这不就对上了嘛,你说巧不巧。
还有小伙伴需求留意,该后置处理器一旦调用,履行到setBeanFactory()时,就会被BeanFactoryAwareMethodInterceptor阻拦,此刻传递的参数是this.beanFactory
,也便是Spring的bean容器,所以在阻拦器直接args[0]反射赋值,才是没有问题的。
- ImportAwareBeanPostProcessor后置处理器的调用 现在咱们现已知道经过后置处理器ImportAwareBeanPostProcessor进行setBeanFactory()的回调,也知道传递的参数是Spring的bean容器。那么问题又来了,这个后置处理器是什么时分调用,究竟他调用了,阻拦器才干履行,特点$$beanFactory才干被赋值啊。
好烦啊,有没有,没办法,Spring的代码便是这样,功用完成的链路很长,也很涣散,一环扣一环,想串联起来很难,这也是笔者写这个系列文章的初衷,想给咱们整合起来,理解来龙去脉,这样一来文章篇幅势必很长,没办法的,咱们忍受一下。好了,不烦琐了,咱们持续。
细心看笔者聊透Spring bean的生命周期这篇文章的小伙伴可能有印象,在特点填充这一步,回调的后置处理器,有ImportAwareBeanPostProcessor,咱们在一同看一下:
OK,现在这个这个链路也明晰了,咱们一同总结一下:
3.2 BeanMethodInterceptor的调用
关于@Bean工厂办法的调用时机,其实是在bean的生命周期的初始化bean阶段
,不清楚的小伙伴自行查看吧。烦琐不动了。
因为BeanFactoryAwareMethodInterceptor阻拦器的调用在bean的生命周期的特点填充阶段
,BeanMethodInterceptor的调用在初始化bean阶段
,所以是能确保BeanFactoryAwareMethodInterceptor
阻拦器的触发早于BeanMethodInterceptor
阻拦器的,这也是BeanMethodInterceptor
可以直接运用的原因。
最终咱们一同总结一下吧,期望咱们多学习,多进步,最终祝咱们新年快乐,发大财。