Spring中ApplicationContext额外接口
参考官方文档
注:本文中的ApplicationContext是指应用程序上下文,而不是特指ApplicationContext接口。
通过MessageSource接口实现国际化
国际化简单来说就是在不修改内部代码的情况下,根据不同语言及地区显示相应的语言。
加载ApplicationContext时,自动搜索上下文中定义的MessagesSource Bean(名称必须为messageSource)。如果无法找到消息的任何源,则实例化一个空的messageSource。
/* 注入MessageSource */
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasenames("i18n/message");
return source;
}
在类路径下的i18n下创建message_zh_CN.properties、message_en_US.properties
# message_en_US.properties内容
argument.required=The {0} argument is required.
# message_zh_CN.properties内容
argument.required=需要参数:{0}!
这两个文件表示US和CN地区显示的消息,如果想添加更多,新建message_xxx.properties即可,具体可查看Java.util.Locel类
测试效果:
/* The loginName argument is required. */
System.out.println(context.getMessage("argument.required",
new Object[]{"loginName"}, Locale.US));
/* 需要参数:登录名! */
System.out.println(context.getMessage("argument.required",
new Object[]{"登录名"}, Locale.getDefault()));
通过ApplicationListener实现事件监听
ApplicationContext中的事件处理通过ApplicationEvent类和ApplicationListener接口提供。如果将ApplicationListener注入为bean,则每次ApplicationEvent都会发布到ApplicationContext,就会通知该bean。这是标准的观察者设计模式。
事件类型
所有的事件继承自JDK自带的EventObject,同样,所有的监听器继承JDK自带的EventListener。
事件类型 | 说明 |
---|---|
ContextRefreshedEvent | 容器刷新完之后触发(在refresh方法中的finishRefresh方法中发布) |
ContextStartEvent | 容器启动时发布,如ConfigurableApplicationContext的start方法(context首次start,无法监听到,因为监听器还未注入容器) |
ContextStoppedEvent | 容器停止时,如ConfigurableApplicationContext的stop方法 |
ContextClosedEvent | 容器关闭,ConfigurableApplicationContext的close方法或者通过JVM Shutdown钩子时发布。关闭意味着所有bean都将被销毁。关闭上下文后,它达到了其生命结束,不能刷新或重新启动。 |
RequestHandledEvent | 请求被处理完发布,仅在WEB场景下有效 |
ServletRequestHandledEvent | RequestHandledEvent子类,包含更多请求信息 |
除此之外,自定义事件也是很简单的。 |
使用事件监听
有两种方法:
- 实现ApplicationListener接口并注入到容器中
- 在bean的方法上添加@eventListener注解
下面举例说明
/* 自定义转账事件 */
public class AccountTransferEvent extends ApplicationEvent {
private final int from;
private final int to;
public AccountTransferEvent(int from, int to, Object source) {
super(source);
this.from = from;
this.to = to;
}
}
/* AccountService中 */
@Transactional(isolation = Isolation.DEFAULT, rollbackFor = Exception.class)
@Override
public void transfer(int id1, int id2, int money) {
AccountService accountService = (AccountService) AopContext.currentProxy();
accountService.in(id2, money);
accountService.out(id1, money);
/* 发布事件 */
eventPublisher.publishEvent(new AccountTransferEvent(id1, id2, this));
}
/* 监听事件 */
@Component
public class MyEventListener implements ApplicationListener<AccountTransferEvent> {
@Override
public void onApplicationEvent(AccountTransferEvent event) {
System.out.println("转账了");
}
@EventListener(classes = ContextRefreshedEvent.class)
public void listenContextRefreshEvent(ContextRefreshedEvent event) {
System.out.println("容器刷新");
}
}
除此之外,Spring还支持通过@Order注解按顺序处理事件、Asynchronous监听器等。
通过ResourceLoader接口加载资源
ApplicationContext是一个ResourceLoader,可用于加载资源对象。Spring中的Resource是Java.net.URL类的包装。可以方便的由getResource方法从包括从类路径,文件系统,标准URL等位置加载。
需要注意的一点是在开发过程中可以正常加载类路径下的资源,打成jar包之后会报错,比如以下写法,加载类路径下/img-types目录下的所有文件,开发环境正常,打包情况报错。
@Override
public void afterPropertiesSet() throws Exception {
Resource resource = applicationContext.getResource("classpath:/img-types");
File file = resource.getFile();
for (File f : Objects.requireNonNull(file.listFiles())) {
List<String> imgNames = Files.readAllLines(Paths.get(f.getPath()), StandardCharsets.UTF_8);
imageMap.put(f.getName(), imgNames);
log.debug("put imgs " + resource.getFilename() + " " + imgNames);
}
}
正确的写法,使用ResourcePatternResolver,如下所示
@Controller
@Slf4j
public class RandomImageController implements ApplicationContextAware, InitializingBean {
private ResourcePatternResolver applicationContext;
/* .... */
private final Map<String, List<String>> imageMap = new HashMap<>(4);
/* .... */
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
Resource[] resources = applicationContext.getResources("classpath:/img-types/*");
for (Resource resource : resources) {
List<String> imgNames = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))
.lines().collect(Collectors.toList());
imageMap.put(resource.getFilename(), imgNames);
log.debug("put imgs " + resource.getFilename() + " " + imgNames);
}
}
}
可以实现ResourceLoaderAware接口方便的加载资源。
更多关于Spring Resource的内容可查看官方文档:Resources
Springboot事件监听
Springboot提供了更多事件类型,部分事件在ApplicationContext创建之前,使用上与Spring略有不同,不能使用注入的方式。
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Main.class);
application.setBannerMode(Banner.Mode.LOG);
application.addListeners(new StartListener());
application.run(args);
}
BeanFactory
BeanFactory API为Spring的IOC功能提供了基础。
BeanFactory和相关接口(例如Beanfactoryaware,InitializationBean,DirepsableBean)是其他框架组件的重要集成点。不需要任何注释或甚至反射,它们允许容器与其组件之间非常有效的相互作用。应用程序级别Bean可以使用相同的回调接口。
ApplicationContext实现了BeanFactory接口,在BeanFactory的基础上,提供了丰富的功能。通常情况下,我们应该使用ApplicationContext而不是BeanFactory,除非需要完全控制bean的处理细节。
Q.E.D.