专栏首页趣谈编程Spring,你可能只了解冰山一角

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:workspacests4-cnt	aste-spring	argetclassesorgcnt	seanBoss.class], 
-> class org.springframework.context.annotation.ScannedGenericBeanDefinition

可以看出类名称就是Boss类的全名,因此它是通过类注册的,所以工厂bean的名称和工厂方法的名称都是null。 下面是Company类的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:workspacests4-cnt	aste-spring	argetclassesorgcnt	seanCompany.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)

本文分享自微信公众号 - 趣谈编程(qutanbiancheng),作者:编程新说李新杰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 人工智能会改变世界?那这项技能你必须要掌握了。(内含福利)

    假设我拥有多啦A梦的时光机,时光机带我穿越到了50年后,那未来的生活一定离不开人工智能。

    用户1260737
  • 【面试现场】如何在10亿数中找出前1000大的数

    小史:我可以用分治法,这有点类似快排中partition的操作。随机选一个数t,然后对整个数组进行partition,会得到两部分,前一部分的数都大于t,后一部...

    用户1260737
  • 深入浅出Spring IOC(二)

    上一篇文章 深入浅出Spring IOC(一)我们介绍了Spring的IOC,即控制反转,Spring来创建对象,程序中需要使用对象时,直接通过Spring容器...

    用户1260737
  • 品Spring:帝国的基石

    生活是一杯酒,有时需要麻醉自己,才能够暂时忘却痛苦与不快。 生活是一杯茶,有时需要细细品味,才发现苦涩背后也会有甘甜。 Spring是一杯酒,一眼望不到边的官...

    Java团长
  • 为您介绍人工智能的前世今生

    今天介绍一篇文章带你了解人工智能的前世今生,让你3分钟成为人工智能的达人。另外,2月28日周日21:50央视财经《对话》栏目将对话AI专家马尔科夫,开启人工智能...

    人工智能的秘密
  • 使用SmokePing监控你的服务器

    SmokePing是一款监控网络状态和稳定性的开源软件(它是rrdtool的作者开发的),通过它可以监控到公司IDC的网络状况,如延时,丢包率,是否BGP多线等...

    所有的酒都不如你
  • 中医+互联网=?腾讯这款AI医学影像产品走进深圳中医院啦!

    传统中医和现代互联网结合,将会擦出怎样的火花?20日,记者从深圳市中医院获悉,近日,人工智能医疗腾讯觅影在该院上线,该院也成为首批微信新农合支付试点医院,传统中...

    企鹅号小编
  • 微信小程序——使用setData修改数组中的单个对象

    微信小程序已经出来挺久的时间了,之前只是在文档上粗略的看了一下,最近稍得空闲,便利用微信小程序平台写一个练手的项目,顺便学习一下小程序开发,感觉大体跟前端开发基...

    Originalee
  • 《Hello NumPy》系列-切片的花式操作

    高阶部分篇篇都是干货,建议大家不要错过任何一节内容,最好关注我,或者关注公众号(同名),方便看到每次的文章推送。

    知秋小一
  • Apache ShardingSphere 社区的探索与拓展

    Apache ShardingSphere社区受邀参与了11月9日在清华大学举办的《Apache Event——走进Apache开源软件社区》的分享活动。在活动...

    开源社

扫码关注云+社区

领取腾讯云代金券