ApplicationContext的额外能力
Spring IOC容器也提供了下列功能:
1、负责国际化的MessageSource接口
2、通过ResourceLoader接口来加载资源
3、负责事件发布的ApplicationEventPublisher,监听器ApplicationListener
4、提供父容器访问功能的HierarchicalBeanFactory
这里只介绍前三项
MessageSource
ApplicationContext接口继承并扩展了MessageSource,提供了国际化的能力,HierarchicalMessageSource接口可以分层次的解析消息,MessageSource接口提供了下列方法:
String getMessage(String code,Object[] args,String default,Locale loc):如果在locale中没有检索到对应消息,则使用default指定的消息,args指定占位符的值,code指定消息名,loc指定国际化信息
String getMessage(String code,Object[] args,Locale loc):与前面的函数功能一样,不一样的是缺少了default参数,如果没有找到对应的消息,会抛出NoSuchMessageException异常
String getMessage(MessageSourceResolvable resolvable,Locale locale):上述两种方法所用到的参数全部封装在MessageSourceResolvable,功能与前两者一样
当ApplicationContext初始化后,会查找MessageSource实现类对应的bean,这个bean的名字必须是messageSource,如果这样的bena被查找到,所有之前之前描述的方法都会由这个bean调用,否则,ApplicationContext会查找父容器,如果父容器也没有查找到,一个空的DelegatingMessageSource对象会被实例化,负责上述方法的调用,MessageSource只能查看类路径下(classpath)的文件
Spring提供了两种MessageSource的实现——ResourceBundleMessageSource和StaticMessageSource,两者都继承并实现了HierarchicalMessageSource(可以嵌套处理message),StaticMessageSource很少使用,ResourceBundleMessageSource如下使用:
<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>
在basenames属性中可以指定资源文件,上述例子假设我们有三个资源文件,名为format、exceptions、windows,如下:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
省略了windwos.properties文件,由于ApplicationContext本身继承了MessageSource接口,所以可以如下使用:
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", null);
System.out.println(message);
}
输出结果为:Alligators rock!
上述例子中exceptions.properties使用了占位符,可以在getMessage函数中指明占位符的值:
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);
}
}
运行结果为:The userDao argument is required.
之前介绍的功能都没有涉及到国际化,接下来介绍一下国际化的方式:
假设我们想根据英国(en-GB)语言环境解析消息,我们需要创建三个文件(format_en_GB.properties
, exceptions_en_GB.properties
, 、 windows_en_GB.properties
),其中一个文件的内容为:
# 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);
}
运行结果:Ebagum lad, the 'userDao' argument is required, I say, required.
标准和自定义事件
ApplicationListener接口会处理事件,每次ApplicationEventPublisher实现类将事件发布到ApplicationContext中时,都会通知ApplicationListener,两者使用的是观察者模式
Spring提供了下列标准事件:
ContextRefreshedEvent:当Application初始化或是刷新(注册了一个Bean以后会使用refresh()刷新容器)完毕,容器初始化完毕意味着所有的bean都被初始化,容器可以被正常使用。
ContextStartedEvent:当ApplicationContext启动时发布,启动是指调用ConfigurableApplication接口的start()方法,此时所有继承并实现了Lifecycle的的bean将会接收到启动信息
ContextStoppedEvent:当ApplicationContext停止时发布,停止是指调用ConfigurableApplication接口的stop()方法,此时所有继承并实现了Lifecycle的的bean将会接收到停止信息
ContextClosedEvent:当ApplicationContext关闭时发布,关闭是指调用ConfigurableApplication接口的close()方法,此时所有的单例bean都会被摧毁,此时容器完全被摧毁,无法刷新或是重新启动
RequestHandledEvent:HTTP请求处理完毕后发布的事件,只在web应用中有效(即使用DispatcherServlet)
spring允许我们自定义事件处理机制,首先先要定义事件类,通过继承ApplicationEvent类,即可成为事件类:
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...
}
事件的发布需要通过ApplicationEventPublisher接口,该接口的publishEvent()方法即可发布事件,事件得发布一般通过继承ApplicationEventPublisherAware接口实现
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...
}
}
在配置阶段,Spring容器会检测到EmailService继承了ApplicationEventPublisherAware接口,会自动调用该接口的setApplicationEventPublisher()方法,该方法传入的是Spring容器本身(其继承实现了ApplicationEventPublisher),此时便可以将事件发布到容器当中,为了接收到事件,需要继承ApplicationListerner接口:
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...
}
}
注意到ApplicationListener<BlackListEvent>,意味着这个类为BlackListEvent的事件监听器,由它负责处理BlackListEvent事件,监听器同步接收事件并进行处理,意味着publishEvent()方法是阻塞的,接着onApplicationEvent方法会被调用处理事件
基于注解的事件监听器
事件监听器除了继承ApplicationListener实现外,也可以使用@EventListener注解,可以将上述例子的BlackListNotifier更改为:
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
使用注解,不用继承接口,同时事件监听的方法名称也可以自定义,监听的事件也可以通过函数参数类型进行解析,如果一个方法需要监听许多事件或者不想使用函数参数,也可以如下定义:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
通过注解的condition属性,也可以监听满足一定条件的事件,condition属性的值是SPEL表达式:
@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
上述例子表明监听事件必须含有test属性,并且该属性值为foo,当我们处理完这个事件后,也可以发布另外一个事件(通过函数返回值):
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
如果想发布多个事件,则返回事件的Collection,异步监听器不支持这种使用方式
异步监听器
@Async注解允许异步执行方法,用于监听器,可实现异步监听器:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
是用异步监听器需要注意以下几点:
1、如果监听器抛出异常,会将异常传递给调用者,更多详情请查看AsyncUncaughtExceptionHandler
2、不允许通过return的方式发布事件
监听器的执行顺序
一个事件可以有多个监听器处理,此时我们可以通过@Order注解声明执行顺序,值越大越慢执行
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
在监听器中使用泛型
监听器的参数允许使用泛型
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
加载资源文件
Applicationcontext是一个资源加载器,资源的位置通过url的方式指定,只要能通过url定位的资源都可以导入
我们也可以使用Resource类型来获得静态资源,它可以作为依赖被注入,当将一个 ResourceLoaderAware 接口的实现类部署到应用上下文时(此类会作为一个 spring 管理的 bean), 应用上下文会识别出此为一个 ResourceLoaderAware 对象,并将自身作为一个参数来调用 setResourceLoader() 函数,如此,该实现类便可使用 ResourceLoader 获取 Resource 实例来加载你所需要的资源,更多详情请看:https://blog.csdn.net/xiangjai/article/details/53954252