Spring 核心(IOC)- 容器的附加功能

    技术2025-01-08  45

    目录

    容器的附加功能1. 使用MessageSource国际化2. 标准和自定义事件*3. 基于注解的事件监听*4. web应用中方便的实例化容器上下文

    容器的附加功能

    为了以更面向框架的风格来增强BeanFactory的功能,上下文包还提供了以下功能:

    通过MessageSource接口访问i18n(国际化)风格的信息。通过ResourceLoader接口访问资源,如URL和文件。通过使用ApplicationEventPublisher接口的发布器把事件通知给实现ApplicationListener接口的Bean监听器,来达到事件发布。通过HierarchicalBeanFactory接口加载多个上下文,并让每一个上下文都关注于一个特定的层,如web层。
    1. 使用MessageSource国际化

    通过实现MessageSource接口定制国际化功能(也可以使用Spring已提供的实现类),Spring还提供了其子接口HierarchicalMessageSource,该子接口可以分层地解析消息。这两个接口一起提供了Spring实现消息解析的基础。

    MessageSource接口定义了三个重载方法:

    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

    其参数含义为:

    code:要获取的消息字符串。args:一个参数数组,用于给消息中的参数中进行填充。defaultMessage:在没发现消息情况下返回设置的默认消息。locale:本地化。resolvable:集成包含code、args、defaultMessage这三个参数。

    当容器初始化时,会先去搜索有无定义的MessageSource类型的Bean在容器中,其Bean名称必须为messageSource。如果发现了此Bean,会将前面三个方法委托给此消息源调用;若没有发现,则会继续搜索包含同名Bean的父消息源,若还是没有发现,则容器会实例化一个空的DelegatingMessageSource,以便能调用上述方法。

    Spring总共提供了4个MessageSource实现类:ResourceBundleMessageSource、StaticMessageSource、DelegatingMessageSource和ReloadableResourceBundleMessageSource。它们都实现了HierarchicalMessageSource接口以执行嵌套消息传递。最常用的则是ResourceBundleMessageSource实现类。

    假设我们准备了以下国际化资源文件:

    # myi18n.properties name=default my name is {0} # myi18n_en_US.properties name=english my name is {0} # myi18n_zh_CN.properties name=chinese my name is {0}

    然后我们在配置类中注册MessageSourceBean,显然,我们得使用ResourceBundleMessageSource实现类。

    @Configuration public class MyConfig { @Bean("messageSource") public MessageSource ResourceBundleMessageSource(){ //注意:引用类型是ResourceBundleMessageSource,否则无法使用下面的方法 ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); //添加单个或多个国际化资源文件,通过基名。 messageSource.setBasenames("myi18n"); return messageSource; } }

    由于ApplicationContext接口是MessageSource接口的子接口,则我们可以直接通过容器上下文对象调用上述三个方法。

    public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class); //获取zh_CN地区的消息 String message = ac.getMessage("name", new Object[]{"张三"},"default", Locale.SIMPLIFIED_CHINESE); //打印:chinese my name is 张三 System.out.println(message); }

    你也可以通过实现MessageSourceAware接口来获取已定义的任何MessageSource引用,使用方法和ApplicationContextAware一致,在Bean初始化时,任何实现xxxAware接口的Bean都会被注入相应的引用。

    ResourceBundleMessageSource还有其它替代方案,Spring提供了ReloadableResourceBundleMessageSource实现类,它同样支持bundle文件格式,但是比基于JDK标准的ResourceBundleMessageSource更加灵活。它还能读取外部文件(不仅仅是类路径下),此外,它还支持bundle属性文件的热加载。

    2. 标准和自定义事件*

    容器中的事件发布依托ApplicationEventPublisher接口,而容器中的事件创建和监听则分别是通过ApplicationEvent抽象类和ApplicationListener接口实现的。如果一个Bean实现ApplicationListener接口并且注册到容器中,那么每一个ApplicationEvent实现类被发布到容器中时(即触发事件然后进行发布),该Bean都会得到通知并回调相应方法。在设计模式中这是典型的观察者模式。

    自Spring4.2后,关于事件基础方面的功能已得到改善,并加入了基于注解策略以及发布任意事件的能力,无需再继承其ApplicationEvent。

    关于Spring所提供的标准事件:

    事件描述ContextRefreshedEvent当容器初始化或刷新时发布事件(刷新指容器上下文对象的refresh()方法),只要容器上下文对象未关闭,可重复刷新。但部分容器上下文对象不支持刷新功能。ContextStartedEvent当容器上下文对象使用start()方法显式的启动时发布事件,一般用于容器停止后再重新启动。ContextStoppedEvent当容器上下文对象使用stop()方法显式的停止时发布事件。所有生命周期内的Bean都将会接收一个停止的信号。ContextClosedEvent当容器上下文对象使用close()方法时发布事件。容器关闭,销毁所有单例Bean,并且无法重新开启和刷新。RequestHandledEvent应用于web中,当请求完成后发布事件。ServletRequestHandledEventRequestHandledEvent子类,用于添加特定的servlet上下文信息。

    你也可以创建和发布自定义事件。通过继承ApplicationEvent实现自定义事件。

    public class AddUserEvent extends ApplicationEvent { private final int id; private final String name; public AddUserEvent(Object source, int id, String name) { super(source); this.id = id; this.name = name; } }

    然后由ApplicationEventPublisherAware 获取其事件发布对象引用,再调用publishEvent()方法即可完成自定义事件发布。

    @Service public class UserdService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void AddUser(User user) { if (// ...) { //事件发布 publisher.publishEvent(new AddUserEvent(this, user.getId(), user.getName())); // Add User... } } }

    最后通过注册实现ApplicationListener接口的Bean监听接收事件。

    @Component public class AddUserEventListener implements ApplicationListener<AddUserEvent> { public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }

    Spring的事件机制是为同一容器上下文中的Bean之间的简单通讯而设计的。然而,在大型且复杂的企业级需求下,会使用单独维护的项目,它有着更完整更轻量的支持,且往往在Spring构建之上。

    3. 基于注解的事件监听*

    通过方法级的@EventListener注解注册事件监听器,并且不再需要实现ApplicationListener接口。

    @Component public class AddUserEventListener { @EventListener public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }

    一般情况下,@EventListener注解通过其方法参数匹配对应事件。当然,也可以通过其注解属性指定单个或多个事件类型。

    @Component public class AddUserEventListener { @EventListener({AddUserOneEvent.class, AddUserTwoEvent.class}) public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }

    甚至通过SpEL表达式进行逻辑匹配。

    @Component public class AddUserEventListener { @EventListener(condition = "#event.content == 'my-event'") public void onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... } }

    请注意,#root.event允许你访问底层事件,及时你方法参数中实际引用了任意的事件对象。

    具体SpEL表达式的使用请参阅官方文档

    你也可以通过这个监听器为跳板,发布下一个或多个事件集合。

    @Component public class AddUserEventListener { @EventListener public OtherEvent onApplicationEvent(AddUserEvent event) { // 处理方法逻辑..... //发布其他事件 return new OtherEvent(...); } }

    此外,你还可以添加@Async和@Order注解实现异步事件监听和事件监听排序。

    使用异步事件监听需要注意的问题:

    若异步事件监听出现异常,它不会响应给调用者。异步事件监听不支持通过返回值发布后续事件。
    4. web应用中方便的实例化容器上下文

    你可以使用声明的方式实例化容器上下文,如ContextLoader。也可以使用ApplicationContext编程地实例化一个ApplicationContext实例。

    通过ContextLoaderListener注册一个ApplicationContext。

    <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>

    ContextLoaderListener通过检查上下文参数,若参数不存在,则默认采用/WEB-INF/applicationContext.xml路径的配置文件;若存在,则采用其参数中的一个或多个配置文件(空格、分号和逗号区分),并且路径还支持Ant风格。

    Processed: 0.013, SQL: 9