Spring Bean
五月的第三周,来学习 Spring Bean 相关的内容。
本篇文章的主要学习来源是《从 0 开始深入学习 Spring》。
Bean 是什么
Bean 是一个相当早的概念。
Java 在 1996 年发布时,同年 12 月发布了 Java Bean 1.00-A,这是最早的 Bean。
JavaBeans 是 Java 中一种特殊的类,统一规范提供 getter、setter 方法。
在实际企业开发中,JavaBeans 太过简单,sun 公司在它的基础上堆功能,堆出了 EJB(Enterprise JavaBean)。
EJB 功能强大,但未免太重了,后来又简化成 POJO(plain old Java object)。
这么多年发展下来,Bean 指代的是 Java 的可重用软件组件(reuse software components),主要有这么几个特点:
- 所有属性为 private
- 提供默认构造方法
- 提供 getter 和 setter 方法
- 实现 serializable 接口
有关 JavaBeans 的概念,可以参考《Java bean 是个什么概念?》、《维基百科:JavaBeans》、《廖雪峰:JavaBean》。
Spring 革 EJB 的命,但沿用了 Bean 这一叫法,作为可重用软件组件的称呼,(个人理解)是指程序中一堆重复使用的对象。
Spring 中的 Bean,跟 JavaBeans 是两个概念,它们的共同点是可重用组件,除此之外都不太一样。
吐个槽。我查阅了很多网络资料,Spring 的 Bean 到底是什么,得到的回答有两类:
- Spring Ioc 容器管理的 Java 对象
- BeanDefinition 类的实例对象
特喵的……这不是递归解释吗……Bean 是 Bean 容器管理的对象,是 Bean 定义的实例,这还用你说……
补充几个冷知识:
Java 指的是爪哇岛,名称来源是作者曾在爪哇岛上喝过一种美味的咖啡。
Java 的 Logo 就是一杯咖啡,class 文件的前四个字节是魔数 0xCAFABABE。
Bean 的字面义就是咖啡豆,代表 Java 中的小部件。
Bean 可以装在 jar 里。
有关这个冷知识可以参考《Why Java Beans are called “beans”?》。
前置知识
尽管大多数点进这篇文章的人,都知道控制反转、依赖注入、Spring 的 IoC 容器这些概念,但我还是补充一下 Spring Bean 的基础世界观。
在 Spring 的世界里,Bean 是被统一管理的,都装进一个叫IoC 容器
的罐子里。程序启动初始化的时候,Spring 就会收集所有的 Bean,全部装到IoC 容器
里面,之后程序运行起来,每次需要哪个 Bean,就去问IoC 容器
要。(这个设计叫做控制反转)
例如我们平常写的,被 @Controller
、@Service
注解过的类实例,都是 Spring 中的 Bean,都会被装进IoC 容器
里面。
IoC 容器
中的 IoC 全称是 Inversion of Control(控制反转),这个概念比较抽象,指的是获取对象的方式“反转”了(原始描述是“依赖对象的获得被反转了”,太抽象了),原来获取一个对象要 new 一个(或者工厂模式创建一个),现在可以去问IoC 容器
要一个,就无需关心对象的创建和初始化问题了,这样做最大的好处是解耦。
Spring 中的IoC 容器
实际上指的是两类接口:BeanFactory
和 ApplicationContext
接口,后者是前者的子接口。
1 | // BeanFactory 作为 IoC 容器 |
这块知识体系比较庞大,本篇文章只学习 Bean 的部分,不学习 Bean 容器。
声明 Bean
在 Spring 中主要有三类声明 Bean 的方式。
我们创建一个 Person
类,它的全限定名是 com.pz.bean.Person
:
1 | public class Person { |
通过 XML 声明 Bean
在 resources
目录下创建一个 XML 文件,加入一个 <bean>
元素,具体可参考官方文档。
1 |
|
通过 @Configuration 配合 @Bean 声明 Bean
在配置类(被 @Configuration
注解的类)里,通过 @Bean
注解声明 Bean。
1 |
|
通过 @ComponentScan 配合 @Component 声明 Bean
在配置类上额外标注 @ComponentScan
开启注解扫描,就可以扫描到 @Component
注解声明的 Bean。
1 |
|
Web 三层架构用的注解:@Controller
、@Service
、@Repository
,底层也是 @Component
。
通过 FactoryBean 声明 Bean
感觉不太常用,但还是列一下:
1 | // —————————————————— 定义 FactoryBean —————————————————— |
奇技淫巧
除了上述几种方法外,还可以编程式声明 Bean。
编程式的意思是,我们找到 Spring 的 IoC 容器,手动写代码,把自己想声明的 Bean 塞到 IoC 容器里。
这块知识超纲了,平常几乎也不会用到,在此只贴其中一种代码:
1 | public static void main(String[] args) throws Exception { |
获取 Bean
在 IoC 容器中装载 Bean 之后,我们可以从中获取 Bean(尽管几乎没人这么做过?)。
BeanFactory 接口只能获取单个 Bean,ApplicationContext 接口可以获取多个 Bean(具体原因不详述,要学习到 IoC 容器时才能理解)。
1 | // BeanFactory 作为 IoC 容器 |
上面只罗列了两种手动获取 Bean 的方法,其实方法重载挺多的,可以通过 Bean 的名称、类型等获取。
主动从 IoC 容器中获取 Bean,这个动作被称为依赖查找,也就是我需要 Bean,我主动去 Bean 容器里索要 Bean。
这种方式不多见,更常见的方式是:我需要 Bean,代码自动把 Bean 给我(依赖注入),这种方式主要体现在属性注入上。
Bean 的属性注入
属性可以分两种:一种属性是静态值,一种属性是另一个 Bean。比如下面这段代码中的 Cat 类,属性 name
就是静态值,属性 master
就是另一个 Bean。
1 |
|
主要有四种属性注入的方式:
setter 方法注入属性
XML 方式的 setter 注入
1
2
3<bean id="cat" class="com.pz.bean.Cat">
<property name="name" value="mimi"/>
</bean>注解方式的 setter 注入
1
2
3
4
5
6
public Cat cat() {
Cat cat = new Cat();
cat.setName("mimi");
return cat;
}
构造器注入属性
首先要修改一下 Cat 类,增加一个带参数的构造方法:
1 |
|
XML 方式的构造器注入
1
2
3<bean id="cat" class="com.pz.bean.Cat">
<constructor-arg index="0" value="mimi"/>
</bean>注解式构造器注入
1
2
3
4
public Cat cat() {
return new Cat("mimi");
}
注解式属性注入
@Value
注入静态字段1
2
3
4
5
6
7
public class Cat {
"mimi") (
private String name;
private Person master;
// ... getter setter 方法
}@Value
注入外部配置在 resources 目录下新建一个 properties 文件,存放外部配置,并在配置类上增加
@PropertySource
注解。1
cat.name=mimi
1
2
3
4
5
"classpath:bean/cat.property") (
public class InjectValueConfiguration {
}使用
${}
表示引入外部配置:1
2
3
4
5
6
7
public class Cat {
"${cat.name}") (
private String name;
private Person master;
// ... getter setter 方法
}@Value
注入 SpEL 表达式使用
#{}
表示 SpEL 表达式,具体语法自查:1
2
3
4
5
6
7
public class Cat {
"#{'mimi'}") (
private String name;
private Person master;
// ... getter setter 方法
}
自动注入 Bean 属性
如果一个 Bean 依赖另一个 Bean,就要通过自动注入实现。
@Autowired
在属性上标注
@Autowired
注解,IoC 容器会按照属性的类型,找到对应类型的 Bean 注入给属性:1
2
3
4
5
6
7
8
public class Cat {
private String name;
private Person master;
// ... getter setter 方法
}@Autowired
的使用方式和细节挺多的,详情可参考《IOC基础-依赖注入-自动注入&复杂类型注入》(要收费hh)。@Resource
在属性上标注
@Resource
注解,IoC 容器会按照属性的名称,找到对应名称的 Bean 注入给属性:1
2
3
4
5
6
7
8
public class Cat {
private String name;
"person") (name =
private Person master;
// ... getter setter 方法
}@Resource
注解来自 JSR250 规范。@Inject
它跟
@Autowired
是一样的策略,也是按照类型注入,但它来自 JSR330 规范。1
2
3
4
5
6
7
8
public class Cat {
private String name;
private Person master;
// ... getter setter 方法
}
接下来的内容都是进阶内容了,涉及到 Spring 的实现细节。
BeanDefinition
BeanDefinition 也就是 Bean 的定义,它是一种元信息。
元信息是指“定义的定义”,在这里 Bean 的定义是指所有的 Bean 有一些公共性的东西(比如 Bean 的名称、是否是单例、是否懒加载等等),把这些公共性的东西抽取出来,作为 BeanDefinition。
在这种角度上,Bean 可以简单认为是 BeanDefinition 的实例。
BeanDefinition 主要包括以下几部分:
- Bean 的类信息:全限定类名(用于反射生成 bean)
- Bean 的属性:作用域(scope)、是否默认 Bean(primary)、描述信息(description)
- Bean 的行为特征:是否延迟加载、是否自动注入、初始化/销毁方法
- Bean 与其他 Bean 的关系:父 Bean(parentName)、依赖的 Bean(dependsOn)
- Bean 的配置属性:构造器参数、属性变量值
下面贴一下 BeanDefinition 的接口代码(很大程度上参考自博文《Spring IOC 容器源码分析》):
1 | // 为了视觉效果,去掉了很多 @Nullable 注解 |
BeanDefinition 有很多实现类,我粗略画了张图整理了一下相关体系:
Bean 的生命周期
有点复杂……画张图溜了……