2.Spring bean
本章目标
- Spring bean常用属性(理解)
- setter注入(熟练)
- 构造器注入(掌握)
- Spring bean生命周期(理解)
本章内容:
一、Spring bean常用属性
Spring框架的核心功能有两个:
1、Spring容器作为超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为Bean。
2、Spring容器管理容器中Bean之间的依赖关系,Spring使用一种被称为”依赖注入”的方式来管理Bean之间的依赖关系。
1、<bean>
元素的常用属性
属性名称 | 描述 |
---|---|
id | 是一个 Bean 的唯一标识符,Spring 容器对 Bean 的配置和管理都通过该属性完成 |
name | Spring 容器同样可以通过此属性对容器中的 Bean 进行配置和管理,name 属性中可以为 Bean 指定多个名称,每个名称之间用逗号或分号隔开 |
class | 该属性指定了 Bean 的具体实现类,它必须是一个完整的类名,使用类的全限定名 |
scope | 用于设定 Bean 实例的作用域,其属性值有 singleton(单例)、prototype(原型)、request、session 和 global Session。其默认值是 singleton |
constructor-arg | <bean> 元素的子元素,可以使用此元素传入构造参数进行实例化。该元素的 index 属性指定构造参数的序号(从 0 开始),type 属性指定构造参数的类型 |
property | <bean> 元素的子元素,用于调用 Bean 实例中的 Set 方法完成属性赋值,从而完成依赖注入。该元素的 name 属性指定 Bean 实例中的相应属性名 |
ref | <property> 和 <constructor-arg> 等元素的子元索,该元素中的 bean 属性用于指定对 Bean 工厂中某个 Bean 实例的引用 |
value | <property> 和<constractor-arg> 等元素的子元素,用于直接指定一个常量值 |
list | 用于封装 List 或数组类型的依赖注入 |
set | 用于封装 Set 类型属性的依赖注入 |
map | 用于封装 Map 类型属性的依赖注入 |
entry | <map> 元素的子元素,用于设置一个键值对。其 key 属性指定字符串类型的键值,ref 或 value 子元素指定其值 |
2、Spring Bean的作用域范围
Spring通过scope属性用于设定 Bean 实例的作用域,其属性值有 :
Scope | Description |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . |
session | Scopes a single bean definition to the lifecycle of an HTTP Session . Only valid in the context of a web-aware Spring ApplicationContext . |
application | Scopes a single bean definition to the lifecycle of a ServletContext . Only valid in the context of a web-aware Spring ApplicationContext . |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket . Only valid in the context of a web-aware Spring ApplicationContext . |
其中singleton和prototype最常用的两种,其它我们不关注
2.1 singleton作用域
单例对象会放到一级缓存中,后面生命周期会提到
修改主启动类,再创建一IFruit对象,查看Apple的构造方法走几次
public class App {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
System.out.println("beanFactory工厂对象创建了");
IFruit fruit = ac.getBean("apple",IFruit.class);
IFruit fruit1 = ac.getBean("apple",IFruit.class);
fruit.eat();
fruit1.eat();
}
}
发现控制台只走了一次构造方法
3.2 prototype作用域
在上示例基础上
修改scope为prototype
<!---
bean的作用域
通过scope来指定作用域,scope常见的值有两种
singletion:单例的,默认
prototype:原型
-->
<bean id="apple" class="com.woniuxy.service.impl.Apple" scope="prototype"> </bean>
再次查看后台,发现构造方法走了两次
注意:如果配置了bean的lazy-init=“false”属性,则中有Scope配置为单例singletion时才有效
二、依赖注入
1、简介
Rod Johnson是第一个高度重视以配置文件来管理Java实例的协作关系的人,他给这种方式起了一个名字:控制反转(Inverse of Control,IoC)。后来Martine Fowler为这种方式起了另一个名称:依赖注入(Dependency Injection),因此不管是依赖注入,还是控制反转,其含义完全相同。当某个Java对象(调用者)需要调用另一个Java对象(被依赖对象)的方法时,在传统模式下通常有两种做法:
1、原始做法: 调用者主动创建被依赖对象,然后再调用被依赖对象的方法。
2、简单工厂模式: 调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。
注意上面的主动二字,这必然会导致调用者与被依赖对象实现类的硬编码耦合,非常不利于项目升级的维护。使用Spring框架之后,调用者无需主动获取被依赖对象,调用者只要被动接受Spring容器为调用者的成员变量赋值即可,由此可见,使用Spring后,调用者获取被依赖对象的方式由原来的主动获取,变成了被动接受——所以Rod Johnson称之为控制反转。
另外从Spring容器的角度来看,Spring容器负责将被依赖对象赋值给调用者的成员变量——相当于为调用者注入它依赖的实例,因此Martine Fowler称之为依赖注入。
使用依赖注入,不仅可以为Bean注入普通的属性值,还可以注入其他Bean的引用。依赖注入是一种优秀的解耦方式,其可以让Bean以配置文件组织在一起,而不是以硬编码的方式耦合在一起
DI 两个主要实现方式:
2、setter注入(重点)
即当前对象只需要为其依赖对象所对应的属性添加setter方法,IOC容器通过此setter方法将相应的依赖对象设置到被注入对象的方式即setter方法注入。
原理:创建对象后,利用反射调用对象的setter方法,为相关的属性注入值
2.1、创建Song类
接下来以Sing(唱),Song(歌)高度偶合的示例来说明
package com.wn.spring.type;
public class Song {
private String name;
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.2、创建Sing类
package com.wn.spring.type2;
/**
* 设置注入
*/
public class Sing {
private Song song;
public void setSong(Song song) {
this.song = song;
}
public void startSing(){
System.out.println("开始唱"+song.getName());
}
}
注意一定要有set方法
2.3、xml配置文件
简单值注入:
property中name与类中定义的属性名一致,值由value指定
什么是简单值:基本数据类型:byte,short,int,long,char,float,double,boolean和String通过的value属性或者是子标签来完成注入的
<bean id="song" class="com.wn.spring.type2.Song">
<property name="name" value="中国人"/>
<property name="number" value="2"/>
</bean>
问?可不可以把连接数据的相关配置放到一个类中,然后通过配置的方式,把值注入进去
引用类型注入:
property中name与类中定义的属性名一致,值由ref指定引用对象
<bean id="sing" class="com.wn.spring.type2.Sing">
<property name="song" ref="song"/>
</bean>
4、构造器注入:
即被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IOC容器)知道它需要哪些依赖对象,然后IOC容器会检查被注入对象的构造方法, 取得其所需要的依赖对象列表,进而为其注入相应对象
4.1、修改Sing
原理:在创建对象时,利用反射调用该带参的构造函数创建对象实例
/**
* 构造方法注入
*/
public class Sing {
private Song song;
public Sing(Song song) {
this.song = song;
}
public void startSing(){
System.out.println("开始唱"+song.getName());
}
}
4.2、配置文件修改
<bean id="song3" class="com.wn.spring.type3.Song">
<property name="name" value="中国人"/>
<property name="number" value="2"/>
</bean>
<bean id="sing3" class="com.wn.spring.type3.Sing">
<constructor-arg index="0" ref="song3"/>
</bean>
构造参数注入相比比较麻烦,平时在使用时还是setter注入使用的多
三、Spring Bean的生命周期
本节目录:重点理解Spring Bean的生命周期,关于扩展了解即可!
关于Spring Bean的生命周期,就是一个Bean在IOC容器中从创建到销毁的过程,下面就开始梳理一下一个Bean的创建过程:
1、Spring IOC大体流程
首先当容器启动时,会依据配置或注解扫描指定的包,将其中的类转化为BeanDefinition,并集中在DefaultListableBeanFactory类的beanDefinitionMap变量里。
注意这里只是存储bean的定义信息,还没有实例化bean对象;就像工厂里面一样,原材料已经准备好了,但是还没有进行生产,原材料就是beanDefinition,生产就是实例化
BeanDefinition是一个接口,在Spring中存在多种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition等均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件< bean>元素标签在容器中的内部表示形式。< bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和< bean>中的属性是一一对应的,其中RootBeanDefinition是最常用的实现类,它对应一般性的< bean>元素标签,GenericBeanDefinition是自2.5版本以后新加入到bean文件配置属性的定义类,是一站式服务类
2、生命周期概要流程
Bean的生命周期概括起来有四个阶段:
- 实例化(Instantiation)
- 属性设置(populate)
- 初始化(Initialization)
- 销毁(Destruction)
具体逻辑位于AbstractAutowireCapableBeanFactory类doCreateBean方法中,代码较多,只看重要的部分,如下:
// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 1. 实例化
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object exposedObject = bean;
try {
// 2. 属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 3. 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
// 4. 销毁-注册回调接口
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
return exposedObject;
}
上面是的步实例化、属性赋值、初始化都是Spring容器启动时的步骤,销毁是在容器关闭时的操作,容器销毁时会调用容器的close()方法去销毁容器。
2.1、创建实体类
package com.woniuxy.entity;
/**
* @author :fengSir
* @date :Created By 2022-05-26 16:58
* @description :TODO
*/
public class Person {
private String name;
private String address;
private String phone;
public Person() {
System.out.println("【构造器】调用Person的构造器实例化");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("【注入属性】注入属性name");
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
System.out.println("【注入属性】注入属性address");
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
System.out.println("【注入属性】注入属性phone");
this.phone = phone;
}
// 通过<bean>的init-method属性指定的初始化方法
public void myInit() {
System.out.println("【init-method】调用<bean>的init-method属性指定的初始化方法");
}
// 通过<bean>的destroy-method属性指定的初始化方法
public void myDestory() {
System.out.println("【destroy-method】调用<bean>的destroy-method属性指定的初始化方法");
}
}
这个包括了Bean本身调用的方法和通过配置文件中
的init-method和destroy-method指定的方法
2.2、配置xml文件
<bean id="person" class="com.woniuxy.entity.Person" init-method="myInit" destroy-method="myDestory" scope="singleton" >
<property name="name" value="tom"/>
<property name="address" value="西安"/>
<property name="phone" value="13800000000"/>
</bean>
2.3、主启动程序
System.out.println("现在开始初始化容器");
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
System.out.println("容器初始化成功");
//得到Preson,并使用
Person person = ac.getBean("person",Person.class);
System.out.println(person);
System.out.println("现在开始关闭容器!");
((ClassPathXmlApplicationContext)ac).registerShutdownHook();
关闭容器使用的是实际是AbstractApplicationContext的钩子方法。
2.4、结果
3、生命周期扩展(了解)
- 获取 BeanName:对传入的 name 进行解析,转化为可以从 Map 中获取到 BeanDefinition 的 bean name。
- 合并 Bean 定义:对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以获取完整的 Bean 定义信息。
- 实例化:使用构造或者工厂方法创建 Bean 实例。
- 属性填充:寻找并且注入依赖,依赖的 Bean 还会递归调用 getBean 方法获取。
- 初始化:调用自定义的初始化方法。
- 获取最终的 Bean:如果是 FactoryBean 需要调用 getObject 方法,如果需要类型转换调用 TypeConverter 进行转化。
- 存入单例池:构造好了的单例对象,会被放到loC的一个Map中.Map的key是beanName,value是bean实例.这个Map是一个单例池,也就是我们说的一级缓存
- 获得对象:我们就可以通过getBean(“user”),从单例池中获取是user的对象了
思维导图
本文来自博客园,作者:icui4cu,转载请注明原文链接:https://www.cnblogs.com/icui4cu/p/18839015