版本约定面试
本文内容若没作特殊说明,均基于如下版本:安全
1.85.2.2.RELEASE
复制代码
上篇文章介绍了代理对象两个拦截器其中的前者,即 BeanFactoryAwareMethodInterceptor ,它会拦截 setBeanFactory() 方法从而完成给代理类指定属性赋值。经过第一个拦截器的讲解,你可以成功“忽悠”不少面试官了,但仍旧不可以解释咱们最常使用中的这个疑惑: 为什么经过调用@Bean方法最终指向的仍旧是同一个Bean呢?bash
带着这个疑问,开始本文的陈述。请系好安全带,准备发车了...app
根据不一样的配置方式,展现不一样状况。从Lite模式的使用 产生误区 ,到使用Full模式解决问题,最后引出解释为什么有此效果的缘由分析/源码解析。ide
配置类:post
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);
}
}
复制代码
运行程序:ui
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);
}
复制代码
运行结果:this
son created...624271064
son created...564742142
parent created...持有的Son是:564742142
com.yourbatman.fullliteconfig.config.AppConfig@1a38c59b
容器内的Son实例:624271064
容器内Person持有的Son实例:564742142
false
复制代码
结果分析:spa
这样的话, 就出问题了 。问题表如今这两个方面:3d
这种状况在生产上是 必定须要避免 ,那怎么破呢?下面给出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模式是容错性最强的一种方式,你乱造都行,没啥顾虑。
固然喽,方法不能是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 这个拦截器,也也就是所谓的两个拦截器的 后者 。
舒适提示:请务必确保已经了解过了上篇文章的流程分析哈,否则下面内容很容易形成你 脑力不适 的
相较于上个拦截器,这个拦截器不可为不复杂。官方解释它的做用为:拦截任何标注有 @Bean注解的方法的 调用 ,以确保正确处理Bean语义,例如 做用域 (请别忽略它)和AOP代理。
复杂归复杂,但没啥好怕的,一步一步来呗。一样的,我会按以下两步去了解它: 执行时机+ 作了何事 。
废话很少说,直接结合源码解释。
BeanMethodInterceptor:
@Override
public boolean isMatch(Method candidateMethod) {
return (candidateMethod.getDeclaringClass() != Object.class &&
!BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}
复制代码
三行代码,三个条件:
简而言之, **标注有@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);
}
复制代码
步骤总结:
这是该拦截器的执行步骤,留下两个答:question:下面我来一一解释(按照倒序)。
这是最为常见的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 + "】");
}
}
复制代码
本配置类一共有 三个 方法:
所以它最终交给 cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);方法直接执行父类(也就是目标类)的方法体:
值得注意的是: 此时所处的对象仍旧是代理对象内 ,这个方法体只是经过代理类调用了 super(xxx) 方法进来的而已嘛~
同上,会走到目标类的方法体里,开始调用 notBeanMethod()和son() 这两个方法,这个时候处理的方式就不同了:
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()方法后, 实际获得的是容器内的实例 ,从而保证了咱们这么写是不会有问题的。
以上程序的运行结果是:
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模式下(去掉@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 也是向容器提供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);
}
复制代码
步骤总结:
说明:不管是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()也就实际是去容器里拿对象的。
经过本例有以下小细节须要指出:
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模式下(拿掉 @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 我我的以为足够用5篇以上文章专题讲解,虽然在 Spring Framework 里使用得比较少,可是在理解 Spirng Cloud 的自定义扩展实现上显得很是很是有必要,因此你可关注我公众号,会近期推出相关专栏的。
关于Spring配置类这个专栏内容,讲解到这就完成99%了,绝不客气的说关于此部分知识真正能够实现“横扫千军”,据我了解没有解决不了的问题了。