Spring有两个核心接口:BeanFactory和ApplicationContext(BeanFactory的子接口);他们都可代表Spring容器,Spring容器是生成Bean实例的工厂,并管理容器中的Bean;Bean是Spring管理的基本单位,在基于Spring的JavaEE应用中,所有的组件都被当成Bean处理,包括数据源、Hibernate的SesisonFactoy、事务管理器等
应用中的所有组件,都处于Spring的管理下,都被Spring以Bean的方式管理,Spring负责创建Bean实例,并管理其生命周期
Spring容器负责创建Bean实例,所以需要知道每个Bean的实现类,Java程序面向接口编程,无须关心Bean实例的实现类;但Spring容器必须能精确知道每个Bean实例的实现类,因此Spring配置文件必须精确配置Bean实例的实现类
一、Spring容器
Spring容器最基本的接口就是BeanFactory:负责配置、创建、管理Bean;负责管理Bean与Bean之间的依赖关系;其实现类有XmlBeanFactory;其子类ApplicationContext的实现类有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext;在Web应用中使用Spring容器,通常有XmlWebApplicationContext、AnnotationConfigApplicationContext
创建Spring容器实例时,必须提供Spring容器管理的Bean详细配置信息。创建BeanFactory实例时,应提供XML配置文件作为参数。XML配置文件通常使用Resource对象传入
BeanFactory接口常用方法:
boolean containsBean(String beanId):判断Spring容器是否包含id为beanId的Bean实例
<T> T getBean(Class<T> requiredType):获取Spring容器中属于requiredType类型的、唯一的Bean实例
Obejct getBean(String beanId):返回容器id为beanId的Bean实例
<T> T getBean(String beanId,Class<T> requiredType):返回容器id为beanId,并类型为requiredType的Bean
Class<?> getType(String beanId):返回容器中指定Bean实例的类型
对于独立的应用程序,可通过如下方法实例化BeanFactory: // 搜索当前文件路径下的beans.xml文件创建Resource对象 InputStreamResource isr = new FileSystemResource(“bean.xml”); // 以Resource对象作为参数,创建BeanFactory实例 XmlBeanFactory factory = new XmlBeanFactory(isr); 或 // 搜索类加载路径,以类加载路径下的beans.xml文件创建Resource对象 ClassPathResource res = new ClassPathResource(“beans.xml”); // 以Resource对象为参数,创建BeanFactory实例 XmlBeanFactory factory = new XmlBeanFactory(res);
应用中有多个属性配置文件,应采用BeanFcatory的子接口ApplicationContext来创建BeanFactory的实例 ApplicationContext常用实现类: FileSystemXmlApplicationContext:以基于文件系统的XML配置文件创建ApplicationContext实例 ClassPathXmlApplicationContext:以类加载路径下的XML配置文件创建ApplicationContext实例 需同时加载多个XML配置文件,可采用如下方式: // 搜索CLASSPATH路径,以CLASSPATH路径下的bean.xml、service.xml文件创建ApplicationContext ApplicationContext appContext = new ClassPathXmlApplicationContext(new String[]{“bean.xml”,”service.xml”}); 或 // 以指定路径下的bean.xml、service.xml文件创建ApplicationContext ApplicationContext appContext = new FileSystemXmlApplicationContext(new String[]{“bean.xml”,“service.xml”});
Spring配置文件可用XML Schema来定义配置文件的语义约束;也可用DTD(不能用Spring2.X、Spring3.0所新增的配置标签) <?xml version="1.0" encoding="UTF-8"?> <!-- 使用spring-beans-3.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-3.0.xsd"> ……………… </beans> <?xml version="1.0" encoding="UTF-8"?> <!-- 指定Spring配置文件的DTD信息 --> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> ... </beans>
二、使用ApplicationContext
Application允许以声明式方式操作容器,无须手动创建它;可利用如ContextLoader的支持类,在Web应用启动时自动创建ApplicaionContext;也可用编程方式创建ApplicationContext
除提供BeanFactory所支持的全部功能外,ApplicationContext还有如下额外功能:
继承MessageSource接口,因此提供国际化支持
资源访问,比如URL和文件
事件机制
载入多个配置文件
以声明式启动、并创建Spring容器
当系统创建ApplicationContext容器时,默认预初始化所有singleton Bean;即当ApplicationContext容器初始化完成后,容器中所有singleton Bean也实例化完成
三、ApplicationContext的国际化支持
MessageSource接口中用于国际化的方法:
String getMessage(Stringcode,Object[] args,Locale loc)
String getMessage(Stringcode,Object[] args,Stringdefault,Locale loc)
String getMessage(MessageSourceResolvableresolvable,Locale locale)
ApplicationContext正是通过这三个方法来完成国际化的,当程序创建ApplicationContext容器时,Spring自动查找在配置文件中名为messageSource的Bean实例,一旦找到这个Bean实例,上述3个方法的调用被委托给该messageSource Bean;若没有该Bean,ApplicationContext会查找其父定义中的messageSourceBean;若找到,它被作为messageSource使用;
若无法找到messageSource Bean,系统将创建一个空的StaticMessageSourceBean,该Bean能接受上述三个方法的调用;
在Spring中配置messageSource Bean时通常使用ResourceBundleMessageSource类
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>message</value> <!-- 若有多个资源文件,全部列在此处 --> </list> </property> </bean>
美式英语资源文件(message_en_US.properties) hello=welcome,{0} now=now is:{0}
简体中文资源文件(message.properties) hellow=欢迎你,{0} now=现在时间是:{0} 因为该资源文件包含了非西欧文字,应使用native2ascii工具(JDK中bin目录下有)将这份资源文件国际化: 将该文件放到jdk的bin目录下 然后输入命令native2ascii message.properties message_zh_CN.properties
public class SpringTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 创建数组 String[] a = { "读者" }; // 使用getMessage方法获取本地化信息;Locale的getDefault方法 String hello = ctx.getMessage("hello", a, Locale.getDefault()); Object[] b = { new Date() }; String now = ctx.getMessage("now", b, Locale.getDefault()); // 打印出两条本地化消息 System.out.println(hello); System.out.println(now); } }
四、ApplicationContext的事件机制
ApplicationContext的事物机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可实现ApplicationContext的事物处理;若容器中有一个ApplictionListenerBean,每当ApplicaitonContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发
Spring的事件框架有如下两个重要成员:
ApplicationEvent:容器事件,必须由ApplicationContext发布
ApplicationListener:监听器,可由容器中的任何监听器Bean担任
实际上,Spring的事件机制与所有事件机制都基本相似,它们都需要事件源、事件和事件监听器组成;只是此处的事件源是ApplicationContext,且事件必须由Java程序显示触发
/** * 定义一个ApplicationEvent类,其对象就是一个Spring容器事件 * */ public class EmailEvent extends ApplicationEvent { private String address; private String text; public EmailEvent(Object source) { super(source); } // 定义一个有参数的构造器 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; } }
/** * 容器监听器类 */ public class EmailNotifier implements ApplicationListener { /** * 每当容器内发生任何事件时,此方法都被触发 */ @Override public void onApplicationEvent(ApplicationEvent evt) { if (evt instanceof EmailEvent) { // 只处理EmailEvent,发送email通知。。。 EmailEvent emailEvent = (EmailEvent) evt; System.out.println("需要发送邮件的接受地址" + emailEvent.getAddress()); System.out.println("需要发送邮件的邮件正文" + emailEvent.getText()); } else { // 容器内置事件不作任何处理 System.out.println("容器本身的事件: " + evt); } } }
<beans> <!-- 配置监听器 --> <bean class="org.app.main.EmailNotifier"/> </beans> <!-- 当在Spring中配置一个实现了ApplicationListener的Bean,Spring容器就会把这个Bean当成容器事件的监听器 --> <!-- 当系统创建Spring容器、加载Spring容器时会自动触发容器事件,容器事件监听器可监听到这些事件 --> <!-- 此外,程序也可调用ApplicationContect的pulishEvent()方法来主动触发容器事件-->
/** * 程序调用ApplicationContext的publishEvent来触发事件 * */ public class SpringTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 创建一个ApplicationEvent对象 EmailEvent ele = new EmailEvent("hello", "[email protected]", "this is a test"); // 主动触发容器事件 ctx.publishEvent(ele); } }
运行结果:
容器本身的事件: org.springframework.context.event.ContextRefreshedEvent[source=org[email protected]2d776d65: startup date [Fri Nov 21 14:48:01 GMT+08:00 2014]; root of context hierarchy]
需要发送邮件的接受地址[email protected]
需要发送邮件的邮件正文this is a test
从执行结果可看出,监听器不仅监听到程序所触发的事件,也监听到容器内置的事件;实际上,若开发者需要在Spring容器初始化、销毁时回调自定义方法,就可通过上面事件监听器来实现
若Bean想发布事件,则Bean必须获得其容器的引用;若程序中没有直接获取容器的引用,则应该让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处理用户请求结束后,系统会自动触发该事件
五、让Bean获取Spring容器
前面介绍的几个例子都是程序先创建Spring容器,再调用Spring容器的getBean()方法来获取Spring容器中的Bean;在这种访问模式下,程序总是持有Spring容器的引用
但在实际应用中,尤其是Web应用中,Spring容器通常采用声明式方式配置产生:在web.xml文件中配置一个Listener,该Listener将会负责初始化Spring容器;前端MVC框架可直接调用Spring容器(无须访问Spring容器本身);在这种情况下,容器中Bean处于容器管理下,无须主动访问容器,只需接受容器的注入管理即可;Bean实例的依赖关系通常由容器动态注入,无须Bean实例主动请求
在这种情况下,Spring容器中Bean通常不会需要访问容器中其他的Bean-----采用依赖注入,让Spring把依赖的Bean注入到依赖Bean中即可;但在某些特殊情况下,容器中的Bean可能需要主动访问Spring容器本身,Spring也为这种需求做好了准备
实现BeanFactoryAware接口的Bean,拥有访问BeanFactory容器的能力,实现BeanFactoryAware(/ApplicationContextAware)接口的Bean实例被容器创建后,他会有一个引用,该引用指向创建它的BeanFactory;BeanFactoryAware接口只有setBeanFactory(BeanFactorybeanFactory)(/setApplicationContext(ApplicationContextappicationContext))方法:该方法中的beanFactory指向创建它的BeanFactory;该方法由Spring调用;当Spring容器调用该方法时,它会把自身作为参数传入该方法
/** * 定义Bean类,实现ApplicationContextAware接口,该类将拥有访问容器的能力 * */ public class Chinese implements ApplicationContextAware { // 将BeanFactory容器以成员变量保存 private ApplicationContext ctx; /** * 实现ApplicationContextAware接口必须实现的方法 */ @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { this.ctx = ctx; } // 获得ApplicaitonContext的测试方法 public ApplicationContext getContext() { return ctx; } }
实现ApplicationContextAware接口让Bean拥有了访问容器的能力,但污染了代码,导致代码与Spring接口耦合在一起;因此,若不是特别必要,不要直接访问容器