Spring 源码第四弹!深入理解 BeanDefinition

Stella981
• 阅读 437

松哥原创的 Spring Boot 视频教程已经杀青,感兴趣的小伙伴戳这里-->Spring Boot+Vue+微人事视频教程

聚沙成塔!不知不觉 Spring 源码已经更到第五篇啦~

看源码之前,要先会用功能,这是最基本的,因为在源码讲解中,默认大家已经熟知 Spring 基本用法了,如果还不熟悉 Spring 的基本用法,可以看一下松哥发布在 B 站上的免费入门视频:https://www.bilibili.com/video/BV1Wv41167TU。

上篇文章和小伙伴们介绍了 Spring 源码中的 EntityResolver,这个是用来解决 XML 文件校验问题的。

接下来本来应该接着第二弹的 XML 文件解析流程继续往下走了,考虑到接下来我们会涉及到一个重要的概念 BeanDefinition,而有的小伙伴对此可能还不熟悉,因此本文松哥就先来和大家捋一捋 BeanDefinition 是什么东西!

本文是 Spring 源码解读第五篇,阅读本系列前面文章有助于更好的理解本文:

  1. Spring 源码解读计划

  2. Spring 源码第一篇开整!配置文件是怎么加载的?

  3. Spring 源码第二弹!XML 文件解析流程

  4. Spring 源码第三弹!EntityResolver 是个什么鬼?

1.BeanDefinition

在 Spring 容器中,我们广泛使用的是一个一个的 Bean,BeanDefinition 从名字上就可以看出是关于 Bean 的定义。

事实上就是这样,我们在 XML 文件中配置的 Bean 的各种属性,这些属性不仅仅是和对象相关,Spring 容器还要解决 Bean 的生命周期、销毁、初始化等等各种操作,我们定义的关于 Bean 的生命周期、销毁、初始化等操作总得有一个对象来承载,那么这个对象就是 BeanDefinition。

XML 中定义的各种属性都会先加载到 BeanDefinition 上,然后通过 BeanDefinition 来生成一个 Bean,从这个角度来说,BeanDefinition 和 Bean 的关系有点类似于类和对象的关系。

要理解 BeanDefinition,我们从 BeanDefinition 的继承关系开始看起。

Spring 源码第四弹!深入理解 BeanDefinition

BeanDefinition 是一个接口,继承自 BeanMetadataElement 和 AttributeAccessor 接口。

  • BeanMetadataElement:该接口只有一个方法 getSource,该方法返回 Bean 的来源。

  • AttributeAccessor:该接口主要规范了问任意对象元数据的方法。

我们来看下 AttributeAccessor:

public interface AttributeAccessor { void setAttribute(String name, @Nullable Object value); @Nullable Object getAttribute(String name); @Nullable Object removeAttribute(String name); boolean hasAttribute(String name); String[] attributeNames();}

这里定义了元数据的访问接口,具体的实现则是 AttributeAccessorSupport,这些数据采用 LinkedHashMap 进行存储。

这是 BeanDefinition 所继承的两个接口。接下来我们来看下 BeanDefinition 接口:

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; int ROLE_APPLICATION = 0; int ROLE_SUPPORT = 1; int ROLE_INFRASTRUCTURE = 2; void setParentName(@Nullable String parentName); @Nullable String getParentName(); void setBeanClassName(@Nullable String beanClassName); @Nullable String getBeanClassName(); void setScope(@Nullable String scope); @Nullable String getScope(); void setLazyInit(boolean lazyInit); boolean isLazyInit(); void setDependsOn(@Nullable String... dependsOn); @Nullable String[] getDependsOn(); void setAutowireCandidate(boolean autowireCandidate); boolean isAutowireCandidate(); void setPrimary(boolean primary); boolean isPrimary(); void setFactoryBeanName(@Nullable String factoryBeanName); @Nullable String getFactoryBeanName(); void setFactoryMethodName(@Nullable String factoryMethodName); @Nullable String getFactoryMethodName(); ConstructorArgumentValues getConstructorArgumentValues(); default boolean hasConstructorArgumentValues() {  return !getConstructorArgumentValues().isEmpty(); } MutablePropertyValues getPropertyValues(); default boolean hasPropertyValues() {  return !getPropertyValues().isEmpty(); } void setInitMethodName(@Nullable String initMethodName); @Nullable String getInitMethodName(); void setDestroyMethodName(@Nullable String destroyMethodName); @Nullable String getDestroyMethodName(); void setRole(int role); int getRole(); void setDescription(@Nullable String description); @Nullable String getDescription(); ResolvableType getResolvableType(); boolean isSingleton(); boolean isPrototype(); boolean isAbstract(); @Nullable String getResourceDescription(); @Nullable BeanDefinition getOriginatingBeanDefinition();}

BeanDefinition 中的方法虽然多,但是结合我们平时在 XML 中的配置,这些方法其实都很好理解:

  1. 首先一开始定义了两个变量用来描述 Bean 是不是单例的,后面的 setScope/getScope 方法可以用来修改/获取 scope 属性。

  2. ROLE_xxx 用来描述一个 Bean 的角色,ROLE_APPLICATION 表示这个 Bean 是用户自己定义的 Bean;ROLE_SUPPORT 表示这个 Bean 是某些复杂配置的支撑部分;ROLE_INFRASTRUCTURE 表示这是一个 Spring 内部的 Bean,通过 setRole/getRole 可以修改。

  3. setParentName/getParentName 用来配置 parent 的名称,这块可能有的小伙伴使用较少,这个对应着 XML 中的 <bean parent=""> 配置。

  4. setBeanClassName/getBeanClassName 这个就是配置 Bean 的 Class 全路径,对应 XML 中的 <bean class=""> 配置。

  5. setLazyInit/isLazyInit 配置/获取 Bean 是否懒加载,这个对应了 XML 中的 <bean lazy-init=""> 配置。

  6. setDependsOn/getDependsOn 配置/获取 Bean 的依赖对象,这个对应了 XML 中的 <bean depends-on=""> 配置。

  7. setAutowireCandidate/isAutowireCandidate 配置/获取 Bean 是否是自动装配,对应了 XML 中的 <bean autowire-candidate=""> 配置。

  8. setPrimary/isPrimary 配置/获取当前 Bean 是否为首选的 Bean,对应了 XML 中的 <bean primary=""> 配置。

  9. setFactoryBeanName/getFactoryBeanName 配置/获取 FactoryBean 的名字,对应了 XML 中的 <bean factory-bean=""> 配置,factory-bean 松哥在之前的入门视频中讲过,小伙伴们可以参考这里:https://www.bilibili.com/video/BV1Wv41167TU。

  10. setFactoryMethodName/getFactoryMethodName 和上一条成对出现的,对应了 XML 中的 <bean factory-method=""> 配置,不再赘述。

  11. getConstructorArgumentValues 返回该 Bean 构造方法的参数值。

  12. hasConstructorArgumentValues 判断上一条是否是空对象。

  13. getPropertyValues 这个是获取普通属性的集合。

  14. hasPropertyValues 判断上一条是否为空对象。

  15. setInitMethodName/setDestroyMethodName 配置 Bean 的初始化方法、销毁方法。

  16. setDescription/getDescription 配置/返回 Bean 的描述。

  17. isSingleton Bean 是否为单例。

  18. isPrototype Bean 是否为原型。

  19. isAbstract Bean 是否抽象。

  20. getResourceDescription 返回定义 Bean 的资源描述。

  21. getOriginatingBeanDefinition 如果当前 BeanDefinition 是一个代理对象,那么该方法可以用来返回原始的 BeanDefinition 。

这个就是 BeanDefinition 的定义以及它里边方法的含义。

2.BeanDefinition 实现类

上面只是 BeanDefinition 接口的定义,BeanDefinition 还拥有诸多实现类,我们也来大致了解下。

先来看一张继承关系图:

Spring 源码第四弹!深入理解 BeanDefinition

这么多实现类看着有点眼花缭乱,不过搞清楚了每一个接口和类的作用,再看就很容易了。

2.1 AbstractBeanDefinition

AbstractBeanDefinition 是一个抽象类,它根据 BeanDefinition 中定义的接口提供了相应的属性,并实现了 BeanDefinition 中定义的一部分方法。BeanDefinition 中原本只是定义了一系列的 get/set 方法,并没有提供对应的属性,在 AbstractBeanDefinition 中将所有的属性定义出来了。

后面其他的实现类也基本上都是在 AbstractBeanDefinition 的基础上完成的。

2.2 RootBeanDefinition

这是一个比较常用的实现类,对应了一般的元素标签。

2.3 ChildBeanDefinition

可以让子 BeanDefinition 定义拥有从父 BeanDefinition 那里继承配置的能力。

2.4 GenericBeanDefinition

GenericBeanDefinition 是从 Spring2.5 以后新加入的 BeanDefinition 实现类。GenericBeanDefinition 可以动态设置父 Bean,同时兼具 RootBeanDefinition 和 ChildBeanDefinition 的功能。

2.5 AnnotatedBeanDefinition

表示注解类型 BeanDefinition,拥有获取注解元数据和方法元数据的能力。

2.6 AnnotatedGenericBeanDefinition

使用了 @Configuration 注解标记配置类会解析为 AnnotatedGenericBeanDefinition。

3.实践

理论讲了这么多,接下来我们通过几行代码来实践下,验证一下我们前面所说的对不对。

首先项目中添加 spring-context 依赖,如下:

<dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context</artifactId>    <version>5.2.6.RELEASE</version></dependency>

然后我们来创建一个 User 类,如下:

public class User {    private String username;    private String address;    @Override    public String toString() {        return "User{" +                "username='" + username + '\'' +                ", address='" + address + '\'' +                '}';    }    public String getUsername() {        return username;    }    public void setUsername(String username) {        this.username = username;    }    public String getAddress() {        return address;    }    public void setAddress(String address) {        this.address = address;    }}

接下来我们先来验证 RootBeanDefinition。我们自己纯手工定义一个 RootBeanDefinition,并且将之注册到 Spring 容器中去。

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("username", "javaboy");pvs.add("address", "www.javaboy.org");RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class, null, pvs);ctx.registerBeanDefinition("user",rootBeanDefinition);ctx.refresh();User bean = ctx.getBean(User.class);System.out.println(bean);

MutablePropertyValues 是定义对象中的一个一个属性,构造 RootBeanDefinition 的时候,我们传入了类名称和属性集合,最终把 rootBeanDefinition 注册到容器中去。剩下的事情由容器完成,然后我们就可以从容器中获取到 User 对象了。

最终输出结果如下:

User{username='javaboy', address='www.javaboy.org'}

看了这个例子,小伙伴们应该能够大致明白,我们在 XML 中定义的各种属性,就是先被解析到 BeanDefinition 中,然后再注册到 Spring 容器中去,最后拿到我们需要的 Bean。

ChildBeanDefinition 具有从父 Bean 继承数据的能力,我们来看下这个怎么用。

首先新建一个 Person 类,Person 类在 User 类的基础上增加一个 nickname 属性,这样 Person 就可以继承到 User 的 username 和 address 两个属性的值了:

public class Person {    private String username;    private String address;    private String nickname;    @Override    public String toString() {        return "Person{" +                "username='" + username + '\'' +                ", address='" + address + '\'' +                ", nickname='" + nickname + '\'' +                '}';    }    public String getUsername() {        return username;    }    public void setUsername(String username) {        this.username = username;    }    public String getAddress() {        return address;    }    public void setAddress(String address) {        this.address = address;    }    public String getNickname() {        return nickname;    }    public void setNickname(String nickname) {        this.nickname = nickname;    }}

接下来自定义 ChildBeanDefinition:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("username", "javaboy");pvs.add("address", "www.javaboy.org");RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(User.class, null, pvs);ctx.registerBeanDefinition("user",rootBeanDefinition);ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition("user");childBeanDefinition.setBeanClass(Person.class);childBeanDefinition.getPropertyValues().add("nickname", "江南一点雨");ctx.registerBeanDefinition("person", childBeanDefinition);ctx.refresh();User user = ctx.getBean(User.class);Person person = ctx.getBean(Person.class);System.out.println("user = " + user);System.out.println("person = " + person);

首先定义 RootBeanDefinition 并注册到 Spring 容器中,然后再定义 ChildBeanDefinition,ChildBeanDefinition 继承了 RootBeanDefinition 中现有的属性值。

最后我们从 Spring 容器中获取 User 和 Person,打印结果如下:

user = User{username='javaboy', address='www.javaboy.org'}person = Person{username='javaboy', address='www.javaboy.org', nickname='江南一点雨'}

可以看到,Person 确实继承了 User 的属性值。

RootBeanDefinition 和 ChildBeanDefinition 都可以被 GenericBeanDefinition 代替,效果一样,如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("username", "javaboy");pvs.add("address", "www.javaboy.org");GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();rootBeanDefinition.setBeanClass(User.class);rootBeanDefinition.setPropertyValues(pvs);ctx.registerBeanDefinition("user",rootBeanDefinition);GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();childBeanDefinition.setParentName("user");childBeanDefinition.setBeanClass(Person.class);childBeanDefinition.getPropertyValues().add("nickname", "江南一点雨");ctx.registerBeanDefinition("person", childBeanDefinition);ctx.refresh();User user = ctx.getBean(User.class);Person person = ctx.getBean(Person.class);System.out.println("user = " + user);System.out.println("person = " + person);

运行结果如下:

user = User{username='javaboy', address='www.javaboy.org'}person = Person{username='javaboy', address='www.javaboy.org', nickname='江南一点雨'}

可以看到,和前面的运行效果一致。

在我们本系列前面文章(Spring 源码第一篇开整!配置文件是怎么加载的?)的案例中,默认使用的也是 GenericBeanDefinition,如下:

Spring 源码第四弹!深入理解 BeanDefinition

现在 Spring Boot 广泛流行之后,Java 配置使用越来越多,以 @Configuration 注解标记配置类会被解析为 AnnotatedGenericBeanDefinition;以 @Bean 注解标记的 Bean 会被解析为 ConfigurationClassBeanDefinition。

我们新建一个 MyConfig 配置类,如下:

@Configurationpublic class MyConfig {    @Bean    User user() {        return new User();    }}

查看获取到的 BeanDefinition 结果如下:

Spring 源码第四弹!深入理解 BeanDefinition

而其他 @Service、@Controller、@Repository 以及 @Component 等注解标记的 Bean 则会被识别为 ScannedGenericBeanDefinition。这个我就不一一演示了,小伙伴们可以自行测试哦。

4.小结

好啦,今天主要是和小伙伴们介绍一下 Spring 中的 BeanDefinition。通过上面的原理+案例,相信小伙伴们已经明白,我们通过 XML 或者 Java 注解配置的 Bean,我们定义的东西会先被解析成 BeanDefinition,然后再通过 BeanDefinition 来生成我们所需要的 Bean。

下篇文章我们就来看这个 BeanDefinition 到底是怎么从 XML 文件中生成的。

好啦,小伙伴们如果觉得有收获,记得点个在看鼓励下松哥哦~

今日干货

Spring 源码第四弹!深入理解 BeanDefinition

刚刚发表

查看: 13500 回复:135

公众号后台回复 SpringBoot,免费获取 274 页SpringBoot修炼手册。

本文分享自微信公众号 - 江南一点雨(a_javaboy)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Spring Boot 接入 GitHub 第三方登录,只要两行配置!
松哥原创的SpringBoot视频教程已经杀青,感兴趣的小伙伴戳这里SpringBootVue微人事视频教程(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzI1NDY0MTkzNQ%3D%3D%26mi
Stella981 Stella981
2年前
Spring Security 中如何细化权限粒度?
松哥原创的SpringBoot视频教程已经杀青,感兴趣的小伙伴戳这里SpringBootVue微人事视频教程(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzI1NDY0MTkzNQ%3D%3D%26mi
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这