第五章 在IOC容器中装配Bean

Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean, 装配好Bean之间的依赖关系,为上层应用提供准备就绪的环境。

Bean配置信息是Bean的元数据信息,它由以下四个方面组成:

  • Bean的实现类
  • Bean的属性信息,如数据源的连接数、用户名、POJO类的私有域等
  • Bean的依赖关系,其实就是某种Java类里的注入,引用了其他类对象
  • Bean的行为配置,如生命周期范围以及生命周期各个过程的回调函数(第四章的各种类型接口,利用Spring提供的扩展点来自定义Bean 的创建过程)

Bean的元数据信息在Spring容器内部是由一个个BeanDefinition形成的Bean注册表,Spring支持了多种形式的Bean配置方式 (主要讲基于XML的方式):

  • 基于XML的配置
  • 基于注解的配置
  • 基于Java类的配置
  • 基于Groovy动态语言的配置

Bean基本配置

下面的内容就是Bean的基本配置,几乎所有时候,我们都使用id来getBean,同一个容器中id是不允许重复的,但是name可以。

 <bean id="car" name="#car1" class="com.smart.simple.Car"></bean>
  <bean id="boss" class="com.smart.simple.Boss"></bean>

依赖注入

Spring支持两种注入方式:属性注入(最常用)和构造函数注入

属性注入要求我们的Bean类提供一个默认构造函数(不带参数),并为需要注入的属性提供对应的setter方法。Spring首先调用Bean的默认构造函数 来实例化Bean对象,实例化完成后各个各个属性的值都还为null或者相应的默认值;然后通过反射的方式调用setter方法注入属性值。

构造函数注入是另一种常用的注入方式,它保证一些必要属性在Bean的实例化的时候就得到设置,确保Bean在实例化后就可以使用。如下

<bean id="car1" class="com.smart.ditype.Car">
	<constructor-arg type="java.lang.String">
        <value>红旗CA72</value>
	</constructor-arg>
	<constructor-arg type="double">
        <value>20000</value>
	</constructor-arg>
</bean>

看起来我们只能通过构造函数的入参类型和索引信息间接确定构造函数的配置项和入参之间的关系,因为 Java反射机制并不会记住构造函数的入参名

注入参数

在Spring配置文件中,我们可以将String、int等字面值注入Bean中,还可以将集合、Map等类型的数据注入到Bean中,还可以通过 <ref bean = “xxx”>的方式注入配置文件中其它定义的Bean。

方法注入

lookup方法注入:一般在希望通过一个singleton Bean获取一个prototype Bean时使用。

方法替换:可以通过<replace-method>使用某个Bean的方法替换另一个Bean的方法。

Bean作用域

每一个Bean的作用域将对其生命周期和创建方式产生影响,下面是Spring支持的作用域类型,其中后面三种作用域只适用于WebApplicationContext环境

类型            说明 
 singleton       在Spring IOC容器中仅存在这个Bean的一个实例 | 
 prototype       每次从容器中调用getBean()方法时,都返回一个新实例 | 
 request         每次HTTP请求都会创建一个新的Bean | 
 session         同一个HTTP session共享一个Bean,不同的session使用不同的Bean | 
 globalSession   同一个全局session共享一个Bean,一般应用于Porlet应用环境 |

除了上面预定义的五种作用域以外,Spring也允许用户通过相应的接口自定义作用域,只需通过相应的接口实现即可,但是一般没有必要

singleton:一般情况下,无状态或者状态不可变的类适合用单例模式,大部分实例都适用于单例模式,这也是Spring容器中Bean的默认作用域,singleton的Bean在同一Spring IOC容器中只有一个实例。 在默认情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存于容器中,如果不希望在容器启动时提前实例化singleton的Bean,可以通过lazy-init控制。 lazy-init = “true"的Bean在某些情况下依然会提前实例化:如果该Bean被其他需要提前实例化的Bean所引用,那么Spring将忽略延迟实例化的设置。

prototype:在默认情况下,Spring容器在启动时不实例化prototype的Bean。此外,Spring容器将prototype的Bean交给调用者后,就不再管理它的生命周期。

另外三种作用域:在使用这些作用域之前,必须在web容器中进行一些额外的配置

  • 低版本的web容器中(Servlet2.3之前),用户可以使用HTTP请求过滤器进行配置
  • 高版本的web容器中,可以利用HTTP请求监听器(RequestContextListener)进行配置。在第四章介绍WebApplicationContext初始化时,已经通过 ContextLoaderListener将web容器和Spring容器进行了整合,现在又多了一个新的RequestContextListener。在整合Spring容器时,我们使用了 ContextLoaderListener,它实现了ServletContextListener监听器接口,ServletContextListener只负责监听web容器启动和关闭的事件。 而RequestContextListener实现了ServletRequestListener监听器接口,该监听器监听了HTTP请求,web服务器接收的每一次请求都会通知该监听器。

attention:Spring容器启动和关闭操作由web容器的启动和关闭操作事件触发(通过contextLoaderListener监听器),但是如果Spring容器中的Bean本身 需要request、session、globalSession作用域的支持,Spring容器本身就需要获得web容器的HTTP请求事件,也就是通过配置RequestContextListener ,Spring容器和web容器可以结合的更加紧密,既获取了web容器的启动关闭事件,又获取了web容器的HTTP请求事件。

Spring容器启动和关闭操作由web容器的启动和关闭事件触发,但如果Spring容器中的Bean需要request、session、globalSession作用域的支持,Spring容器就必须获得web容器的HTTP请求事件,以HTTP 请求事件“”““驱动”“Bean作用域的控制逻辑。

request:request作用域的Bean对应一个HTTP请求的生命周期,请求处理完毕后,这个Bean就会被销毁。

session:Bean的作用域横跨整个session,session中的所有HTTP请求都共享同一个Bean,当session结束时,实例才会被销毁。

globalSession:仅在Porlet的web应用中使用。

使用注解定义Bean

不管是XML还是注解,其实质都是表达Bean定义的载体,都是为Spring容器提供Bean定义的信息。需要注意一些注解的使用方式

基于Java类的配置

将一个POJO类增加@configuration注解,即标注为定义Bean的配置类,就可以为Spring容器提供Bean的定义信息,每个标注了@Bean的类方法就相当于提供了一个Bean的定义信息。需要注意的是: @Configuration注解类本身已经标注了@Component注解,所以任何标注了@Configuratiion注解的类,本身也相当于标注了@Component,即他们可以像普通的Bean一样被注入到其他Bean中。


第六章 Spring容器高级主题

Spring的AbstractApplicationContext是ApplicationContext的抽象实现类,该抽象类的refresh()方法定义了Spring容器在加载配置文件后的各项处理过程,这些过程刻画了Spring容器启动时 所执行的各项操作和内部逻辑,如下:

  • ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); //1、初始化BeanFactory
  • invokeBeanFactoryPostProcessors() //2、调用工厂后处理器
  • registerBeanPostProcessors() //3、注册Bean后处理器
  • initMessageSource() //4、初始化消息源
  • initApplicationEventMulticaster() //5、初始化应用上下文事件广播器
  • onRefresh() //6、初始化其他特殊类型的Bean,由具体子类实现
  • registerListeners() //7、注册事件监听器
  • finishBeanFactoryInitialization() //8、初始化所有单实例的Bean,使用懒加载模式的除外
  • finishRefresh() //9、完成刷新并发布容器刷新事件

(1) 初始化BeanFactory:根据配置文件实例化BeanFactory,在obtainFreshBeanFactory()方法中,首先调用refreshBeanFactory()方法刷新 BeanFactory,然后调用getBeanFactory()方法获取BeanFactory,这两个方法都是由具体子类来实现的,在这一步中,Spring将配置文件的信息装入容器的 Bean定义注册表(BeanDefinitionRegistry)中,但此时Bean还未初始化。

(2) 调用工厂后处理器:根据反射机制从BeanDefinitionRegistry中找到所有实现了BeanFactoryPostProcessor接口的Bean,并调用其 postProcessBeanFactory()接口方法。

(3) 注册Bean后处理器:根据反射机制从BeanDefinitionRegistry中找到所有实现了BeanPostProcessor接口的Bean,并将他们注册到容器Bean后处理器的注 册表中。

(4) 初始化消息源:初始化容器的国际化消息源。

(5) 初始化应用上下文事件广播器:用户可以在配置文件中自定义一个事件广播器,只要实现了ApplicationEventMulticaster接口即可,Spring会通过反射机制将其注册成容器的事件广播器,没有则使用默认的。

(6) 初始化其他特殊类型的Bean:这是一个钩子方法,子类可以通过这个方法,来执行一些特殊的操作。

(7) 注册事件监听器:Spring根据反射机制,从BeanDefinitionRegistry中找出所有实现了ApplicationListener接口的Bean,将它们注册为容器的事件监听器,实际操作就是将它们添加到事件广播器 所提供的事件监听器注册表中。

(8) 注册所有单实例的Bean:初始化Bean后,将他们放入Spring容器的缓存池中。

(9) 发布容器刷新事件:创建上下文刷新事件,事件广播器负责将这些事件广播到每个注册的事件监听器中(委托给了ApplicationEventMulticaster)。

相应的,我们来看Spring容器从加载配置文件到创建出一个完整的Bean的流程:

(1) ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示这个配置文件资源

(2) BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中的每一个<bean>都被解析成一个BeanDefinition对象,并 保存到BeanDefinitionRegistry中

(3) 容器扫描BeanDefinitionRegistry中的BeanDefinition,利用反射机制识别出Bean工厂后处理器(实现了BeanFactoryPostProcessor接口的Bean),然 后调用这些Bean工厂后处理器对BeanDefinitionRegistry中的BeanDefinition进行加工处理,主要包括以下两项工作:

  • 对使用占位符的<bean>元素标签进行解析,得到最终的配置值。这意味着对一些半成品的BeanDefinition对象进行加工处理,得到成品的BeanDefinition对象。
  • 对BeanDefinitionRegistry中的BeanDefinition进行扫描,利用反射机制找出所有属性编辑器的Bean(实现了PropertyEditor接口的Bean),并自动将它们 注册到Spring容器的**属性编辑器注册表(PropertyEditorRegistry)**中。

(4) Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用instantiationStrategy着手进行Bean实例化的工作。这个实 例化操作并不会参与Bean属性的设置工作,因此返回的Bean也是一个半成品的。

(5) 在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装。BeanWrapper提供了很多以反射机制操作Bean的方法,它将结合该Bean的 BeanDefinition以及容器中的属性编辑器,完成Bean的属性注入工作。

(6) 利用容器中注册的Bean后处理器(实现了BeanPostProcessor接口的Bean),对已经完成属性设置工作的Bean进行后续加工,直接装配出一个就绪的Bean。

一个BeanWrapperImpl实例内部封装了两类组件:被封装的待处理的Bean,以及一套用于设置Bean属性的属性编辑器。要顺利的填充Bean属性,除了目标Bean实例 和属性编辑器以外,还需要获取Bean对应的BeanDefinition,它可以从Spring容器的BeanDefinitionRegistry中直接获取。Spring主控程序从 BeanDefinition中获取Bean属性的具体信息PropertyValue,并使用属性编辑器对PropertyValue进行转换以得到具体的属性值。BeanWrapperImpl内部使用 Spring的BeanUtils工具类对Bean进行反射操作,设置属性。

属性编辑器的主要功能就是将外部设置的值转换为JVM内部的对应类型。

Spring容器事件其实本质上就是一个观察者模式的封装,事件源产生事件事件监听器监听这些事件以触发不同的响应操作,事件广播器将这些事件通知给事件监听器注册表中的 事件监听器,可以看一个案例:这个例子包括一个模拟的邮件发送器MailSender(事件源),它在发送邮件时,会产生一个MailSendEvent事件,容器中注册了监听该事件的事件监听器MailSendListener。 我们来看一下MailSendEvent的代码:

package com.smart.event;

import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;

public class MailSendEvent extends ApplicationContextEvent {
	private String to;
	
	public MailSendEvent(ApplicationContext source, String to) {
        super(source);
        this.to = to;
	}
	public String getTo() {
		
        return this.to;
	}
}

它扩展了ApplicationContextEvent,事件对象除了一个source以外,还有一个目的地属性to,事件监听器MailSendListener负责监听MailSendEvent事件,代码如下:

package com.smart.event;

import org.springframework.context.ApplicationListener;

public class MailSendListener implements ApplicationListener<MailSendEvent>{

	public void onApplicationEvent(MailSendEvent event) {
            MailSendEvent mse = (MailSendEvent) event;
            System.out.println("MailSendListener:向" + mse.getTo() + "发送完一封邮件");
	}
}

监听器直接实现了ApplicationListener接口,在方法中对MailSendEvent事件进行处理,MailSender要拥有发布事件的能力,必须实现ApplicationContextAware接口,代码如下:

package com.smart.event;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class MailSender implements ApplicationContextAware {

	private ApplicationContext ctx ;

    //ApplicationContextAware的接口方法,以便容器启动时注入容器实例
	public void setApplicationContext(ApplicationContext ctx)
            throws BeansException {
        this.ctx = ctx;

	}	
	public void sendMail(String to){
        System.out.println("MailSender:模拟发送邮件...");
        MailSendEvent mse = new MailSendEvent(this.ctx,to);
        //向容器中所有事件事件监听器发送事件
        ctx.publishEvent(mse);
	}
}

在sendMail方法中,首先模拟发送邮件,然后产生一个MailSendEvent事件,并通过容器句柄ctx向容器中的所有事件监听器发送事件。在Spring配置文件中,仅需进行如下配置:

     <bean class="com.smart.event.MailSendListener"/>
     <bean id="mailSender" class="com.smart.event.MailSender"/>

下面的代码便可以启动容器,并调用mailSender Bean来发送邮件:

package com.smart.event;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicatonEventTest {

	public static void main(String[] args) {
        String resourceFile = "com/smart/event/beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(resourceFile);	
        MailSender mailSender = ctx.getBean(MailSender.class);
        mailSender.sendMail("test mail.");
        System.out.println("done.");
	}
}