Spring学习笔记10_ApplicationContext的附加功能(Additional Capabilities of the ApplicationContext)

文章参考来源:Spring Framework官方文档
前言:
org.springframework.beans.factory包提供了管理和操作bean的基本功能,包括以编程的方式
org.springframework.context包添加了ApplicationContext接口,它扩展了BeanFactory接口,此外还扩展了其他接口,以一种更面向应用程序框架的风格提供额外的功能。
许多人以一种完全声明的方式使用ApplicationContext,甚至不是通过编程来创建它,而是依赖于像ContextLoader这样的支持类来自动实例化ApplicationContext,作为Java EE web应用程序正常启动过程的一部分。
为了以更面向框架的方式增强BeanFactory功能,上下文包还提供了以下功能:
(1)通过MessageSource接口以i18n风格访问消息。
(2)通过ResourceLoader接口访问资源,例如url和文件。
(3)事件发布,即通过使用ApplicationEventPublisher接口发布到实现ApplicationListener接口的bean。
(4)通过HierarchicalBeanFactory接口加载多个(分层)上下文,让每个上下文都集中在一个特定的层上,比如应用程序的web层。

ApplicationContext源码如下:

package org.springframework.context;

import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, 
HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
    @Nullable
    String getId();

    String getApplicationName();

    String getDisplayName();

    long getStartupDate();

    @Nullable
    ApplicationContext getParent();

    AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}

1. 使用MessageSource国际化

从ApplicationContext源码看,ApplicationContext接口继承了一个名为MessageSource的接口,因此提供了国际化(“i18n”)功能。Spring还提供了HierarchicalMessageSource接口,该接口可以按层次结构解析消息。这些接口一起提供了Spring实现消息解析的基础。在这些接口上定义的方法包括:

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource检索消息的基本方法。如果没有为指定的区域设置找到消息,则使用默认消息。通过使用标准库提供的MessageFormat功能,传入的任何参数都成为替换值。
  • String getMessage(String code, Object[] args, Locale loc):本质上与前面的方法相同,但有一点不同:不能指定默认消息。如果找不到消息,则抛出NoSuchMessageException。
  • String getMessage(MessageSourceResolvable resolvable, Locale Locale):上述方法中使用的所有属性也被包装在一个名为MessageSourceResolvable的类中,可以与此方法一起使用。

当加载ApplicationContext时,它会自动搜索上下文中定义的MessageSource bean。bean的名称必须是messageSource。如果找到了这样的bean,那么对上述方法的所有调用都将委托给消息源。如果没有找到消息源,ApplicationContext尝试查找包含同名bean的父类。如果是,则使用该bean作为MessageSource。如果ApplicationContext不能找到任何消息源,则实例化一个空的DelegatingMessageSource,以便能够接受对上面定义的方法的调用。

Spring提供了三种MessageSource实现,ResourceBundleMessageSource,ReloadableResourceBundleMessageSource和StaticMessageSource。它们都实现了HierarchicalMessageSource以实现嵌套消息传递。很少使用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>

以上例子中,假设在类路径中定义了三个资源包,分别是format、exceptions和windows。任何解析消息的请求都以通过ResourceBundle对象解析消息的jdk标准方式处理。为了便于示例,假设上述两个资源包文件的内容如下所示:
format.properties文件

    message=Alligators rock!

exceptions.properties

argument.required=The {0} argument is required.

以下示例显示了运行MessageSource功能的程序。请记住,所有ApplicationContext实现也是MessageSource实现,因此可以转换为MessageSource接口:

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
    System.out.println(message);
}

上述程序的结果输出如下:

Alligators rock!

2. 标准和自定义事件

ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。如果实现ApplicationListener接口的bean被部署到上下文中,那么每当ApplicationEvent被发布到ApplicationContext时,就会通知该bean。本质上,这是标准的Observer设计模式。
Spring 4.2起,事件的发布流程有了显著改进,提供了基于注释的模型以及发布任意事件(即不必从ApplicationEvent扩展的对象)的能力。

以下给出了Spring提供的标准事件:

事件描述
ContextRefreshedEvent当初始化或刷新ApplicationContext时发布(例如,通过使用ConfigurableApplicationContext接口上的refresh()方法)。在这里,“initialized”意味着加载完所有bean,检测并激活后处理器bean,预实例化单例,并且ApplicationContext对象已经准备好可以使用了。只要上下文没有被关闭,刷新就可以被触发多次,前提是所选的ApplicationContext实际上支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持。
ContextStartedEvent通过使用ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时发布。这里,“started”意味着所有生命周期bean都接收一个显式的启动信号。通常,这个信号用于在显式停止后重新启动bean,但它也可以用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。
ContextStoppedEvent在使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时发布。这里,“stopped”意味着所有生命周期bean都接收一个显式的停止信号。可以通过调用start()重新启动已停止的上下文。
ContextClosedEvent通过使用ConfigurableApplicationContext接口上的close()方法或通过JVM关机hook关闭ApplicationContext时发布。在这里,“关闭”意味着所有单例bean将被销毁。一旦上下文关闭,它就会到达生命的终点,无法刷新或重新启动。
RequestHandledEvent一个特定于web的事件,告诉所有bean一个HTTP请求已经得到服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的web应用程序。
ServletRequestHandledEventRequestHandledEvent的一个子类,用于添加特定于servlet的上下文信息。

当然,也可以创建和发布自己的自定义事件,如下:

public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // accessor and other methods...
}

要发布自定义的ApplicationEvent,可以调用ApplicationEventPublisher上的publishEvent()方法。
通常,是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为Spring bean来完成的。下面的例子展示了这样一个类:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List<String> blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // send email...
    }
}

在配置阶段,Spring容器检测到实现了ApplicationEventPublisherAware接口的EmailService,并自动调用其setApplicationEventPublisher()方法。实际上,传入的参数是Spring容器本身。我们是通过ApplicationEventPublisher接口与application context 进行交互。

要接收自定义的ApplicationEvent,可以创建一个实现ApplicationListener的类,并将其注册为Spring bean。如下:

public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

基于注解的事件监听器

Spring 4.2开始,可以使用@EventListener注解在被容器管理的bean的任何公共方法上注册事件监听器,之前的BlockedListNotifier 可以改成:

public class BlockedListNotifier {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    @EventListener
    public void processBlockedListEvent(BlockedListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

如果希望在方法上应该侦听多个事件,或者希望在不使用任何参数的情况下定义它,可这样使用:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}

甚至可以通过SpEL表达式来定义监听条件:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

异步监听器

如果需要一个特定的监听器异步处理事件,可以重用常规的@Async支持:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}

在使用异步事件监听器时,要注意以下限制:

  • 如果异步事件监听器抛出异常,它不会传播到调用者。有关更多细节,请参阅 AsyncUncaughtExceptionHandler
  • 异步事件监听器方法不能通过返回值来发布后续事件。
  • 如果您需要发布另一个事件作为处理的结果,注入ApplicationEventPublisher来手动发布事件。

有序监听器

可以先调用一个监听器,在方法声明中添加@Order注释:

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}

通用事件监听器

还可以使用泛型进一步定义事件的结构,考虑使用EntityCreatedEvent<T> ,其中T是创建的实际实体的类型。例如,可以创建以下侦听器定义来只为Person类接收EntityCreatedEvent:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    // ...
}

在某些情况下,如果所有事件都遵循相同的结构(前面示例中的事件也应该如此),那么这可能会变得非常冗长乏味。
在这种情况下,可以实现ResolvableTypeProvider来指导运行时环境所提供的框架:

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()));
    }
}

这不仅适用于ApplicationEvent,也适用于任何作为事件发送的对象。

3. 便利地访问底层资源

应用程序 context 是一个ResourceLoader,可用于加载Resource资源对象。Resource实质上是JDK java.net.URL类的功能更丰富版本。事实上,Resource的实现在适当的时候包装了一个java.net.URL的实例。
Resource可以以透明的方式从几乎任何位置获取底层资源,包括类路径、文件系统位置、可用标准URL描述的任何位置,以及其他一些变体。如果resource位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应用程序context类型。
提供给ApplicationContext构造方法的位置路径或路径实际上是资源字符串,例如,ClassPathXmlApplicationContext将简单的位置路径视为类路径位置。还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL加载定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值