做了何事
这里是具体拦截逻辑,会比第一个拦截器复杂很多。源码不算非常的多,但牵扯到的东西还真不少,比如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); }
步骤总结:
1.拿到当前BeanFactory工厂对象。该工厂对象通过第一个拦截器BeanFactoryAwareMethodInterceptor已经完成了设值
2.确定Bean名称。默认是方法名,若通过@Bean指定了以指定的为准,若指定了多个值以第一个值为准,后面的值当作Bean的alias别名
3.判断当前方法(以parent()方法为例)是否是个Scope域代理。也就是方法上是否标注有@Scope注解
- 若是域代理类,那旧以它的方式来处理喽。beanName的变化变化为scopedTarget.parent
- 判断scopedTarget.parent这个Bean是否正在创建中…若是的,那就把当前beanName替换为scopedTarget.parent,以后就关注这个名称的Bean了~
- 试想一下,如果不来这个判断的话,那最终可能的结果是:容器内一个名为parent的Bean,一个名字为scopedTarget.parent的Bean,那岂不又出问题了麽~
4.判断请求的Bean是否是个FactoryBean工厂Bean。
- 若是工厂Bean,那么就需要enhance增强这个Bean,以拦截它的getObject()方法
- 拦截getObject()的做法是:当执行getObject()方法时转为 -> getBean()方法
- 为什么需要这么做:是为了确保FactoryBean产生的实例是通过getBean()容器去获取的,而非又自己创建一个出来了
- 这种case先打个❓,下面会结合代码示例加以说明
5.判断这个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
这个结果很好理解,这里我就不再啰嗦了。总之就不能这么用就对了~