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=u6B22u8FCEu4F60,{0} now=u73B0u5728u65F6u95F4u662FuFF1A{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=u6B22u8FCEu4F60,{0} now=u73B0u5728u65F6u95F4u662FuFF1A{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"/>
啦啦啦