目录
5.将Spring ApplicationContext作为Java EE RAR文件部署
ApplicationContext的额外功能
org.springframework.beans.factory包提供了管理与操作beans的基础功能。
org.springframework.context包提供了ApplicationContext接口,此接口扩展了BeanFactory接口,并且扩展了其他接口,用以提供面向框架风格应用的额外功能。
开发者通常使用完全声明风格使用ApplicationContext。
1.使用MessageSource国际化
ApplicationContext扩展了名为MessageSource的接口,因此提供了国际化功能。
Spring提供了HierarchicalMessageSource接口,因此可以层次解析信息。
这些接口构成了Spring消息处理的基础。
这些接口提供了getMessage方法用来获取检索信息。
当ApplicationContext被载入时,会自动搜索定义在context中的MessaegSource bean。
bean的名字必须为messageSource。
当messageSource bean被找到,所有的getMessage方法都会被委派到信息源。
ps:如果没有找到信息源,ApplicationContext会试图寻找包含名为messageSource的bean的双亲,并将其作为MessageSource使用。
ps:如果ApplicationContext还是无法找到可使用的信息源,会创建一个空DelegatingMessageSource。
Spring为MessageSource提供了两种实现,ResourceBundleMessageSsource与StaticMessageSource。
这两种实现又都实现了HierarchicalMessageSource,用于处理嵌套信息。
StaticMessageSource很少使用,不过提供了编程添加信息到源的方法。
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
note:ResourceBundleMessageSource的使用。
note:这个例子假设,在classpath下定义了三个名为formate、exceptions、windows的资源包(属性文件)。
ps:resource bundle,即资源包,指编译在应用程序中的属性文件。
ps:JDK通过将使用ResourceBundles作为标准方法,用于处理解析信息的请求。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", null);
System.out.println(message);
}
note:程序使用MessageSource功能。
note:获取信息源中键为message的属性的值。
ps:ApplicationContext的实现也是MessageSource的实现。
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.foo.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", null);
System.out.println(message);
}
}
# in exceptions.properties
argument.required=The {0} argument is required.
note:传入的参数转化为字符串,然后将之插入到检索返回信息的占位符上。
ps:String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource中检索信息的基础方法。
ps:如果指定场所没有信息被检索到,default将会被返回。
ps:提供的参数会使用标准库提供的MessageFormat功能用于返回的信息。
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
note:针对British(en-GB)地区解析信息。
ps:Spring的MessageSource实现遵循与JDK的ResourceBundle相同的场所解析规则与回退操作规则。
ps:通常情况下,地区解析有应用运行环境管理。
可以使用MessageSourceAware接口,获取任何定义的MessageSource。
任何定义在ApplicationContext中并实现了MessageSourceAware接口的bean,会被注入context中的MessageSource。
可以使用ReloadableResourceBundleMessageSource类代替ResourceBundleMessageSource。
ReloadableResourceBundleMessageSource可以从任何Spring的资源位置读取属性文件(不只是classpath下),并且支持热加载属性文件。
2.标准事件与定制事件
ApplicationContext的事件处理机制由ApplicationEvent类与ApplicationListener接口提供。
如果实现了ApplicationListener接口的bean配置到了context中,每一次ApplicationEvent被发布,都会通知这个bean。
这是一个标准的观察者模式。
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String test;
public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}
// accessor and other methods...
}
note:通过继承ApplicationEvent构建新事件BlackListEvent。
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}
}
note:需要作为bean配置进容器。
ps:通过调用ApplicationEventPublisher的publishEvent()方法发布定制的ApplicationEvent。
ps:为了完成发布,通常需要通过实现ApplicationEventPublisherAware获取实现类,然后作为bean注册。
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
note:需要作为bean配置进容器。
ps:默认情况下所有的监听器同步接收事件。
ps:同步单线程处理处理监听器接收事件的一个优点为可以在事务上下文内部进行操作。
Spring的事件机制被设计为位于同一个ApplicationContext下的beans之间的简单通信。
Spring Integration项目提供了用于构建well-known Spring程序模型的轻量级、面向模式、事件驱动的架构的完整支持。
基于注解的事件监听器
使用@EventListener监听事件
public class BlackListNotifier {
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
ps:通过使用@EventListener注解,监听器可以不再实现ApplicationListener接口,而是直接使用public方法。
使用@EventListener同时监听多个事件
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
通过SpEL设置监听过滤
@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
note:只有当事件的test属性等于foo时,此事件才会被监听。
ps:每一个SpEL表达式都针对专用的上下文求值
异步监听器
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
ps:异步监听器如果抛出异常,异常不会传给调用者。
ps:异步监听器不能发送回复。
监听器排序
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
泛型事件
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
note:只监听类型参数为Person的EntityCreatedEvent事件。
ps:由于类型擦除,所以只有当被触发的Event解析被监听器过滤的泛型参数,泛型事件机制才会运作。
使用ResolvableTypeProvider引导Spring框架
public class EntityCreatedEvent<T>
extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}
3.对低层次资源的便捷访问
为了更好地理解与使用ApplicationContext,用户通常需要熟悉Spring的Resource抽象。
一个ApplicationContext就是一个ResourceLoader,用于加载Resource。
Resource基本上就是一个有着更多特性的java.net.URL,事实上,Resource的实现包装了java.net.URL的实例。
Resource几乎可以以透明样式从任何位置(classpath、文件系统以及任何可以以标准URL或其变体描述的位置)获取低层次资源。
如果资源位置字符串是一个没有前缀的简单路径,这些资源位置字符串所表达的含义将会适应实际ApplicationContext类型。
实现了ResourceLoaderAware的bean,在ApplicationContext的初始化阶段,ResourceLoader会被注入ApplicationContext依赖。
可以选择将Resource设置为依赖注入点,用于访问静态资源,这些Resource会被作为普通的属性注入。
可以将这些Resource属性指定为普通的字符串路径。
这些Resource属性使用一个自动在context中注册的特定的JavaBean ProoertyEditor,将这些文本字符串转化为真实的Resource对象。
提供给ApplicationContext构造器的路径是实际资源字符串,对于特定的context实现,简单形式的路径会被进行适当的处理。
ClassPathXmlApplicationContext将简单位置路径作为classpath位置处理。
可以通过给位置路径添加特定的前缀,强迫从classpath或URL中加载定义,无论实际的ApplicationContext的类型是什么。
4.初始化web应用的ApplicationContext
使用ContextLoaderListener注册一个ApplicationContext
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ps:ContextLoaderListener会检查contextConfigLocation参数。
ps:如果此参数不存在,listener默认使用/WEB-INF/applicationContext.xml。
ps:参数可以通过预定义的分隔符(逗号,分号,空格)输入多个xml文件路径。
5.将Spring ApplicationContext作为Java EE RAR文件部署
可以将ApplicationContext作为RAR文件部署,即将context以及所有所需的bean类与jar库全部封装为一个Java EE配置单元。
RAR等价于一个单机的ApplicationContext,只是运行在Java EE环境下,可以访问Java EE服务设施。
在部署WAR文件的场景下,部署RAR是一个更为自然的选择。
一个没有任何HTTP进入点的WAR文件只能用于将Spring的ApplicationContext导入Java EE环境中。
RAR部署对于不需要HTTP进入点并且只由信息终端与定时任务组成的ApplicationContext是一个理想的选择。
RAR部署的context下的beans,可以使用应用服务器资源,eg:JTA事务管理、JNDI绑定JDBC数据源、JMS ConnectionFactory实例、在JMX服务器注册,所有这些通过Spring的标准事务管理、JNDI、JMX支持设备实现。
Application组件还可以通过Spring的TaskExecutor抽象与应用服务器的 JCA WorkManage进行交互。
简单地将Spring的ApplicationContext部署为Java EE RAR文件:
- 将所有的应用类全部打包进RAR文件,即拥有不同的文件扩展名的JAR文件
- 将所有必须的Jar库全部添加进RAR文件的根下
- 添加"META-INF/ra.xml"部署描述符以及相关的bean定义的xml文件
- 将RAR文件放入应用服务器的部署目录下
RAR部署单元通常是自足的,即不会将自己的组件暴露给外界,甚至是相同应用的模块。
与基于RAR的ApplicationContext的交互通常通过与其他模块共享的JMS destination。
基于RAR的ApplicationContext(eg:定时任务)可以对文件系统中的文件做出反应。
如果需要同步访问外界时,可以通过导出被其他应用模块在相同机制下使用的RMI端点的方式实现。