7.4 使用 Spring 容器

      Spring 有两个核心接口:BeanFactory 和 ApplicationContext,其中ApplicationContext 是 BeanFactory 的子接口。它们都可代表 Spring 容器,Spring 容器是生成 Bean 实例的工厂,并管理容器中的Bean。

      Java 程序面向接口编程,无须关心 Bean 实例的实现类;但 Spring 容器负责创建 Bean 实例,因此必须精确知道每个 Bean 实例的实现类,故Spring 配置文件必须指定 Bean 实例的实现类。

      7.4.1 Spring 容器

        Spring 容器最基本的接口就是BeanFactory。BeanFactory 负责配置、创建、管理Bean,它有一个子接口:ApplicationContext,因此也被称为Spring上下文。Spring 容器还负责管理Bean 与 Bean 之间的依赖关系。

        BeanFactory 接口包含如下几个基本方法。

          boolean containsBean(String name):判断Spring容器是否包含id为name 的Bean 实例。

          <T> T getBean(Class<T> requiredType):获取Spring容器中属于requiredType类型的、唯一的Bean实例。

          Object getBean(String name):返回容器id为name的Bean实例。

          <T> T getBean(String name,Class requiredType):返回容器中id为name,并且类型为requiredType的Bean。

          Class<?> getType(String name):返回容器中id为name的Bean实例的类型。

        BeanFactory 常用的实现类是DefaultListableBeanFactory。

        ApplicationContext 常用的实现类是FileSystemXmlApplicationContext、ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext。

        如果在Web应用中使用Spring容器,则通常有XmlWebApplicationContext、AnnotationConfigWebApplicationContext两个实现类。

        创建BeanFactory实例时,应该提供XML配置文件作为参数,XML配置文件通常使用Resource对象传入。

        Resource接口是Spring提供的资源访问接口,通过使用该接口,Spring能以简单、透明的方式访问磁盘、类路径以及网络上的资源。

        大部分Java EE应用,可在启动Web应用时自动加载ApplicationContext实例,接收Spring管理的Bean无须知道ApplicationContext的存在,一样可以利用ApplicationContext的管理。

        对于独立的应用程序,可通过如下方法实例化BeanFactory。



//        搜索类加载路径下的beans.xml 文件创建Resource对象
        Resource isr = new ClassPathResource("beans.xml");
//        创建默认的BeanFactory容器
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//        让默认的BeanFactory容器加载isr对应的XML配置文件
        new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(isr);



        或者采用如下代码来创建BeanFactory:



//        搜索文件系统的当前路径下的beans.xml 文件创建Resource对象
        Resource isr = new FileSystemResource("beans.xml");
//        创建默认的BeanFactory容器
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//        让默认的BeanFactory容器加载isr对应的XML配置文件。
        new XmlBeanDefinitionReader(beanFactory).loadBeanDefinitions(isr);



        如果应用需要加载多个配置文件来创建Spring容器,则应该采用BeanFactory的子接口ApplicationContext来创建BeanFactory的实例。

        ApplicationContext接口包含FileSystemXmlApplicationContext和ClassPathXmlApplicationContext两个常用的实现类。

        如果需要同时加载多个XML配置文件来创建Spring容器,则可以采用如下方式:



//        以类加载路径下的Beans.xml、service.xml文件创建ApplicationContext
        ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml","service.xml");



        当然也可支持从文件系统的相对路径或绝对路径来搜索配置文件,只要使用FileSystemXmlApplicationContext即可。



//        以类加载路径下的Beans.xml、service.xml文件创建ApplicationContext
        ApplicationContext ctx = new FileSystemXmlApplicationContext("beans.xml","service.xml");



       7.4.2 使用ApplicationContext

        大部分时候使用ApplicationContext实例作为容器,因此也把Spring容器称为Spring上下文。ApplicationContext是BeanFactory接口的子接口,它增强了BeanFactory的功能。

        ApplicationContext允许以声明式方式操作容器,无须手动创建它。可利用ContextLoader的支持类,在Web应用启动时自动创建ApplicationContext。当然也可采用编程方式创建ApplicationContext。

        除了提供BeanFactory所支持的全部功能外,ApplicationContext还有如下额外的功能。

          ApplicationContext默认会预初始化所有singleton Bean,也可通过配置取消预初始化。

          ApplicationContext继承MessageSource接口,因此提供国际化支持。

          资源访问,比如访问URL文件。

          事件机制。

          同时加载多个配置文件。

          以声明式方式启动并创建Spring容器。

        配置文件:



<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring 配置文件的根元素,使用Spring-beans-4.0.xsd语义约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <!-- 如果不加任何特殊的配置,该Bean默认是singleton行为的 -->
    <bean id="chinese" class="edu.pri.lime._7_4_2.bean.Person">
        <property name="test" value="孙悟空"/>
    </bean>

</beans>



        Class: Person



package edu.pri.lime._7_4_2.bean;

public class Person {
    
    public Person(){
        System.out.println("==正在执行Person无参数的构造器==");
    }
    
    public void setTest(String name){
        System.out.println("正在调用setTest()方法,传入参数为:" + name);
    }
}



        Class: TestBean



package edu.pri.lime._7_4_2.test;

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

public class TestBean {

    public static void main(String[] args){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("app_7_4_2.xml");
    }
}



        Console:



==正在执行Person无参数的构造器==
正在调用setTest()方法,传入参数为:孙悟空



        配置文件<bean.../>如果没有任何特殊配置,该Bean就是singleton Bean,ApplicationContext会在容器初始化完成后,自动调用Person类的构造器创建chinese Bean,并以“孙悟空”作为参数去调用chinese Bean的setTest()方法。

        如果使用BeanFactory作为容器,BeanFactory不会预初始化容器中的Bean。

        为了阻止Spring容器预初始化容器中的singleton Bean,可以为<bean.../>元素指定lazy-init="true",该属性用于阻止容器预初始化该Bean。



<!-- 阻止容器预初始化Bean -->
    <bean id="chinese" class="edu.pri.lime._7_4_2.bean.Person" lazy-init="true">
        <property name="test" value="孙悟空"/>
    </bean>



      7.4.3 ApplicationContext 的国际化支持

        ApplicationContext接口继承了MessageSource接口,因此具有国际化功能。

        MessageSource接口中定义的两个用于国际化的方法:

          String getMessage(String code,Object[] args,Locale loc)

          String getMessage(String code,Object[] args,String default,Locale loc)

        ApplicationContext 正是通过这两个方法来完成国际化的,当程序创建ApplicationContext容器时,Spring自动查找配置文件中名为messageSource的Bean实例,一旦找到这个Bean实例,上述两个方法的调用就被委托给该messageSource Bean。如果没有该Bean,ApplicationContext会查找其父容器中的messageSource Bean;如果找到,它将被作为messageSource Bean 使用。如果无法找到messageSource Bean,系统将会创建一个空的StaticMessageSource Bean ,该Bean能就收上述两个方法的调用。

        在Spring中配置messageSource Bean是通常使用ResourceBundleMessageSource类。

        message_zh_CN.properties:



hello=\u6B22\u8FCE\u4F60,{0}
now=\u73B0\u5728\u65F6\u95F4\u662F\uFF1A{0}



        message_en_US.properties:



hello=welcome,{0}
now=now is:{0}



        app_7_4_3.xml:



<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring 配置文件的根元素,使用Spring-beans-4.0.xsd语义约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean id="messageSource"
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <!-- 驱动Spring调用messageSource Bean的setBasenames()方法, 
        该方法需要一个数组参数,使用list元素配置多个数组元素 -->
        <property name="basenames">
            <list>
                <value>message</value>
                <!-- 如果有多个资源文件,全部列在此处 -->
            </list>
        </property>
    </bean>
</beans>



        SpringTest:



package edu.pri.lime._7_4_3.main;

import java.util.Date;
import java.util.Locale;

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

public class SpringTest {

    public static void main(String[] args){
//        实例化ApplicationContext
        ApplicationContext ctx = new ClassPathXmlApplicationContext("app_7_4_3.xml");
//        使用getMessage()方法获取本地化消息。
//        Locale的getDefault()方法获取本地化消息
//        Gets the current value of the default locale for the specified Category for this instance of the Java Virtual Machine.
//        Category used to represent the default locale for formatting dates, numbers, and/or currencies.
        String hello = ctx.getMessage("hello", new String[]{"孙悟空"},Locale.getDefault(Locale.Category.FORMAT));
        String now = ctx.getMessage("now", new Object[]{new Date()},Locale.getDefault(Locale.Category.FORMAT));
//        打印出两条本地化信息
        System.out.println(hello);
        System.out.println(now);
        
        System.out.println("----------------------------");
        Locale enUs = new Locale("en", "US");
        String hello_ = ctx.getMessage("hello", new String[]{"lime"}, enUs);
        String now_ = ctx.getMessage("now", new Object[]{new Date()}, enUs);
        System.out.println(hello_);
        System.out.println(now_);
    }
}



        Spring 的国际化支持,其实是简历在Java程序国际化的基础之上的。其核心思路都是将程序中需要实现国际化的信息写入资源文件,而代码中仅仅使用相应的各信息的Key。

      7.4.4 ApplicationContext 的事件机制

        ApplicationContext的事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext的事件处理。

        如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。

        Spring的事件框架有如下两个重要成员:

          ApplicationEvent:容器事件,必须由ApplicationContext发布。

          ApplicationListener:监听器,可由容器中的任何监听器Bean担任。

        Spring的事件机制与所有的事件机制有基本相似,它们都需要由事件源、事件和事件监听器组成。只是此处的事件源是ApplicationContext,且事件必须由Java程序显式触发。

         Class:EmailEvent



package edu.pri.lime._7_4_4.bean;

import org.springframework.context.ApplicationEvent;

//只要一个Java类继承了ApplicationEvent基类,那该对象就可作为Spring容器的容器事件(观察者模式:主题)
public class EmailEvent extends ApplicationEvent {

    private static final long serialVersionUID = 1L;
    
    private String address;
    private String text;
    
    public EmailEvent(Object source) {
        super(source);
        // TODO Auto-generated constructor stub
    }

//    初始化全部成员变量的构造器
    public EmailEvent(Object source, String address, String text) {
        super(source);
        this.address = address;
        this.text = text;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}



        Class : MyOtherEvent



package edu.pri.lime._7_4_4.bean;

import org.springframework.context.ApplicationEvent;

public class MyOtherEvent extends ApplicationEvent {
    private static final long serialVersionUID = -2196975742599078369L;

    public MyOtherEvent(Object source) {
        super(source);
        // TODO Auto-generated constructor stub
    }
}



        Class : EmailNotifier



package edu.pri.lime._7_4_4.bean;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

//容器事件的监听器类必须实现ApplicationListener接口(观察者模式:观察者)
public class EmailNotifier implements ApplicationListener {


//    该方法会在容器发生任何事件时都会自动触发
    public void onApplicationEvent(ApplicationEvent event) {
//        只处理EmailEvent,模拟发送email通知
        if(event instanceof EmailEvent){
            EmailEvent emailEvent = (EmailEvent) event;
            System.out.println("EmailEvent " + event);
            System.out.println("需要发送邮件的接收地址  " + emailEvent.getAddress());
            System.out.println("需要发送邮件的邮件正文  " + emailEvent.getText());
        }else{
//            其他事件不作任何处理
            System.out.println("其他事件 :" + event);
        }
    }

}



        XML :app_7_4_4.xml



<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring 配置文件的根元素,使用Spring-beans-4.0.xsd语义约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <!-- 配置监听器 -->
    <!-- 只要在Spring中配置一个实现了ApplicationListener接口的Bean,Spring容器就会把这个Bean当成容器事件的事件监听器 -->
    <bean class="edu.pri.lime._7_4_4.bean.EmailNotifier" />
</beans>



        Class : SpringTest



package edu.pri.lime._7_4_4.main;

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

import edu.pri.lime._7_4_4.bean.EmailEvent;
import edu.pri.lime._7_4_4.bean.MyOtherEvent;

public class SpringTest {

    public static void main(String[] args){
//        当系统创建Spring容器、加载Spring容器时会自动触发容器事件,容器时间监听器可以监听到这些事件。
        ApplicationContext ctx = new ClassPathXmlApplicationContext("app_7_4_4.xml");
//        创建一个ApplicationEvent对象
        EmailEvent emailEvent = new EmailEvent("test","zzulime@outlook.com","this is a test");
//        调用ApplicationContext的pulishEvent()方法来主动触发容器事件。
        ctx.publishEvent(emailEvent);
        
        System.out.println("---------------");
        
        MyOtherEvent myOtherEvent = new MyOtherEvent("myOtherEvent");
        ctx.publishEvent(myOtherEvent);
    }
}



        Console :



十二月 31, 2016 11:58:05 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@56cbfb61: startup date [Sat Dec 31 23:58:05 CST 2016]; root of context hierarchy
十二月 31, 2016 11:58:06 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [app_7_4_4.xml]
其他事件 :org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.ClassPathXmlApplicationContext@56cbfb61: startup date [Sat Dec 31 23:58:05 CST 2016]; root of context hierarchy]
EmailEvent edu.pri.lime._7_4_4.bean.EmailEvent[source=test]
需要发送邮件的接收地址  zzulime@outlook.com
需要发送邮件的邮件正文  this is a test
---------------
其他事件 :edu.pri.lime._7_4_4.bean.MyOtherEvent[source=myOtherEvent]



        监听器不仅监听到程序所触发的事件,也监听到容器内置的事件。实际上,如果开发者需要在Spring容器初始化、销毁时回调自定义方法,就可以通过上面的事件监听器来实现。

        如果Bean希望发布容器事件,则该Bean必须先获得对Spring容器的引用。为了让Bean获得对Spring容器的引用,可让Bean类实现ApplicationContextAware或BeanFactoryAware接口。

        Spring 提供了如下几个内置事件:

          ⊙ ContextRefreshedEvent : ApplicationContext容器初始化或刷新触发该事件,此处的初始化是指,所有的Bean被成功加载,后处理的Bean被检测并激活,所有的singleton Bean 被预实例化,ApplicationContext容器已就绪可用。

          ⊙ ContextStartedEvent : 当使用ConfigurableApplicationContext (ApplicationContext的子接口)接口的start()方法启动ApplicationContext容器时触发该事件。容器管理生命周期的Bean实例将获得一个指定的启动信号,这在经常需要停止后重新启动的场合比较常见。

          ⊙ ContextClosedEvent : 当使用ConfigurableApplicationContext(ApplicationContext的子接口)接口的close()方法关闭ApplicationContext容器时触发该事件。

          ⊙ ContextStoppedEvent : 当使用ConfigurableApplicationContext(ApplicationContext的子接口)接口的stop()方法使ApplicationContext停止时触发该事件。此处的“停止”意味着容器管理生命周期的Bean实例将获得一个指定的停止信号,被停止的Spring容器可再次调用start()方法重新启动。

          ⊙ RequestHandledEvent : Web相关的事件,只能应用于使用DispatcherServlet的Web应用中。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。

          ⊙ SessionConnectedEvent : 用于WebSocket功能服务

          ⊙ SessionConnectEvent :用于WebSocket功能服务

          ⊙ SessionDisconnectEvent :用于WebSocket功能服务

      7.4.5 让Bean获取Spring容器

        在Web应用中,Spring容器通常采用声明式方式配置产生:开发者只要在web.xml文件中配置一个Listener,该Listener将会负责初始化Spring容器,前端MVC框架可以直接调用Spring容器中的Bean,无须访问Spring容器本身。在这种情况下,容器中的Bean处于容器管理下,无须主动访问容器,只需接收容器的依赖注入即可。

        在某些特殊的情况下,Bean需要实现某个功能(比如该Bean需要输出国际化消息,或者giantBean需要想Spirng容器发布事件......),但该功能必须借助于Spring容器才能实现,此时就必须让该Bean先获取Spring容器,然后借助于Spring容器来实现该功能。

        为了让Bean获取它所在的Spring容器,可以让该Bean实现BeanFactoryAware接口,并覆写唯一的方法setBeanFacotry(BeanFactory beanFactory)。

          setBeanFactory(BeanFactory beanFactory) : beanFactory参数指向创建它的BeanFactory。

        该方法将由Spring调用,Spring调用该方法时会将Spring容器作为参数传入该方法。

        与BeanFactoryAware接口类似的有ApplicationContextAware接口,实现该接口的Bean需要实现setApplicationContext(ApplicationContext applicationContext)方法,该方法有Spring来调用,当Spring容器调用该方法时,它会把自身作为参数传入该方法。

         Class : Person



package edu.pri.lime._7_4_5.bean;

import java.util.Date;
import java.util.Locale;

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

/* 国际化功能需要借助于Spring容器来实现,因此程序就需要让Person类实现ApplicationContextAware接口。
 */
public class Person implements ApplicationContextAware {

//    将BeanFactory容器以成员变量保存
    private ApplicationContext ctx;
//    Spring 容器会检测容器中所有的Bean,如果发现某个Bean实现了ApplicationContextAware接口,
//    Spring容器会在创建该Bean后,自动调用该方法,调用该方法时,会将容器本身最为参数传给该方法
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

//    借助ApplicationContext容器的getMessage方法获取国际化消息
    public void sayHi(String name){
        System.out.println(ctx.getMessage("hello", new String[]{name}, Locale.getDefault(Locale.Category.FORMAT)));
    }
}



        property :message_zh_CN.properties



hello=\u6B22\u8FCE\u4F60,{0}
now=\u73B0\u5728\u65F6\u95F4\u662F\uFF1A{0}



        property :message_en_US.properties



hello=welcome,{0}
now=now is:{0}



        XML : app_7_4_5.xml



<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring 配置文件的根元素,使用Spring-beans-4.0.xsd语义约束 -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <!-- 加载容器国际化所需要的语言资源文件 -->
    <bean id="messageSource"
        class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>message</value>
            </list>
        </property>
    </bean>
    <!-- Spring容器会检测容器中所有的Bean,如果发现某个Bean实现了ApplicationContextAware接口, 
    Spring容器会在创建该Bean之后,自动调用该Bean的setApplicationContext()方法, 
        并将容器本身最为参数传给该方法。 -->
    <bean id="person" class="edu.pri.lime._7_4_5.bean.Person" />

</beans>



        Class : SpringTest



package edu.pri.lime._7_4_5.main;

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

import edu.pri.lime._7_4_5.bean.Person;

public class SpringTest {

    public static void main(String[] args){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("app_7_4_5.xml");
        Person per = ctx.getBean("person",Person.class);
        per.sayHi("lime");
    }
}



    7.5 Spring容器中的Bean

      7.5.1 Bean的基本定义和Bean别名

        <beans.../>元素是Spring配置文件的根元素,该元素可以指定如下属性:

          default-lazy-init : 指定该<beans.../> 元素下配置的所有Bean默认的延迟初始化行为。

          default-merge : 指定该<beans.../> 元素下配置的所有Bean默认的merge行为。

          default-autowire : 指定该<beans.../> 元素下配置的所有Bean 默认的自动装配行为。

          default-autowire-candidates: : 指定该<beans.../> 元素下配置的所有Bean 默认是否作为自动装配的候选Bean。

          default-init-method : 指定该<beans.../> 元素下配置的所有Bean 默认的初始化方法。

          default-destroy-method : 指定该<beans.../> 元素下配置的所有Bean 默认的回收方法。

        <beans.../>元素下所能指定的属性都可以在每个<bean.../>子元素中指定----将属性名去掉default即可。区别是:为<bean.../>指定的这些属性,只对特定Bean起作用;如果在<beans.../>元素下指定这些属性,这些属性将会对<beans.../>包含的所有Bean都起作用。当二者所指定的属性不一致时,<bean.../>下指定的属性会覆盖<beans.../>下指定的属性。

        <bean.../>元素的id属性具有唯一性,而且是一个真正的XML ID 属性,因此其他XML元素在引用id时,可以利用XML解析器的验证功能。

        指定别名有两种方式:

        ⊙ 定义<bean.../>元素时通过name属性指定别名;如果需要为Bean实例指定多个别名,则可以在name属性中使用逗号、冒号或者空格来分隔多个别名,后面通过任一别名即可访问该Bean实例。

        ⊙ 通过<alias.../>元素为已有的Bean指定别名。

          <alias name="" alias=""/> name:指定一个Bean实例的标识名,表明将为该Bean实例指定别名;alias:指定一个别名。



<bean id="person" class="edu.pri.lime._7_4_5.bean.Person" name="chinese,lime,oracle"/>
    <alias name="person" alias="jackson"/>
    <alias name="person" alias="tomcat"/>



  啦啦啦