品Spring:帝国的基石
序
生活是一杯酒,有时需要麻醉自己,才能够暂时忘却痛苦与不快。
生活是一杯茶,有时需要细细品味,才发现苦涩背后也会有甘甜。
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年的码农,现在任架构师。喜欢研究技术,崇尚简单快乐。追求以通俗易懂的语言解说技术,希望所有的读者都能看懂并记住。下面是公众号和知识星球的二维码,欢迎关注!
品Spring:帝国的基石的更多相关文章
- 品Spring:能工巧匠们对注解的“加持”
问题的描述与方案的提出 在Spring从XML转向注解时,为了自身的开发方便,对注解含义进行了扩充(具体参考本号上一篇文章). 这个扩充直接导致了一个问题,就是需要从注解往元注解以及元元注解(即沿着从 ...
- 品Spring:SpringBoot和Spring到底有没有本质的不同?
现在的Spring相关开发都是基于SpringBoot的. 最后在打包时可以把所有依赖的jar包都打进去,构成一个独立的可执行的jar包.如下图13: 使用java -jar命令就可以运行这个独立的j ...
- 品Spring:负责bean定义注册的两个“排头兵”
别看Spring现在玩的这么花,其实它的“筹码”就两个,“容器”和“bean定义”. 只有先把bean定义注册到容器里,后续的一切可能才有可能成为可能. 所以在进阶的路上如果要想走的顺畅些,彻底搞清楚 ...
- 品Spring:SpringBoot轻松取胜bean定义注册的“第一阶段”
上一篇文章强调了bean定义注册占Spring应用的半壁江山.而且详细介绍了两个重量级的注册bean定义的类. 今天就以SpringBoot为例,来看看整个SpringBoot应用的bean定义是如何 ...
- 品Spring:SpringBoot发起bean定义注册的“二次攻坚战”
上一篇文章整体非常轻松,因为在容器启动前,只注册了一个bean定义,就是SpringBoot的主类. OK,今天接着从容器的启动入手,找出剩余所有的bean定义的注册过程. 具体细节肯定会颇为复杂,同 ...
- 品Spring:注解之王@Configuration和它的一众“小弟们”
其实对Spring的了解达到一定程度后,你就会发现,无论是使用Spring框架开发的应用,还是Spring框架本身的开发都是围绕着注解构建起来的. 空口无凭,那就说个最普通的例子吧. 在Spring中 ...
- 品Spring:bean工厂后处理器的调用规则
上一篇文章介绍了对@Configuration类的处理逻辑,这些逻辑都写在ConfigurationClassPostProcessor类中. 这个类不仅是一个“bean工厂后处理器”,还是一个“be ...
- 品Spring:详细解说bean后处理器
一个小小的里程碑 首先感谢能看到本文的朋友,感谢你的一路陪伴. 如果每篇都认真看的话,会发现本系列以bean定义作为切入点,先是详细解说了什么是bean定义,接着又强调了bean定义为什么如此重要. ...
- 品Spring:对@PostConstruct和@PreDestroy注解的处理方法
在bean的实例化过程中,也会用到一系列的相关注解. 如@PostConstruct和@PreDestroy用来标记初始化和销毁方法. 平常更多的是侧重于应用,很少会有人去了解它背后发生的事情. 今天 ...
随机推荐
- 随笔编号-15 重构--改善既有代码的设计--Day01--学习笔记
最近公司开发的系统在进行大批量数据查询的时候发现响应速度变得让人无法忍受,so 老大安排我进行代码重构的工作,主要目的就是为提高代码的执行效率.减小方法之间的响应时间.降低方法之间的耦合度.= =! ...
- egret之纹理填充模式(上下填充)
首先,我们准备两张图片,一张作为背景“瓶子”,一张作位填充物“饮料”. 在皮肤里我们设置右边图片的填充模式为“repeat”,修改Y的缩放为:-1.,调整图片位置使之与地图重合,如下: 现在,我们可以 ...
- Gym - 101252H
题意略. 思路:二分.注意当利率高且m比较小的时候,每个月的偿还可能会大于本金,所以我们二分的右边界应该要设为2 * 本金. 详见代码: #include<bits/stdc++.h> # ...
- C++string,char* 字符数组,int类型之间的转换
string.int 常见类型之间相互转换 int & string 之间的转换 C++中更多的是使用流对象来实现类型转换 针对流对象 sstream实现 int,float 类型都可以实现 ...
- codeforces 869 E. The Untended Antiquity(树状数组)
题目链接:http://codeforces.com/contest/869/problem/E 题解:这题是挺好想到solution的但是不太好写,由于题目的特殊要求每个矩形不会重贴所以只要这两个点 ...
- 树状数组求区间和模板 区间可修改 参考题目:牛客小白月赛 I 区间
从前有个东西叫树状数组,它可以轻易实现一些简单的序列操作,比如单点修改,区间求和;区间修改,单点求值等. 但是我们经常需要更高级的操作,比如区间修改区间查询.这时候树状数组就不起作用了,只能选择写一个 ...
- codeforces 761 D. Dasha and Very Difficult Problem(二分+贪心)
题目链接:http://codeforces.com/contest/761/problem/D 题意:给出一个长度为n的a序列和p序列,求任意一个b序列使得c[i]=b[i]-a[i],使得c序列的 ...
- 线段树(求单结点) hdu 1556 Color the ball
Color the ball Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)To ...
- Pytorch读取,加载图像数据(一)
在学习Pytorch的时候,先学会如何正确创建或者加载数据,至关重要. 有了数据,很多函数,操作的效果就变得很直观. 本文主要用其他库读取图像文件(学会这个,你就可以在之后的学习中,将一些效果直观化) ...
- CSS动效集锦,视觉魔法的碰撞与融合(二)
引言 长久以来,我认识到.CSS,是存在极限的.正如曾经替你扛下一切的那个男人,也总有他眼含热泪地拼上一切,却也无法帮你做到的事情,他只能困窘地让你看到他的无能为力,怅然若失. 然后和曾经他成长的时代 ...