当大潮退去,才晓得谁在裸泳。关注公众号【BAT 的乌托邦】开启专栏式学习,回绝浅尝辄止。本文 https://www.yourbatman.cn 已收录,外面一并有 Spring 技术栈、MyBatis、中间件等小而美的专栏供以学习哦。
前言
各位小伙伴大家好,我是 A 哥。本文对 Spring @Configuration
配置类持续进阶,尽管有点烧脑,但目标只有一个:为拿高薪备好弹药。如果说上篇文章曾经脑力有点“不适”了,那这里得先给你个下马威:本篇文章内容将更加的让你“感觉不适”。
读本文之前,为确保连贯性,倡议你移步先浏览上篇文章内容,中转电梯:你自我介绍说很懂 Spring 配置类,那你怎么解释这个景象?
为什么有些时候我会倡议先浏览上篇文章,这的确是无奈之举。技术的内容个别都具备很强相关性,它是须要有 Context 上下文撑持的,所以花几分钟先理解相干内容成果更佳,磨刀不误砍柴工的情理大家都懂。同时呢,这也是写深度剖析类的技术文章的难堪之处:吃力反而不讨好,须要保持。
版本约定
本文内容若没做非凡阐明,均基于以下版本:
- JDK:
1.8
- Spring Framework:
5.2.2.RELEASE
注释
上篇文章介绍了代理对象两个拦截器其中的前者,即 BeanFactoryAwareMethodInterceptor
,它会拦挡setBeanFactory()
办法从而实现给代理类指定属性赋值。通过第一个拦截器的解说,你可能胜利“忽悠”很多面试官了,但仍旧不可能解释咱们最常应用中的这个纳闷:为何通过调用 @Bean 办法最终指向的仍旧是同一个 Bean 呢?
带着这个疑难,开始本文的陈诉。请系好安全带,筹备发车了 …
Spring 配置类的应用误区
依据不同的配置形式,展现不同状况。从 Lite 模式的应用 产生误区,到应用 Full 模式解决问题,最初引出解释为何有此成果的起因剖析 / 源码解析。
Lite 模式:谬误姿态
配置类:
public class AppConfig {
@Bean
public Son son() {Son son = new Son();
System.out.println("son created..." + son.hashCode());
return son;
}
@Bean
public Parent parent() {Son son = son();
System.out.println("parent created... 持有的 Son 是:" + son.hashCode());
return new Parent(son);
}
}
运行程序:
public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig appConfig = context.getBean(AppConfig.class);
System.out.println(appConfig);
// bean 状况
Son son = context.getBean(Son.class);
Parent parent = context.getBean(Parent.class);
System.out.println("容器内的 Son 实例:" + son.hashCode());
System.out.println("容器内 Person 持有的 Son 实例:" + parent.getSon().hashCode());
System.out.println(parent.getSon() == son);
}
运行后果:
son created...624271064
son created...564742142
parent created... 持有的 Son 是:564742142
com.yourbatman.fullliteconfig.config.AppConfig@1a38c59b
容器内的 Son 实例:624271064
容器内 Person 持有的 Son 实例:564742142
false
后果剖析:
-
Son 实例被 创立了 2 次 。很显著这两个 不是同一个 实例
- 第一次是由 Spring 创立并放进容器里(
624271064
这个) - 第二次是由结构 parent 时创立,只放进了 parent 里,并没放进容器里(
564742142
这个)
- 第一次是由 Spring 创立并放进容器里(
这样的话,就出问题了。问题体现在这两个方面:
- Son 对象被创立了两次,单例模式被突破
- 对 Parent 实例而言,它依赖的 Son 不再是 IoC 容器内的那个 Bean,而是一个十分一般的 POJO 对象而已。所以这个 Son 对象将不会享有 Spring 带来的任何“益处”,这在理论场景中个别都是会有问题的
这种状况在生产上是 肯定须要防止,那怎么破呢?上面给出 Lite 模式下应用的正确姿态。
Lite 模式:正确姿态
其实这个问题,当初这么智能的 IDE(如 IDEA)曾经能教你怎么做了:
依照“批示”,能够应用 依赖注入 的形式代替从而防止这种问题,如下:
// @Bean
// public Parent parent() {// Son son = son();
// System.out.println("parent created... 持有的 Son 是:" + son.hashCode());
// return new Parent(son);
// }
@Bean
public Parent parent(Son son){System.out.println("parent created... 持有的 Son 是:" + son.hashCode());
return new Parent(son);
}
再次运行程序,后果为:
son created...624271064
parent created... 持有的 Son 是:624271064
com.yourbatman.fullliteconfig.config.AppConfig@667a738
容器内的 Son 实例:624271064
容器内 Person 持有的 Son 实例:624271064
true
bingo,完满解决了问题。如果你保持应用 Lite 模式,那么请留神它的优缺点哦(Full 模式和 Lite 模式的优缺点见这篇文章)。
没有认真看的同学可能会问:我明明就是依照第一种形式写的,也失常 work 没问题呀。说你是不仔细吧还真是,不信你再回去瞅瞅比照比照。如果你用第一种形式并且可能“失常 work”,那请你查查类头上是不是标注有 @Configuration
注解?
Full 模式:
Full 模式是容错性最强的一种形式,你乱造都行,没啥顾虑。
当然喽,办法不能是 private/final。但个别状况下谁会在配置里 final 掉一个办法呢?你说对吧~
@Configuration
public class AppConfig {
@Bean
public Son son() {Son son = new Son();
System.out.println("son created..." + son.hashCode());
return son;
}
@Bean
public Parent parent() {Son son = son();
System.out.println("parent created... 持有的 Son 是:" + son.hashCode());
return new Parent(son);
}
}
运行程序,后果输入:
son created...1797712197
parent created... 持有的 Son 是:1797712197
com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$8ef51461@be64738
容器内的 Son 实例:1797712197
容器内 Person 持有的 Son 实例:1797712197
true
后果是完满的 。它可能保障你通过调用标注有 @Bean 的办法失去的是 IoC 容器外面的实例对象,而非从新创立一个。相比拟于 Lite 模式,它还有另外一个区别:它会为配置类生成一个CGLIB
的代理子类对象放进容器,而 Lite 模式放进容器的是原生对象。
凡事皆有代价,所有皆在取舍 。原生的才是效率最高的,是对 Cloud Native 最为敌对的形式。但在理论“举荐应用”上, 业务端开发个别只会应用 Full 模式,毕竟业务开发的同学程度是残参差不齐的,容错性就显得至关重要了。
如果你是容器开发者、中间件开发者 … 举荐应用 Lite 模式配置,为容器化、Cloud Native 做好筹备嘛~
Full 模式既然是面向应用侧为罕用的形式,那么接下来就趴一趴 Spring 到底是施了什么“魔法”,让调用 @Bean 办法居然能够不进入办法体内而指向同一个实例。
BeanMethodInterceptor 拦截器
终于到了明天的主菜。对于后面的流程剖析本文就一步跳过,单刀直入剖析 BeanMethodInterceptor
这个拦截器,也也就是所谓的两个拦截器的 后者。
舒适提醒:亲务必确保曾经理解过了上篇文章的流程剖析哈,不然上面内容很容易造成你
脑力不适
的
相较于上个拦截器,这个拦截器不可为不简单。官网解释它的作用为:拦挡任何标注有 @Bean
注解的办法的 调用 ,以确保正确处理 Bean 语义,例如 作用域(请别疏忽它)和 AOP 代理。
简单归简单,但没啥好怕的,一步一步来呗。同样的,我会按如下两步去理解它:执行机会 + 做了何事。
执行机会
废话不多说,间接联合源码解释。
BeanMethodInterceptor:@Override
public boolean isMatch(Method candidateMethod) {return (candidateMethod.getDeclaringClass() != Object.class &&
!BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}
三行代码,三个条件:
- 该办法不能是 Object 的办法(即便你 Object 的办法标注了 @Bean,我也不认)
- 不能是
setBeanFactory()
办法。这很容易了解,它交给上个拦截器搞定即可 - 办法必须标注标注有 @Bean 注解
简而言之,标注有 @Bean 注解办法 执行时
会被拦挡。
所以上面例子中的 son()和 parent()这两个,以及 parent()外面调用的 son()办法的执行 它都会拦挡(一共拦挡 3 次)~
小细节:办法只有是个 Method 即可,无论是 static 办法 还是一般办法,都会“参加”此判断逻辑哦
做了何事
这里是具体拦挡逻辑,会比第一个拦截器简单很多。源码不算十分的多,但牵扯到的货色还真不少,比方 AOP、比方 Scope、比方 Bean 的创立等等,了解起来还蛮吃力的。
本处以拦挡到 parent()
办法的执行为例,联合源码进行跟踪解说:
BeanMethodInterceptor:// enhancedConfigInstance:被拦挡的对象实例,也是代理对象
// beanMethod:parent()办法
// beanMethodArgs:空
// cglibMethodProxy:代理。用于调用其 invoke/invokeSuper()来执行对应的办法
@Override
@Nullable
public Object intercept(Object enhancedConfigInstance,
Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable {
// 通过反射,获取到 Bean 工厂。也就是 $$beanFactory 这个属性的值~
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
// 拿到 Bean 的名称
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// 判断这个办法是否是 Scoped 代理对象 很显著本利里是没有标注的 暂先略过
// 简答的说:parent()办法头上是否标注有 @Scoped 注解~~~
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {beanName = scopedBeanName;}
}
// ======== 上面要解决 bean 间办法援用的状况了 ========
// 首先:查看所申请的 Bean 是否是 FactoryBean。也就是 bean 名称为 `&parent` 的 Bean 是否存在
// 如果是的话,就创立一个代理子类,拦挡它的 getObject()办法以返回容器里的实例
// 这样做保障了办法返回一个 FactoryBean 和 @Bean 的语义是成果一样的,确保了不会反复创立多个 Bean
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
// 先失去这个工厂 Bean
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
// Scoped proxy factory beans are a special case and should not be further proxied
// 如果工厂 Bean 曾经是一个 Scope 代理 Bean,则不须要再加强
// 因为它曾经可能满足 FactoryBean 提早初始化 Bean 了~
}
// 持续加强
else {return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
// 查看给定的办法是否与以后调用的容器绝对应工厂办法。// 比拟办法名称和参数列表来确定是否是同一个办法
// 怎么了解这句话,参照上面详解吧
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 这是个小细节:若你 @Bean 返回的是 BeanFactoryPostProcessor 类型
// 请你应用 static 静态方法,否则会打印这句日志的~~~~
// 因为如果是非静态方法,局部后置解决生效解决不到你,可能对你程序有影像
// 当然也可能没影响,所以官网也只是倡议而已~~~
if (logger.isInfoEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {... // 输入 info 日志}
// 这示意:以后 parent()办法,就是这个被拦挡的办法,那就没啥好说的
// 相当于在代理代理类里执行了 super(xxx);
// 然而,然而,然而,此时的 this 仍旧是代理类
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// parent()办法里调用的 son()办法会交给这里来执行
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
步骤总结:
- 拿到以后 BeanFactory 工厂对象。该工厂对象通过第一个拦截器
BeanFactoryAwareMethodInterceptor
曾经实现了设值 - 确定 Bean 名称。默认是办法名,若通过 @Bean 指定了以指定的为准,若指定了多个值以第一个值为准,前面的值当作 Bean 的 alias 别名
-
判断以后办法 (以 parent() 办法为例)是否是个 Scope 域代理。也就是办法上是否标注有
@Scope
注解- 若是域代理类,那旧以它的形式来解决喽。beanName 的变动变动为
scopedTarget.parent
- 判断
scopedTarget.parent
这个 Bean 是否正在创立中 … 若是的,那就把以后 beanName 替换为scopedTarget.parent
,当前就关注这个名称的 Bean 了~ - 试想一下,如果不来这个判断的话,那最终可能的后果是:容器内一个名为 parent 的 Bean,一个名字为
scopedTarget.parent
的 Bean,那岂不又出问题了麽~
- 若是域代理类,那旧以它的形式来解决喽。beanName 的变动变动为
-
判断申请的 Bean 是否是个 FactoryBean 工厂 Bean。
- 若是工厂 Bean,那么就须要 enhance 加强这个 Bean,以拦挡它的 getObject()办法
- 拦挡
getObject()
的做法是:当执行getObject()
办法时转为 ->getBean()
办法 - 为什么须要这么做:是为了确保 FactoryBean 产生的实例是通过 getBean()容器去获取的,而非又本人创立一个进去了
- 这种 case 先打个❓,上面会联合代码示例加以阐明
-
判断这个 beanMethod 是否是 以后正在被调用的工厂办法。
- 若是正在创立的办法,那就好说了,间接
super(xxx)
执行父类办法体完事~ - 若不是正在创立的办法,那就须要代理喽,以确保理论调用的仍旧是理论调用
getBean
办法而保障是同一个 Bean - 这种 case 先打个❓,上面会联合代码示例加以阐明。因为这个 case 是最常见的主线 case,所以先把它搞定
- 若是正在创立的办法,那就好说了,间接
这是该拦截器的执行步骤,留下两个打❓上面我来一一解释(依照倒序)。
屡次调用 @Bean 办法为何不会产生新实例?
这是最为常见的 case。示例代码:
@Configuration
public class AppConfig {
@Bean
public Son son() {Son son = new Son();
System.out.println("son created..." + son.hashCode());
return son;
}
@Bean
public Parent parent() {notBeanMethod();
Son son = son();
System.out.println("parent created... 持有的 Son 是:" + son.hashCode());
return new Parent(son);
}
public void notBeanMethod(){System.out.println("notBeanMethod invoked by【" + this + "】");
}
}
本配置类一共有 三个 办法:
- son():标注有 @Bean。
因而它最终交给 cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
办法间接执行父类(也就是指标类)的办法体:
值得注意的是:此时所处的对象仍旧是代理对象内 ,这个办法体只是通过代理类调用了super(xxx)
办法进来的而已嘛~
- parent():标注有 @Bean。它外部会还会调用 notBeanMethod()和 son()两个办法
同上,会走到指标类的办法体里,开始调用 notBeanMethod()和 son() 这两个办法,这个时候解决的形式就不一样了:
- 调用
notBeanMethod()
办法,因为它没有标注 @Bean 注解,所以不会被拦挡 -> 间接执行办法体 - 调用
son()
办法,因为它标注有 @Bean 注解,所以会持续进入到拦截器里。但请留神和下面 间接调用 son() 办法不一样的是:此时以后正在被 invoked 的办法是 parent()办法,而并非 son()办法 ,所以他会被交给resolveBeanReference()
办法来解决:
BeanMethodInterceptor:private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
ConfigurableBeanFactory beanFactory, String beanName) {
// 以后 bean(son 这个 Bean)是否正在创立中... 本处为 false 嘛
// 这个判断次要是为了避免前面 getBean 报错~~~
boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
try {
// 如果该 Bean 的确正在创立中,先把它标记下,搁置前面 getBean 报错~
if (alreadyInCreation) {beanFactory.setCurrentlyInCreation(beanName, false);
}
// 更具该办法的入参,决定前面应用 getBean(beanName)还是 getBean(beanName,args)
// 根本准则是:凡是只有有一个入参为 null,就调用 getBean(beanName)
boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
if (useArgs && beanFactory.isSingleton(beanName)) {for (Object arg : beanMethodArgs) {if (arg == null) {
useArgs = false;
break;
}
}
}
// 通过 getBean 从容器中拿到这个实例 本处拿出的就是 Son 实例喽
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) : beanFactory.getBean(beanName));
// 办法返回类型和 Bean 理论类型做个比拟,因为有可能类型不一样
// 什么时候会呈现类型不一样呢?当 BeanDefinition 定义信息类型被笼罩的时候,就可能呈现此景象
if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {if (beanInstance.equals(null)) {beanInstance = null;} else {
...
throw new IllegalStateException(msg);
}
}
// 以后被调用的办法,是 parent()办法
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
if (currentlyInvoked != null) {String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
// 这一步是注册依赖关系,通知容器:// parent 实例的初始化依赖于 son 实例
beanFactory.registerDependentBean(beanName, outerBeanName);
}
// 返回实例
return beanInstance;
}
// 偿还标记:笔记理论的确还在创立中嘛~~~~
finally {if (alreadyInCreation) {beanFactory.setCurrentlyInCreation(beanName, true);
}
}
}
这么一来,执行完 parent()办法体里的 son()办法后,理论失去的是容器内的实例,从而保障了咱们这么写是不会有问题的。
-
notBeanMethod():因为没有标注 @Bean,所以它并不会被容器调用,而只能是被下面的
parent()
办法调用到,并且也不会被拦挡(值得注意的是:因为此办法不须要被代理,所以此办法能够是private final
的哦~)
以上程序的运行后果是:
son created...347978868
notBeanMethod invoked by【com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$ec611337@12591ac8】parent created... 持有的 Son 是:347978868
com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$ec611337@12591ac8
容器内的 Son 实例:347978868
容器内 Person 持有的 Son 实例:347978868
true
能够看到,Son 从头至尾都只存在一个实例,这是合乎咱们的预期的。
Lite 模式下体现如何?
同样的代码,在 Lite 模式下(去掉 @Configuration 注解即可),不存在“如此简单”的代理逻辑,所以上例的运行后果是:
son created...624271064
notBeanMethod invoked by【com.yourbatman.fullliteconfig.config.AppConfig@21a947fe】son created...90205195
parent created... 持有的 Son 是:90205195
com.yourbatman.fullliteconfig.config.AppConfig@21a947fe
容器内的 Son 实例:624271064
容器内 Person 持有的 Son 实例:90205195
false
这个后果很好了解,这里我就不再啰嗦了。总之就不能这么用就对了~
FactoryBean 模式分析
FactoryBean
也是向容器提供 Bean 的一种形式,如最常见的 SqlSessionFactoryBean
就是这么一个大代表,因为它比拟罕用,并且这里也作为此拦截器一个 独自的执行分支,所以很有必要钻研一番。
执行此分支逻辑的条件是:容器内曾经存在 &beanName
和beanName
两个 Bean。执行的形式是:应用 enhanceFactoryBean()
办法对 FactoryBean
进行加强。
ConfigurationClassEnhancer:// 创立一个子类代理,拦挡对 getObject()的调用,委托给以后的 BeanFactory
// 而不是创立一个新的实例。这些代理仅在调用 FactoryBean 时创立
// factoryBean:从容器内拿进去的那个曾经存在的工厂 Bean 实例(是工厂 Bean 实例)// exposedType:@Bean 标注的办法的返回值类型
private Object enhanceFactoryBean(Object factoryBean, Class<?> exposedType,
ConfigurableBeanFactory beanFactory, String beanName) {
try {
// 看看 Spring 容器内曾经存在的这个工厂 Bean 的状况,看看是否有 final
Class<?> clazz = factoryBean.getClass();
boolean finalClass = Modifier.isFinal(clazz.getModifiers());
boolean finalMethod = Modifier.isFinal(clazz.getMethod("getObject").getModifiers());
// 类和办法其中有一个是 final,那就只能看看能不能走接口代理喽
if (finalClass || finalMethod) {
// @Bean 标注的办法返回值若是接口类型 尝试走基于接口的 JDK 动静代理
if (exposedType.isInterface()) {
// 基于 JDK 的动静代理
return createInterfaceProxyForFactoryBean(factoryBean, exposedType, beanFactory, beanName);
} else {
// 类或办法存在 final 状况,然而呢返回类型又不是
return factoryBean;
}
}
}
catch (NoSuchMethodException ex) {// 没有 getObject()办法 很显著,个别不会走到这里
}
// 到这,阐明以上条件不满足:存在 final 且还不是接口类型
// 类和办法都不是 final,生成一个 CGLIB 的动静代理
return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);
}
步骤总结:
- 拿到容器内曾经存在的这个工厂 Bean 的类型,看看类上、getObject()办法是否用 final 润饰了
-
凡是只需 有一个 被 final 润饰了,那注定不能应用 CGLIB 代理了喽,那么就尝试应用基于接口的 JDK 动静代理:
- 若你标注的 @Bean 返回的是接口类型(也就是
FactoryBean
类型),那就 ok,应用 JDK 创立个代理对象返回 - 若不是接口(有 final 又还不是接口),那老衲无能为力了:原样 return 返回
- 若你标注的 @Bean 返回的是接口类型(也就是
- 若以上条件不满足,示意一个 final 都木有,那就对立应用 CGLIB 去生成一个代理子类。大多数状况下,都会走到这个分支上,代理是通过 CGLIB 生成的
阐明:无论是 JDK 动静代理还是 CGLIB 的代理实现均非常简单,就是把 getObject()办法代理为应用
beanFactory.getBean(beanName)
去获取实例(要不代理掉的话,每次不就执行你 getObject()外面的逻辑了麽,就又会创立新实例啦~)
须要明确 ,此拦截器对 FactoryBean 逻辑解决分支的目标是:确保你 通过办法调用 拿到 FactoryBean
后,再调用其 getObject()
办法(哪怕调用屡次)失去的都是同一个示例(容器内的单例)。因而须要对 getObject()
办法做拦挡嘛,让该办法指向到getBean()
,永远从容器外面拿即可。
这个拦挡解决逻辑只有在 @Bean 办法调用时才有意义,比方 parent()里调用了 son()这样子才会起到作用,否则你就疏忽它吧~
针对于此,上面给出不同 case 下的代码示例,增强了解。
代码示例(重要)
筹备一个 SonFactoryBean
用于产生 Son 实例:
public class SonFactoryBean implements FactoryBean<Son> {
@Override
public Son getObject() throws Exception {return new Son();
}
@Override
public Class<?> getObjectType() {return Son.class;}
}
并且在配置类里把它放好:
@Configuration
public class AppConfig {
@Bean
public FactoryBean<Son> son() {SonFactoryBean sonFactoryBean = new SonFactoryBean();
System.out.println("我应用 @Bean 定义 sonFactoryBean:" + sonFactoryBean.hashCode());
System.out.println("我应用 @Bean 定义 sonFactoryBean identityHashCode:" + System.identityHashCode(sonFactoryBean));
return sonFactoryBean;
}
@Bean
public Parent parent(Son son) throws Exception {
// 依据后面所学,sonFactoryBean 必定是去容器拿
FactoryBean<Son> sonFactoryBean = son();
System.out.println("parent 流程应用的 sonFactoryBean:" + sonFactoryBean.hashCode());
System.out.println("parent 流程应用的 sonFactoryBean identityHashCode:" + System.identityHashCode(sonFactoryBean));
System.out.println("parent 流程应用的 sonFactoryBean:" + sonFactoryBean.getClass());
// 尽管 sonFactoryBean 是从容器拿的,然而 getObject()你可不能保障每次都返回单例哦~
Son sonFromFactory1 = sonFactoryBean.getObject();
Son sonFromFactory2 = sonFactoryBean.getObject();
System.out.println("parent 流程应用的 sonFromFactory1:" + sonFromFactory1.hashCode());
System.out.println("parent 流程应用的 sonFromFactory1:" + sonFromFactory2.hashCode());
System.out.println("parent 流程应用的 son 和容器内的 son 是否相等:" + (son == sonFromFactory1));
return new Parent(sonFromFactory1);
}
}
运行程序:
@Bean
public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
SonFactoryBean sonFactoryBean = context.getBean("&son", SonFactoryBean.class);
System.out.println("Spring 容器内的 SonFactoryBean:" + sonFactoryBean.hashCode());
System.out.println("Spring 容器内的 SonFactoryBean:" + System.identityHashCode(sonFactoryBean));
System.out.println("Spring 容器内的 SonFactoryBean:" + sonFactoryBean.getClass());
System.out.println("Spring 容器内的 Son:" + context.getBean("son").hashCode());
}
输入后果:
我应用 @Bean 定义 sonFactoryBean:313540687
我应用 @Bean 定义 sonFactoryBean identityHashCode:313540687
parent 流程应用的 sonFactoryBean:313540687
parent 流程应用的 sonFactoryBean identityHashCode:70807318
parent 流程应用的 sonFactoryBean:class com.yourbatman.fullliteconfig.config.SonFactoryBean$$EnhancerBySpringCGLIB$$1ccec41d
parent 流程应用的 sonFromFactory1:910091170
parent 流程应用的 sonFromFactory1:910091170
parent 流程应用的 son 和容器内的 son 是否相等:true
Spring 容器内的 SonFactoryBean:313540687
Spring 容器内的 SonFactoryBean:313540687
Spring 容器内的 SonFactoryBean:class com.yourbatman.fullliteconfig.config.SonFactoryBean
Spring 容器内的 Son:910091170
后果剖析:
达到了预期的成果:parent 在调用 son()办法时,失去的是在容器内曾经存在的 SonFactoryBean
根底上 CGLIB 字节码 晋升过的实例
, 拦挡胜利 ,从而 getObject() 也就理论是去容器里拿对象的。
通过本例有如下小细节须要指出:
- 原始对象和代理 / 加强后(不论是 CGLIB 还是 JDK 动静代理)的实例的
.hashCode()
以及.equals()
办法是一毛一样的,然而identityHashCode()
值(理论内存值)不一样哦,因为是不同类型、不同实例,这点请务必留神 - 最终存在于容器内的仍旧是原生工厂 Bean 对象,而非代理后的工厂 Bean 实例。毕竟拦截器只是拦挡了 @Bean 办法的调用来了个“偷天换日”而已~
- 若
SonFactoryBean
上加个 final 关键字润饰,依据下面讲述的逻辑,那代理对象会应用 JDK 动静代理生成喽,形如这样(本处仅作为示例,理论应用中请别这么干):
public final class SonFactoryBean implements FactoryBean<Son> {...}
再次运行程序,后果输入为:执行的后果一样,只是代理形式不一样而已。从这个小细节你也能看进去 Spring 对代理实现上的偏差:优先选择 CGLIB 代理形式,JDK 动静代理形式用于兜底。
...
// 应用了 JDK 的动静代理
parent 流程应用的 sonFactoryBean:class com.sun.proxy.$Proxy11
...
提醒:若你标注了 final 关键字了,那么请保障 @Bean 办法返回的是
FactoryBean
接口,而不能是SonFactoryBean
实现类,否则最终无奈代理了,原样输入。因为 JDK 动静代理和 CGLIB 都搞不定了嘛~
在以上例子的根底上,我给它“加点料”,再看看成果呢:
应用 BeanDefinitionRegistryPostProcessor
提前就放进去一个名为 son 的实例:
// 这两种形式向容器扔 bd or singleton bean 都行 我就抉择第二种喽
// 留神:此处放进去的是 BeanFactory 工厂,名称是 son 哦~~~ 不要写成了 &son
@Component
public class SonBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {// registry.registerBeanDefinition("son", BeanDefinitionBuilder.rootBeanDefinition(SonFactoryBean.class).getBeanDefinition());
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {SonFactoryBean sonFactoryBean = new SonFactoryBean();
System.out.println("初始化时,注册进容器的 sonFactoryBean:" + sonFactoryBean);
beanFactory.registerSingleton("son", sonFactoryBean);
}
}
再次运行程序,输入后果:
初始化时最早进容器的 sonFactoryBean:2027775614
初始化时最早进容器的 sonFactoryBean identityHashCode:2027775614
parent 流程应用的 sonFactoryBean:2027775614
parent 流程应用的 sonFactoryBean identityHashCode:1183888521
parent 流程应用的 sonFactoryBean:class com.yourbatman.fullliteconfig.config.SonFactoryBean$$EnhancerBySpringCGLIB$$1ccec41d
parent 流程应用的 sonFromFactory1:2041605291
parent 流程应用的 sonFromFactory1:2041605291
parent 流程应用的 son 和容器内的 son 是否相等:true
Spring 容器内的 SonFactoryBean:2027775614
Spring 容器内的 SonFactoryBean:2027775614
Spring 容器内的 SonFactoryBean:class com.yourbatman.fullliteconfig.config.SonFactoryBean
Spring 容器内的 Son:2041605291
成果上并不差别 ,从日志上能够看到:你配置类上应用 @Bean 标注的 son() 办法体 并没执行了,而是应用的最开始注册进去的实例,差别仅此而已。
为何是这样的景象?这就不属于本文的内容了,是 Spring 容器对 Bean 的实例化、初始化逻辑,本公众号前面依旧会采纳专栏式解说,让你彻底弄懂它。以后有趣味的能够先自行参考
DefaultListableBeanFactory#preInstantiateSingletons
的内容~
Lite 模式下体现如何?
Lite 模式下可没这些“增强个性”,所以在 Lite 模式下(拿掉 @Configuration
这个注解便可)运行以上程序,后果输入为:
我应用 @Bean 定义 sonFactoryBean:477289012
我应用 @Bean 定义 sonFactoryBean identityHashCode:477289012
我应用 @Bean 定义 sonFactoryBean:2008966511
我应用 @Bean 定义 sonFactoryBean identityHashCode:2008966511
parent 流程应用的 sonFactoryBean:2008966511
parent 流程应用的 sonFactoryBean identityHashCode:2008966511
parent 流程应用的 sonFactoryBean:class com.yourbatman.fullliteconfig.config.SonFactoryBean
parent 流程应用的 sonFromFactory1:433874882
parent 流程应用的 sonFromFactory1:572191680
parent 流程应用的 son 和容器内的 son 是否相等:false
Spring 容器内的 SonFactoryBean:477289012
Spring 容器内的 SonFactoryBean:477289012
Spring 容器内的 SonFactoryBean:class com.yourbatman.fullliteconfig.config.SonFactoryBean
Spring 容器内的 Son:211968962
后果解释我就不再啰嗦,有了后面的根底就太容易了解了。
为何是 @Scope 域代理就不必解决?
要解释好这个起因,和 @Scope
代理形式的原理常识强相干。限于篇幅,本文就先卖个关子~
对于 @Scope
我集体感觉足够用 5 篇以上文章专题解说,尽管在 Spring Framework
里应用得比拟少,然而在了解 Spirng Cloud
的自定义扩大实现上显得十分十分有必要,所以你可关注我公众号,会近期推出相干专栏的。
总结
对于 Spring 配置类这个专栏内容,解说到这就实现 99% 了,毫不客气的说对于此局部常识真正能够实现“横扫千军”,据我理解没有解决不了的问题了。
当然还剩下 1%,那天然是短少一篇总结篇喽:在下一篇总结篇里,我会用 图文并茂 的形式对 Spring 配置类相干内容的执行流程进行总结,目标是让你 疾速把握,应酬面试嘛。
本文将近 2 万字,手真的很累,如果对你有帮忙,帮点个在看哈。最次要的是:关注我的公众号,前期推出的专栏都会很精彩 ……
关注 A 哥
Author | A 哥(YourBatman) |
---|---|
集体站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
沉闷平台 |
|
公众号 | BAT 的乌托邦(ID:BAT-utopia) |
常识星球 | BAT 的乌托邦 |
每日文章举荐 | 每日文章举荐 |