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 到底是什么,得到的回答有两类:

  1. Spring Ioc 容器管理的 Java 对象
  2. BeanDefinition 类的实例对象

特喵的……这不是递归解释吗……Bean 是 Bean 容器管理的对象,是 Bean 定义的实例,这还用你说……


补充几个冷知识:

  • Java 指的是爪哇岛,名称来源是作者曾在爪哇岛上喝过一种美味的咖啡。

  • Java 的 Logo 就是一杯咖啡,class 文件的前四个字节是魔数 0xCAFABABE。

  • Bean 的字面义就是咖啡豆,代表 Java 中的小部件。

  • Bean 可以装在 jar 里。

    bean-jar.png (324×353)

有关这个冷知识可以参考《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 容器实际上指的是两类接口:BeanFactoryApplicationContext 接口,后者是前者的子接口。

1
2
3
4
5
6
7
// BeanFactory 作为 IoC 容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext();
Object bean = beanFactory.getBean("person");

// ApplicationContext 作为 IoC 容器
ApplicationContext ctx = new ClassPathXmlApplicationContext();
Map<String, Person> beanMap = ctx.getBeansOfType(Person.class);

这块知识体系比较庞大,本篇文章只学习 Bean 的部分,不学习 Bean 容器。


声明 Bean

在 Spring 中主要有三类声明 Bean 的方式。

我们创建一个 Person 类,它的全限定名是 com.pz.bean.Person

1
2
3
4
5
public class Person {
private String name;
private Integer age;
// ... getter setter 方法
}

通过 XML 声明 Bean

resources 目录下创建一个 XML 文件,加入一个 <bean> 元素,具体可参考官方文档

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="person" class="com.pz.bean.Person">
<property name="name" value="pz"/>
<property name="age" value="24"/>
</bean>

</beans>

通过 @Configuration 配合 @Bean 声明 Bean

在配置类(被 @Configuration 注解的类)里,通过 @Bean 注解声明 Bean。

1
2
3
4
5
6
7
8
9
@Configuration
public class QuickstartConfiguration {

@Bean
public Person person() {
return new Person();
}

}

通过 @ComponentScan 配合 @Component 声明 Bean

在配置类上额外标注 @ComponentScan 开启注解扫描,就可以扫描到 @Component 注解声明的 Bean。

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@ComponentScan // 默认扫描本类所在包及子包下的所有 @Component 组件
public class QuickstartConfiguration {
}

@Component
public class Person {
private String name;
private Integer age;
// ... getter setter 方法
}

Web 三层架构用的注解:@Controller@Service@Repository,底层也是 @Component

通过 FactoryBean 声明 Bean

感觉不太常用,但还是列一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// —————————————————— 定义 FactoryBean ——————————————————
public class PersonFactoryBean implements FactoryBean<Toy> {

private Person person;

@Override
public Person getObject() throws Exception {
return null;
}

@Override
public Class<Person> getObjectType() {
return Person.class;
}

public void setPerson(Person person) {
this.person = person;
}
}

// —————————————————— 在配置类中配置 FactoryBean ——————————————————
@Bean
public PersonFactoryBean personFactory() {
PersonFactoryBean personFactory = new PersonFactoryBean();
personFactory.setPerson(new Person());
return personFactory;
}

奇技淫巧

除了上述几种方法外,还可以编程式声明 Bean。

编程式的意思是,我们找到 Spring 的 IoC 容器,手动写代码,把自己想声明的 Bean 塞到 IoC 容器里。

这块知识超纲了,平常几乎也不会用到,在此只贴其中一种代码:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception {
// 获取 IoC 容器
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
// 获取 Bean 定义
BeanDefinition personDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).getBeanDefinition();
// 手动注册 Bean
ctx.registerBeanDefinition("person", personDefinition);
// 刷新 IoC 容器
ctx.refresh();
}

获取 Bean

在 IoC 容器中装载 Bean 之后,我们可以从中获取 Bean(尽管几乎没人这么做过?)。

BeanFactory 接口只能获取单个 Bean,ApplicationContext 接口可以获取多个 Bean(具体原因不详述,要学习到 IoC 容器时才能理解)。

1
2
3
4
5
6
7
// BeanFactory 作为 IoC 容器
BeanFactory beanFactory = new ClassPathXmlApplicationContext();
Object bean = beanFactory.getBean("person");

// ApplicationContext 作为 IoC 容器
ApplicationContext ctx = new ClassPathXmlApplicationContext();
Map<String, Person> beanMap = ctx.getBeansOfType(Person.class);

上面只罗列了两种手动获取 Bean 的方法,其实方法重载挺多的,可以通过 Bean 的名称、类型等获取。


主动从 IoC 容器中获取 Bean,这个动作被称为依赖查找,也就是我需要 Bean,我主动去 Bean 容器里索要 Bean。

这种方式不多见,更常见的方式是:我需要 Bean,代码自动把 Bean 给我(依赖注入),这种方式主要体现在属性注入上。


Bean 的属性注入

属性可以分两种:一种属性是静态值,一种属性是另一个 Bean。比如下面这段代码中的 Cat 类,属性 name 就是静态值,属性 master 就是另一个 Bean。

1
2
3
4
5
6
7
8
9
10
@Component
public class Person {
}

@Component
public class Cat {
private String name;
private Person master;
// ... getter setter 方法
}

主要有四种属性注入的方式:

setter 方法注入属性

  1. XML 方式的 setter 注入

    1
    2
    3
    <bean id="cat" class="com.pz.bean.Cat">
    <property name="name" value="mimi"/>
    </bean>
  2. 注解方式的 setter 注入

    1
    2
    3
    4
    5
    6
    @Bean
    public Cat cat() {
    Cat cat = new Cat();
    cat.setName("mimi");
    return cat;
    }

构造器注入属性

首先要修改一下 Cat 类,增加一个带参数的构造方法:

1
2
3
4
5
6
7
8
9
10
@Component
public class Cat {
private String name;
private Person master;

public Cat(String name) {
this.name = name;
}
// ... getter setter 方法
}
  1. XML 方式的构造器注入

    1
    2
    3
    <bean id="cat" class="com.pz.bean.Cat">
    <constructor-arg index="0" value="mimi"/>
    </bean>
  2. 注解式构造器注入

    1
    2
    3
    4
    @Bean
    public Cat cat() {
    return new Cat("mimi");
    }

注解式属性注入

  1. @Value 注入静态字段

    1
    2
    3
    4
    5
    6
    7
    @Component
    public class Cat {
    @Value("mimi")
    private String name;
    private Person master;
    // ... getter setter 方法
    }
  2. @Value 注入外部配置

    在 resources 目录下新建一个 properties 文件,存放外部配置,并在配置类上增加 @PropertySource 注解。

    1
    cat.name=mimi
    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan
    @PropertySource("classpath:bean/cat.property")
    public class InjectValueConfiguration {
    }

    使用 ${} 表示引入外部配置:

    1
    2
    3
    4
    5
    6
    7
    @Component
    public class Cat {
    @Value("${cat.name}")
    private String name;
    private Person master;
    // ... getter setter 方法
    }
  3. @Value 注入 SpEL 表达式

    使用 #{} 表示 SpEL 表达式,具体语法自查:

    1
    2
    3
    4
    5
    6
    7
    @Component
    public class Cat {
    @Value("#{'mimi'}")
    private String name;
    private Person master;
    // ... getter setter 方法
    }

自动注入 Bean 属性

如果一个 Bean 依赖另一个 Bean,就要通过自动注入实现。

  1. @Autowired

    在属性上标注 @Autowired 注解,IoC 容器会按照属性的类型,找到对应类型的 Bean 注入给属性:

    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class Cat {
    private String name;

    @Autowired
    private Person master;
    // ... getter setter 方法
    }

    @Autowired 的使用方式和细节挺多的,详情可参考《IOC基础-依赖注入-自动注入&复杂类型注入》(要收费hh)。

  2. @Resource

    在属性上标注 @Resource 注解,IoC 容器会按照属性的名称,找到对应名称的 Bean 注入给属性:

    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class Cat {
    private String name;

    @Resource(name = "person")
    private Person master;
    // ... getter setter 方法
    }

    @Resource 注解来自 JSR250 规范。

  3. @Inject

    它跟 @Autowired 是一样的策略,也是按照类型注入,但它来自 JSR330 规范。

    1
    2
    3
    4
    5
    6
    7
    8
    @Component
    public class Cat {
    private String name;

    @Inject
    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// 为了视觉效果,去掉了很多 @Nullable 注解
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {

// 作用域(scope)默认只提供 singleton 和 prototype 两种
// 还有更多类型的作用域,但它们都是扩展
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

// Bean 的角色:用户自己定义的 Bean、复杂配置的支撑部分、Spring 内部的 Bean
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;

// 父 Bean(作用是继承父 Bean 的配置信息)
void setParentName(String parentName);
String getParentName();

// Bean 的类名称,将来通过反射来生成实例
void setBeanClassName(String beanClassName);
String getBeanClassName();

// bean 的作用域
void setScope(String scope);
String getScope();

// 是否懒加载
void setLazyInit(boolean lazyInit);
boolean isLazyInit();

// 该 Bean 依赖的所有的 Bean(depends-on="" 属性设置的值)
void setDependsOn(String... dependsOn);
String[] getDependsOn();

// 该 Bean 是否可以注入到其他 Bean 中
// 只对根据类型注入有效,如果根据名称注入,即使这边设置了 false,也是可以的
void setAutowireCandidate(boolean autowireCandidate);
boolean isAutowireCandidate();

// 是否默认 Bean
// 同一接口的多个实现,如果不指定名字的话,Spring 会优先选择设置 primary 为 true 的 Bean
void setPrimary(boolean primary);
boolean isPrimary();

// 如果该 Bean 采用工厂方法生成,指定工厂名称
void setFactoryBeanName(String factoryBeanName);
String getFactoryBeanName();

// 指定工厂类中的工厂方法名称
void setFactoryMethodName(String factoryMethodName);
String getFactoryMethodName();

// 构造器参数
ConstructorArgumentValues getConstructorArgumentValues();
default boolean hasConstructorArgumentValues() {
return !getConstructorArgumentValues().isEmpty();
}

// Bean 中的属性值
MutablePropertyValues getPropertyValues();
default boolean hasPropertyValues() {
return !getPropertyValues().isEmpty();
}

// 初始化方法
void setInitMethodName(String initMethodName);
String getInitMethodName();

// 销毁方法
void setDestroyMethodName(String destroyMethodName);
String getDestroyMethodName();

// Bean 的角色
void setRole(int role);
int getRole();

// Bean 的描述
void setDescription(String description);
String getDescription();

// BeanDefinition 的可解析类型?
ResolvableType getResolvableType();

// 是否是 singleton/prototype
boolean isSingleton();
boolean isPrototype();

// 是否是抽象的
// 如果是抽象的,那么不能实例化,常用于作为父 bean 用于继承
boolean isAbstract();

// 定义 Bean 的资源描述
String getResourceDescription();

// 如果当前 BeanDefinition 是一个代理对象,那么该方法可以用来返回原始的 BeanDefinition
BeanDefinition getOriginatingBeanDefinition();
}

BeanDefinition 有很多实现类,我粗略画了张图整理了一下相关体系:

BeanDefinition体系


Bean 的生命周期

有点复杂……画张图溜了……

Bean的生命周期