Bean的生命周期和作用域

Bean的”懒加载“


这样编写代码,bean便会在需要的时候注入对象而不是直接注入所有对象。

  • 需要对象时:
  • 不需要对象时:

单例模式

如果像下图一样获取同一个bean给两个不同命名的Student,其实还是只有一个对象, 他们的hashcode相同

如果在bean中增加scope属性为prototype,那么就不是单例模式了:

bean 的作用域

在 Spring 中,可以在元素的 scope 属性里设置 bean 的作用域,以决定这个 bean 是单实例的还是多实例的。

默认情况下,Spring 只为每个在 IOC 容器里声明的 bean 创建唯一一个实例,整个 IOC 容器范围内都能共享该实例:所有后续的 getBean()调用和 bean 引用都将返回这个唯一的 bean 实例。该作用域被称为 singleton,它是所有 bean 的默认作用域。

当 bean 的作用域为单例时,Spring 会在 IOC 容器对象创建时就创建 bean 的对象实例。而当 bean 的作用域为 prototype 时,IOC 容器在获取 bean 的实例时创建 bean 的实例对象。

bean 的生命周期

  • Spring IOC 容器可以管理 bean 的生命周期,Spring 允许在 bean 生命周期内特定的时间点执行指定的任务。
  • Spring IOC 容器对 bean 的生命周期进行管理的过程:
    1. 通过构造器或工厂方法创建 bean 实例
    2. 为 bean 的属性设置值和对其他 bean 的引用
    3. 调用 bean 的初始化方法
    4. bean 可以使用了
    5. 当容器关闭时,调用 bean 的销毁方法
  • 在配置 bean 时,通过 init-method 和 destroy-method 属性为 bean 指定初始化和销毁方法:

<bean name="user" class="com.bean.User" init-method="init" destroy-method="destroy"></bean>

  • bean 的后置处理器
    • bean 后置处理器允许在调用初始化方法前后对 bean 进行额外的处理
    • bean 后置处理器对 IOC 容器里的所有 bean 实例逐一处理,而非单一实例。其典型应用是:检查 bean 属性的正确性或根据特定的标准更改 bean 的属性。
    • bean 后置处理器时需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前后,Spring 将把每个 bean 实例分别传递给上述接口的以下两个方法:postProcessBeforeInitialization(Object, String)、postProcessAfterInitialization(Object, String)
  • 添加 bean 后置处理器后 bean 的生命周期
    ①通过构造器或工厂方法创建 bean 实例
    ②为 bean 的属性设置值和对其他 bean 的引用
    ③将 bean 实例传递给 bean 后置处理器的 postProcessBeforeInitialization()方法
    ④调用 bean 的初始化方法
    ⑤将 bean 实例传递给 bean 后置处理器的 postProcessAfterInitialization()方法
    ⑥bean 可以使用了
    ⑦当容器关闭时调用 bean 的销毁方法

引用外部属性文件

当 bean 的配置信息逐渐增多时,查找和修改一些 bean 的配置信息就变得愈加困难。这时可以将一部分信息提取到 bean 配置文件的外部,以 properties 格式的属性文件保存起来,同时在 bean 的配置文件中引用 properties 属性文件中的内容,从而实现一部分属性值在发生变化时仅修改 properties 属性文件即可。这种技术多用于连接数据库的基本信息的配置。

直接配置:

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="root"/>
    <property name="password" value="root"/>  
    <property name="jdbcUrl" value="jdbc:mysql:127.0.0.1:3306/test"/>
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>

使用外部的属性文件

创建 properties 属性文件

prop.userName=root
prop.password=root
prop.url=jdbc:mysql:127.0.0.1:3306/test
prop.driverClass=com.mysql.jdbc.Driver

从 properties 属性文件中引入属性值

<!-- 指定properties属性文件的位置 →<context:property-placeholder location="classpath:jdbc.properties"/><!-- 从properties属性文件中引入属性值 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${prop.userName}"/>
    <property name="password" value="${prop.password}"/>
    <property name="jdbcUrl" value="${prop.url}"/>
    <property name="driverClass" value="${prop.driverClass}"/>
</bean>

自动装配

概念

  • 手动装配:以 value 或 ref 的方式明确指定属性值都是手动装配。
  • 自动装配:根据指定的装配规则,不需要明确指定,Spring 自动将匹配的属性值注入 bean 中。

装配模式

  • 根据类型自动装配:将类型匹配的 bean 作为属性注入到另一个 bean 中。若 IOC 容器中有多个与目标 bean 类型一致的 bean,Spring 将无法判定哪个 bean 最合适该属性,所以不能执行自动装配
  • 根据名称自动装配:必须将目标 bean 的名称和属性名设置的完全相同
  • 通过构造器自动装配:当 bean 中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。

SpEL

简介

Spring Expression Language,Spring 表达式语言,简称 SpEL。支持运行时查询并可以操作对象图。和 JSP 页面上的 EL 表达式一样,SpEL 根据 JavaBean 风格的 getXxx()、setXxx()方法定义的属性访问对象图,完全符合我们熟悉的操作习惯。

基本语法

SpEL 使用#{…}作为定界符,所有在大框号中的字符都将被认为是 SpEL 表达式。

使用字面量

  • 整数:
  • 小数:
  • 科学计数法:
  • String 类型的字面量可以使用单引号或者双引号作为字符串的定界符号
    <property name="name" value="#{'Chuck'}"/>
    <property name="name" value='#{"Chuck"}'/>
  • Boolean:
  • 引用其他bean:
  • 引用其他 bean 的属性值:

通过注解配置 bean

不用注解的话就是这样的

概述

相对于 XML 方式而言,通过注解的方式配置 bean 更加简洁和优雅,而且和 MVC 组件化开发的理念十分契合,是开发中常用的使用方式。

使用注解标识组件

  • 普通组件:@Component 标识一个受 Spring IOC 容器管理的组件
  • 持久化层组件:@Repository 标识一个受 Spring IOC 容器管理的持久化层组件
  • 业务逻辑层组件:@Service 标识一个受 Spring IOC 容器管理的业务逻辑层组件
  • 表述层控制器组件:@Controller 标识一个受 Spring IOC 容器管理的表述层控制器组件
  • 组件命名规则:
    ①默认情况:使用组件的简单类名首字母小写后得到的字符串作为 bean 的 id(类名为HelloWorld,则在容器中的bean的id为helloWorld)
    ②使用组件注解的 value 属性指定 bean 的 id
    注意:事实上 Spring 并没有能力识别一个组件到底是不是它所标记的类型,即使将@Respository 注解用在一个表述层控制器组件上面也不会产生任何错误,所以@Respository、@Service、@Controller 这几个注解仅仅是为了让开发人员自己明确 当前的组件扮演的角色。

扫描组件

组件被上述注解标识后还需要通过 Spring 进行扫描才能够侦测到。

  • 指定被扫描的 package

<context:component-scan base-package="com.component"/>

  • base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包及其子包中的所有类。
  • 当需要扫描多个包时可以使用逗号分隔。
  • 如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern 属性过滤特定的类
  • JAR 包必须在原有 JAR 包组合的基础上再导入一个:spring-aop-4.0.0.RELEASE.jar


    但是后面的解析位置现在包含了解析器的功能,因此一般 不写引入解析器的那一行
    <context:annotation-config></context:annotation-config>

组件装配

  • 需求
    Controller 组件中往往需要用到 Service 组件的实例,Service 组件中往往需要用到Repository 组件的实例。Spring 可以通过注解的方式帮我们实现属性的装配。

  • 实现依据
    在指定要扫描的包时,context:component-scan 元素会自动注册一个 bean 的后置处理器:AutowiredAnnotationBeanPostProcessor 的实例。该后置处理器可以自动装配标记了@Autowired、@Resource 或@Inject 注解的属性。

  • @Autowired 注解(重点)
    ①根据类型实现自动装配。

    ②构造器、普通字段(即使是非 public)、一切具有参数的方法都可以应用@Autowired 注解

    ③默认情况下,所有使用@Autowired 注解的属性都需要被设置。当 Spring 找不到匹配的 bean 装配属性时,会抛出异常。

    ④若某一属性允许不被设置,可以设置@Autowired 注解的 required 属性为 false

    ⑤默认情况下,当 IOC 容器里存在多个类型兼容的 bean 时,Spring 会尝试匹配 bean的 id 值是否与变量名相同,如果相同则进行装配。如果 bean 的 id 值不相同,通过类型的自动装配将无法工作。此时可以在@Qualifier 注解里提供 bean的名称。Spring 甚至允许在方法的形参上标注@Qualifiter 注解以指定注入 bean 的名称。

    ⑥@Autowired 注解也可以应用在数组类型的属性上,此时 Spring 将会把所有匹配的 bean 进行自动装配。

    ⑦@Autowired 注解也可以应用在集合属性上,此时 Spring 读取该集合的类型信息,然后自动装配所有与之兼容的 bean。

    ⑧@Autowired 注解用在 java.util.Map 上时,若该 Map 的键值为 String,那么 Spring 将自动装配与值类型兼容的 bean 作为值,并以 bean 的 id 值作为键。

  • @Resource
    @Resource 注解要求提供一个 bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为 bean 的名称。

  • @Inject
    @Inject 和@Autowired 注解一样也是按类型注入匹配的 bean,但没有 reqired 属性。
    可以在@Resource之类的注解后加上括号,自定义注入对象的名称

    运行结果: