序
生活是一杯酒, 有时需要麻醉自己, 才能够暂时忘却痛苦与不快.
生活是一杯茶, 有时需要细细品味, 才发现苦涩背后也会有甘甜.
Spring 是一杯酒, 一眼望不到边的官方文档, 着实让人难以下咽.
Spring 是一杯茶, 在无边的源码中畅游之后, 发现色相味道俱全.
高考状元是六月份的网红, Spring 帝国是 Java 界的明星.
状元有自己的 "武功秘籍",Spring 有自己的 "帝国基石".
请随本文一起, 品 Spring, 寻找帝国的基石.
帝国的基石
无论是大到一个国家, 或是小到一个个人, 都有自己赖以存在的基石. 这个基石就是核心支柱, 就像经济基础支撑着上层建筑.
以 BAT 来说, 百度的搜索, 阿里的电商, 腾讯的社交. 可以说这是他们的立司之本, 如果想在这些方面和他们 PK, 几乎没有胜算的可能.
Spring 绝对是 Java 开发领域中一颗闪耀的明星, 它的巨大光芒甚至一直在引领着 Java 的发展方向.
现在说它已经发展为一个帝国, 应该不会有人站出来反对吧. 嗯, 站出来也没关系, 本人不接受反对. 哈哈.
那么有一个问题, 请大家思考下, Spring 帝国的基石是什么?
用过或了解 Spring 的人肯定都会说是 IoC 啦, AOP 啦, 声明式事务啦等等. 只能说这些回答浮于表面, 明显不走心啊.
好了, 我来公布答案吧, 这个帝国的基石, 其实就是 Bean. 肯定会有人问, 这个 bean 是什么东西啊, 那就去看它的定义吧. 对, 就是 Spring 中的 bean 定义.
在 Spring 中, bean 定义其实就是一个接口, 即 BeanDefinition. 我在上一篇 "毕业十年" 的文章中说过, 我们定义的类或接口其实都是对一种数据构成的描述, 所以可以直接把类或接口看作是一种数据结构.
那么 bean 定义接口, 就是一种数据结构, 它记录了一个 bean 的全部信息, 后期 Spring 对这个 bean 的所有操作都是建立在这些信息之上的.
如果对 Spring 不是很熟悉的朋友, 听到 "bean 的全部信息" 这句话会有点懵. 不要担心, 照例拿生活中我们熟悉的事物去做类比, 争取让所有人都能明白.
在医疗行业, 每个患者都会有一个病历, 上面记录了患者家族病史, 患者个人病史, 都做过哪些检查以及检查结果, 都做过哪些治疗以及恢复情况. 还有大夫每次对患者的病情诊断与分析.
这些信息肯定是记录的越全面越好, 后续的治疗方案都是依赖这些信息而制定的. Spring 中 bean 的信息就对等于这里患者的病历信息.
在公安系统, 每个嫌疑人也会有一个档案, 上面记录了他的口供, 作案信息或一些其它证据, 同样这些信息搜集的越全面越好, 后期法官的宣判与量刑也都依赖于它.
那么在这里, 记录案件信息的档案, 就可以对等于 Spring 中 bean 的信息.
相信通过这两个示例, 你已经完全明白了这个 bean 信息的作用和地位. 虽然到目前为止, 你可能还真不知道它里面到底存储的是什么信息. 但这不要紧, 只要记住它非常重要就可以了.
趁着这个机会, 再小小拓展一下:
这里的病历信息和档案信息里面记录的都是一些数据, 所以可以认为它们对应于程序中的数据结构.
医生的治疗方案和法官的宣判, 其实都是依赖这些数据做出的决定, 因此可以认为它们对应于程序中的算法.
可见, 数据结构决定着算法, 或者说, 算法是基于数据结构而设计的.
因此, 可以说数据结构的重要性要大于算法. 良好的数据结构能简化算法, 不好的数据结构只能使算法变得更复杂.
跟着变化走, 把它当朋友
在上篇文章中提到过, 唯一不变的就是变化, 所以随着时间的推移, 只需不断往这个数据结构中补充新的 bean 信息, Spring 再利用这些补充信息去定义新的操作, 以适应发展的需要.
就是这样, Spring 一步一步成长为一个浩浩荡荡的帝国. 就像我在上一遍文章中说的, 类或接口这样的数据结构一定要进行精心设计, 这样代码写起来会简单些, 而且后期改起来也会容易些.
一个非常明显的例子, 一开始都是基于 xml 配置文件的, 现在都是基于注解或 Java 配置的, 可以说 Spring 完成了一次华丽的转身, 而且非常完美丝滑, 没有一点拖泥带水.
其实就是在 bean 定义数据结构中加入了注解和 Java 配置相关的信息, Spring 利用这些信息去重新实现一遍, 并且和基于 xml 的实现并存, 因此既可以用 xml 也可以用注解.
就像我在上一篇文章中说的, 一定要合理抽象, 从宏观整体把握, 良好定义整体架构或结构, 至于一些具体的局部实现细节, 可以根据实际情况来定.
因为局部实现涉及范围一般较小, 后期换用新的方式来个重新实现也会相对容易一些. 从 xml 到注解基本就是这样子的.
其实说实话, 上一篇文章就是从这一篇分离出去的, 专门为本篇文章埋伏笔, 做铺垫用的. 哈哈.
滔滔不绝的说了这么多, 快来看看庐山真面目吧.
最讨厌的就是源码
有句话是怎么说的呢,"要不是为了生活, 谁愿意把自己弄得满身才华". 哈哈, 看源码时多想想这句话.
不想看的, 直接跳过吧.
BeanDefinition 接口, 及 bean 定义, 下面只列出了 get 方法, 其实还有 set 方法:
bean 定义可以继承
String getParentName();
bean 对应的类名称, 用来实例化 bean
String getBeanClassName();
生命周期范围
String getScope();
是否延迟实例化
boolean isLazyInit();
依赖的其它 bean
String[] getDependsOn();
是否作为自动装配候选 bean
boolean isAutowireCandidate();
是否是主要的, 用在可能有多个候选 bean 的情况
boolean isPrimary();
一个用来生成该 bean 的工厂 bean 名称
String getFactoryBeanName();
一个用来生产该 bean 的工厂方法名称
String getFactoryMethodName();
bean 的构造函数
ConstructorArgumentValues getConstructorArgumentValues();
一些 key/value, 可以在 bean 实例化后设置给 bean 的属性
MutablePropertyValues getPropertyValues();
初始化方法名称
String getInitMethodName();
销毁方法名称
String getDestroyMethodName();
角色, 应用层 / 基础设施层
int getRole();
人类可读的描述
String getDescription();
是否单例
boolean isSingleton();
是否原型
boolean isPrototype();
是否抽象
boolean isAbstract();
这两点比较关键, 需要知道:
可以有两种方法来指定一个 bean 的定义, 一个是类名称, 一个是工厂方法.
单例和原型这两种生命周期不是互斥关系, 因为存在既不是单例也不是原型的, 如 request,session 等范围.
AnnotatedBeanDefinition 接口, 扩展了 bean 定义接口, 增加了注解相关信息:
- AnnotationMetadata getMetadata();
- MethodMetadata getFactoryMethodMetadata();
ClassMetadata 接口, 是通过类注册时, 和类相关的一些信息:
- String getClassName();
- boolean isInterface();
- boolean isAnnotation();
- boolean isAbstract();
- boolean isConcrete();
- boolean isFinal();
- boolean isIndependent();
- boolean hasEnclosingClass();
- String getEnclosingClassName();
- boolean hasSuperClass();
- String getSuperClassName();
- String[] getInterfaceNames();
- String[] getMemberClassNames();
MethodMetadata 接口, 是通过工厂方法注册时, 和方法相关的信息:
- String getMethodName();
- String getDeclaringClassName();
- String getReturnTypeName();
- boolean isAbstract();
- boolean isStatic();
- boolean isFinal();
- boolean isOverridable();
AnnotatedTypeMetadata 接口, 用于获取注解的属性信息:
- boolean isAnnotated(String annotationName);
- Map<String, Object> getAnnotationAttributes(String annotationName);
- Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
- MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
- MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
AnnotationMetadata 接口, 用于获取一个类上标有的注解信息:
- Set<String> getAnnotationTypes();
- Set<String> getMetaAnnotationTypes(String annotationName);
- boolean hasAnnotation(String annotationName);
- boolean hasMetaAnnotation(String metaAnnotationName);
- boolean hasAnnotatedMethods(String annotationName);
- Set<MethodMetadata> getAnnotatedMethods(String annotationName);
一个小示例
上面的东西太抽象了, 下面通过一个简单的示例, 来具体看下.
使用 @Component 注解注册一个 Boss 类的 bean 定义.
- @Component
- public class Boss {
- }
使用 @Configuration 类里的 @Bean 方法注册两个 Staff 的 bean 定义, 同时 Company 类的 bean 定义也会被注册.
- public class Staff {
- }
- @Configuration
- public class Company {
- @Bean
- public Staff littleMing() {
- return new Staff();
- }
- @Bean
- public Staff littleQiang() {
- return new Staff();
- }
- }
在注册 bean 定义时, 需要一个 bean 名称, 默认会自动生成, 就是首字母小写的类名或方法名.
因此, 以上四个 bean 定义的名称分别是:
- boss
- company
- littleMing
- littleQiang
既然已经到这里了, 就应该满足一下好奇心, 取出 bean 定义看看, 是什么样子.
下面是 Boss 类的 bean 定义:
- bossBD = Generic bean: class [org.cnt.ts.bean.Boss];
- scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false;
- factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;
- defined in file [G:\workspace\sts4-cnt\taste-spring\target\classes\org\cnt\ts\bean\Boss.class],
- -> class org.springframework.context.annotation.ScannedGenericBeanDefinition
可以看出类名称就是 Boss 类的全名, 因此它是通过类注册的, 所以工厂 bean 的名称和工厂方法的名称都是 null.
下面是 Company 类的 bean 定义:
- companyBD = Generic bean: class [org.cnt.ts.bean.Company$$EnhancerBySpringCGLIB$$d6437e9f];
- scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false;
- factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;
- defined in file [G:\workspace\sts4-cnt\taste-spring\target\classes\org\cnt\ts\bean\Company.class],
- -> class org.springframework.context.annotation.ScannedGenericBeanDefinition
可以看出类名称就是 Company 类的全名, 不过已经被 CGLIB 增强过了. 因此它是通过类注册的, 所以工厂 bean 的名称和工厂方法的名称都是 null.
下面是小明这个 Staff 类的 bean 定义:
- littleMingBD = Root bean: class [null];
- scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
- factoryBeanName=company; factoryMethodName=littleMing; initMethodName=null; destroyMethodName=(inferred);
- defined in class path resource [org/cnt/ts/bean/Company.class],
- -> class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition
可以看出类名是 null, 说明是通过工厂方法注册的, 即 company 工厂类的 littleMing 工厂方法.
下面是小强这个 Staff 类的 bean 定义:
- littleQiangBD = Root bean: class [null];
- scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
- factoryBeanName=company; factoryMethodName=littleQiang; initMethodName=null; destroyMethodName=(inferred);
- defined in class path resource [org/cnt/ts/bean/Company.class],
- -> class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinition
可以看出类名是 null, 说明是通过工厂方法注册的, 即 company 工厂类的 littleQiang 工厂方法.
小名和小强的注册方式完全一样, 而且都是 Staff 类, 我们应该有看看它们是否相同的好奇心.
- littleMingBD == littleQiangBD -> false
- littleMingBD equals littleQiangBD -> false
发现这两个 bean 定义既不是相同, 也不是相等.
现在都是基于注解的, 自然可以获取到类上标的注解的信息.
Boss 类上是 @Component 注解:
bossAnno = {value=}
Company 类上是 @Configuration 注解:
companyAnno = {value=}
littleMing()方法上是 @Bean 注解:
littleMingAnno = {name=[], value=[], initMethod=, autowireCandidate=true, autowire=NO, destroyMethod=(inferred)}
littleQiang()方法上是 @Bean 注解:
littleQiangAnno = {name=[], value=[], initMethod=, autowireCandidate=true, autowire=NO, destroyMethod=(inferred)}
因为我们没有设置注解的属性, 所以上面四个注解都是默认值.
本文主要讲的是 bean 定义, 切记, bean 定义和 bean 实例 (或叫 bean 对象) 可不是一码事, 别搞混了.
其实我们日常的业务开发和知不知道 bean 定义是啥东西关系真不大, 就像我们平时吃喝拉撒一样, 只要会张开嘴吃喝就行了, 至于食物在体内如何消化吸收, 产生废物完全不用知道.
但是要想活的健康, 要想养生, 必须要知道这些, 同理, 要想做一个有追求, 有梦想的程序员, 也需要知道 bean 定义.
如果一个人没有梦想, 那跟咸鱼有什么区别.
示例代码:
- https://github.com/coding-new-talking/taste-spring.git
- (END)
作者是工作超过 10 年的码农, 现在任架构师. 喜欢研究技术, 崇尚简单快乐. 追求以通俗易懂的语言解说技术, 希望所有的读者都能看懂并记住. 下面是公众号和知识星球的二维码, 欢迎关注!
来源: https://www.cnblogs.com/lixinjie/p/taste-spring-001.html