2.Spring bean

本章目标

  1. Spring bean常用属性(理解)
  2. setter注入(熟练)
  3. 构造器注入(掌握)
  4. 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的生命周期概括起来有四个阶段:

  1. 实例化(Instantiation)
  2. 属性设置(populate)
  3. 初始化(Initialization)
  4. 销毁(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的对象了

思维导图

image

posted @ 2025-04-21 17:38  icui4cu  阅读(20)  评论(0)    收藏  举报