当大潮退去,才知道谁在裸泳。
作者:A哥(YourBatman)
公众号:BAT的乌托邦(ID:BAT-utopia)
文末是否有彩蛋:有
前言
各位小伙伴大家好,我是A哥。本专栏/系列讲解到这里,关于Spring的@Configuration
配置类,应该是可以完成95%以上工作上的使用以及问题的解决。你也绝对算得上是一个“懂它”的Java Coder了,面试自然也就不在话下,基本可以实现“吊打面试官”。
建议刚“翻开”本专栏的同学去我公众号往前翻翻,前几篇文章能助你投入精力较少,收获大不一样
虽然你已经可以搞定95%的问题,但还剩5%呢?不要了麽?然而残酷的现实却是这样的,能解决那5%问题的才是真正的王者,他们的薪资往往能高出你一个甚至多个Level,并且在你眼中还好像还“不怎么干活”,不信你品,你细品……这就是不可替代性/稀缺性的价值……
如何提高自己的不可替代性?对于三无的我们,没有办法只能冲着那5%出发呗。对于钟爱于面向工资编程的我们,一般还是有更高追求的嘛,毕竟在趋同的程序员视界里,要的就是不一样,所以需要继续打怪升级。
接下来的两篇内容会比较深入,可能会让一些“初学者”感到不适(若感觉不适赶紧往前翻翻补课),希望坚持,毕竟这最终都会反应到你每个月的工资上,做难事必有所得嘛。
我粗浅的认为,对于大多数人来说,工资是衡量个人市场价值的唯一/最重要标准。工资20k和22k的两人可认为是差不多的,但40k的人肯定比前者价值高出一截
版本约定
本文内容若没做特殊说明,均基于以下版本:
- JDK:
1.8
- Spring Framework:
5.2.2.RELEASE
正文
如果说前面是些武功招式,那么接下来就到了内功修炼阶段了。走得多块得看你手脚是否能灵活会用,而走得多远是由你的体力(内功)决定的。下面我先以一个示例(可当面试题)开始本文的内容。
配置类在Full模式下的“能力”展示
配置类(标注有@Configuration注解,属于Full模式):
1 |
|
case1:
先来个简单点的。
1 | public static void main(String[] args) { |
结果输出:
1 | class com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$d38ead10 |
结果解释:
- Full模式的配置类被CGLIB增强了,所以最终放进容器内的实际是代理对象
- 代理类是由CGLIB生成的子类,所以父类必然就是目标类
- 这个为何是false???其实这个和
AopUtils.isCglibProxy()
的实现有关(建议你源码点进去瞄一眼一切都明白了),这个配置类仅仅是被CGLIB代理了,和AOP没毛关系
case2:
这个case会进阶一些。
1 | public static void main(String[] args) throws IllegalAccessException { |
结果输出:
1 | true |
结果解释:
- CGLIB字节码提升时,会自动给代理类新增一个名为
$$beanFactory
的字段/属性,在运行期间给其赋值。所以通过反射可以从代理实例里拿到这个属性值,并且值就是当前BeanFactory- 小细节:一定只能写成
(appConfig.getClass(), "$$beanFactory")
而不能是(AppConfig.class, "$$beanFactory")
哦,因为这个Field属于代理类而非目标类
- 小细节:一定只能写成
- 这个结果是false,和配置类本身并无关系,考察的知识点是Spring上下文Bean工厂和内建Bean工程的区别,这里先混个脸熟,下个专栏会详解的
- 结果为true。你是否想动粗:“劳资”的AppConfig配置类明明就没有实现
BeanFactoryAware
接口,为毛你给我返回true呢? - 解释同上
如果面试官通过这样的题目来考你(其实目的是想让你“降薪”),你是否招架得住,成为那5%呢?本文将带你一起继续深挖Spring @Configuration
配置里面的“玄机”,看完后你再回来看这几个题目就会感叹了:so easy。
何时创建代理?
我们已然知道Full模式的配置类最终会被CGLIB字节码提升,从而最终放一个代理类对象到Spring容器里。那么我们先来弄清楚创建代理的时机在哪儿~
Spring容器在refresh()
启动步骤的AbstractApplicationContext#invokeBeanFactoryPostProcessors
这一步会执行所有的BeanFactoryPostProcessor
处理器,而此时BeanFactory
才刚刚准备好,容器内除了ConfigurationClassPostProcessor
之外,并无任何其它BeanFactoryPostProcessor
,截图示例如下:
可能你会问:既然这么早期,那这个处理器是什么时候放进去的呢?我只能回答:在Bean容器“开山阶段”同几个开山鼻祖一起放进去的。如果你继续追问很多为什么的话,那我只能回答:这不是本专栏讲解的重点所在,放在下个专栏详解,请关注我公众号即可
既然这样,那么接下来就会会ConfigurationClassPostProcessor
这个后置处理器喽。
ConfigurationClassPostProcessor
用于引导处理@Configuration
配置类。该后置处理器的优先级是较高的,属于PriorityOrdered
分类。
说明:
PriorityOrdered
的优先级肯定比Order
接口的高
1 | // @since 3.0 |
它是一个BeanDefinitionRegistryPostProcessor
处理器,所以在容器启动过程中会先后执行如下两个方法:
postProcessBeanDefinitionRegistry()
从注册进来的配置类(可能是Full模式,可能是Lite模式)里进一步派生bean定义。简而言之:收集到所有的BeanDefinition
(后简称为bd)存储起来,包括@Import、@Component
等等组件。并且做出标注:是Full模式的还是Lite模式的配置类(若非配置组件就不标注哦)。
1 | ConfigurationClassPostProcessor: |
执行完此方法,已经完成了bd的收集和标记,那接下来就是本文的主菜了:帮你解答上面case的结果。
postProcessBeanFactory()
此方法的作用用一句话可概括为:为Full模式的Bean使用CGLIB做字节码提升,确保最终生成的是代理类实例放进容器内。
1 | ConfigurationClassPostProcessor: |
达到这一步之前,已经完成了bd的收集和标记(见上一步)。对bd进行实例化之前,针对于Full模式的配置类这步骤里会做增强处理,那就是enhanceConfigurationClasses(beanFactory)
这个方法。
enhanceConfigurationClasses(beanFactory)
对一个BeanFactory
进行增强,先查找配置类BeanDefinition
,再根据Bean定义信息(元数据信息)来决定配置类是否应该被ConfigurationClassEnhancer
增强。具体处理代码如下:
1 | ConfigurationClassPostProcessor: |
值得注意的是,虽然此方法被设计为public的,但是只被一处使用到。Spring这么做是为了给提供钩子,方便容器开发者做扩展时使用
步骤总结:
- 从BeanFactory拿出所有的bd信息,一个个判断
- 如果是配置类并且是Full模式,就先存储起来,后面会对它做字节码提升。最终如果一个Full模式的配置类都木有,那直接return,此方法结束。否则继续
- 对收集到的每一个 Full模式的配置类,使用
ConfigurationClassEnhancer
增强器进行字节码提升,生成一个CGLIB子类型- 小细节:此处显示标注了AOP自动代理为:始终代理目标类
- 把CGLIB生成的子类型设置到元数据里去:
beanDef.setBeanClass(enhancedClass)
。这样Spring在最后实例化Bean时,实际生成的是该代理类型的实例,从而达到代理/增强的目的
该方法执行完成后,执行“结果”我截了张图,供以参考:
这段代码是不难的,理解起来十分简单。但是,我们仍旧还只知道结果,并不清楚原因。凭它还无法解释上文中两个case的现象,所以我们应该端正态度继续深入,看看ConfigurationClassEnhancer
增强器到底做了什么事。
在介绍ConfigurationClassEnhancer
之前,希望你对CGLIB的使用有那么一定的了解,这样会轻松很多。当然不必过于深究(否则容易怀疑人生),但应该能知道如何使用Enhancer
增强器去增强/代理目标类,如何写拦截器等。
因为之前文章介绍过了CGLIB的基本使用,限于篇幅,此处就不再啰嗦。
ConfigurationClassEnhancer源码分析
得打起精神了,因为接下来才是本文之精华,让你出彩的地方。
@since 3.0。通过生成一个CGLIB子类来增强@Configuration
类与Spring容器进行交互,每个这样的@Bean
方法都会被生成的子类所复写。这样子当遇到方法调用时,才有可能通过拦截从而把方法调用引回容器,通过名称获得相应的Bean。
建立在对CGLIB的使用有一定了解的基础上,再来阅读本文会变得轻松许多。该类有且仅有一个 public方法,如下所示:
1 | ConfigurationClassEnhancer: |
巨简单有没有。
为何Spring的源码是开源软件的范本?因为它各种封装、设计模式用得都非常好,甚至对初学者都是友好的,所以说Spring易学难精。
该public方法的核心,在下面这两个个私有方法上。
newEnhancer()和createClass()
创建一个新的CGLIB Enhancer
实例,并且做好相应配置。
1 | ConfigurationClassEnhancer: |
Enhancer
是CGLIB的最核心API,通过方法名应该基本清楚了每一步都有什么作用吧。
Enhancer
属于CGLIB的核心API,但你发现它的包名是xxx.springframework.xxx
。这是因为CGLIB在Spring内太常用了(强依赖),因此Spring索性就自己fork了一份代码过来~
本方法我们需要关注对Enhancer
实例的配置,有如下关注点:
- 通过它增强的每个类都实现了
EnhancedConfiguration
接口,并且它还是BeanFactoryAware
的子接口- 统一实现接口,这和Spring AOP创建代理是不是如出一辙?想一想
- 实现了
BeanFactoryAware
接口,这样Spring在创建代理类实例的时候会给注入BeanFactory
- 使用
SpringNamingPolicy
策略来生成类名称。这就是解释了为何代理类的名你都能看到BySpringCGLIB字样 - 对于代理最为重要的当属过滤器/拦截器
org.springframework.cglib.proxy.Callback
,它们是实现功能的核心。配置此增强器时设置了CALLBACK_FILTER
共三个拦截器
关于CALLBACK_FILTER
,我们发现在类ConfigurationClassEnhancer
最开始处就申明了三个拦截器放进去了:
1 | ConfigurationClassEnhancer: |
如果说前面都是做准备工作,那么拦截器才是运行期真正干活的“人”了。它能够解答我们今天的疑问~
拦截器分析
什么是动态代理?用通俗的话理解就是:代理的核心逻辑就是依赖于拦截器实现的,可见拦截器(也叫增强)之于代理类是何等重要。
上面的三个拦截器中,NoOp.INSTANCE
代表什么都没做,因此我们只需要关注前两个。他俩均是MethodInterceptor
接口的实现类,均实现了intercept()
方法来做具体的拦截操作(他俩均是私有静态内部类哟)。
说明:本文的两个case用第一个拦截器即可解释,鉴于第二个拦截器非常的复杂,所以我把它放在下篇文章详解(已写得差不多了,因为太复杂,篇幅比本文还长)
BeanFactoryAwareMethodInterceptor
顾名思义,它表示的是BeanFactoryAware
方法的拦截器,所以靠猜应该能猜到它拦截的是setBeanFactory(beanFactory)
方法。
说明:Spring所有的拦截器实现的拦截都是方法级别的。虽然也支持构造器的拦截,但并没有内置实现,需要使用者自行扩展(比较复杂,一般并无使用场景)
相较于下文要讲的第二个拦截器,这个拦截器比较简单。但是它实现的功能可不简约哦,因为它能够解释文首提出的两个case,让你谈薪更有底气。
既然是拦截器,就应该按如下两步去了解它:执行时机 + 做了何事。
执行时机
执行时机决定了增强逻辑何时执行,毕竟一般来说都不可能是增强所有的嘛。
1 | BeanFactoryAwareMethodInterceptor: |
我们知道setBeanFactory()
方法是由Spring容器在初始化Bean时回调调用的,而代理类实现了EnhancedConfiguration
接口(间接实现了BeanFactoryAware
接口),所以该拦截器的执行时机为:在Spring初始化代理类实例时执行拦截。
说明:isSetBeanFactory()判断方法做这么“复杂”主要是为了容错,“担心”你自己定义了一个名为
setBeanFactory
的方法而“搞错了”。
做了何事
作为一个拦截器,增强逻辑才是它的核心。
1 | BeanFactoryAwareMethodInterceptor: |
从执行时机知道了,它拦截的是setBeanFactory()
方法的执行。所以这里的Method就代表的是setBeanFactory()
方法,Object[] args
的值是当前容器的BeanFactory工厂(注意理解这句话)实例。
此拦截器增强完成后,结果截图如下:
好了,介绍到这里本文就先告一段落。如果你是认真的看完了本文的分析,那么现在你再“回到顶部”理解那两个case的结果,你就豁然开朗了。
建议一定弄懂,我觉得已经很明朗了,所以就不再废话。若有不清楚,可以下方扫码加我微信私聊我吧(或者文末留言)
总结
又是一篇关于Spring配置类的长文,只希望对你有帮助才有意义。最为核心的两个增强/拦截器,迫于篇幅和读者的脑力(毕竟理解起来还是比较费劲的),今天只讲一个。我把另外一个更为重要、更为复杂、更为多金
的部分放在了下文专文阐述,你可关注我公众号保持“收看”。
下篇码字已经码得差不多了,80%吧。手累了,今天先休息,明天再搞,内容很精彩😄
关注YourBatman
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx1056342982 |
活跃平台 |
|
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |