# 1.15 ApplicationContext的其他功能

正如在引言一章中所讨论的，org.springframework.beans.factory包提供了管理和操作bean的基本功能，包括以编程方式。context包添加了ApplicationContext接口，它扩展了BeanFactory接口，此外还扩展了其他接口，以提供更多面向应用程序框架的功能。许多人以完全声明的方式使用ApplicationContext，甚至不是以编程方式创建应用程序上下文，而是依赖于诸如ContextLoader这样的支持类来自动实例化应用程序上下文，作为JavaEE Web应用程序正常启动过程的一部分。

为了以更面向框架的方式增强BeanFactory功能，上下文包还提供以下功能：

* 通过MessageSource接口访问i18n样式的消息。
* 通过ResourceLoader接口访问资源，如URL和文件。
* 事件发布，即通过使用applicationEventPublisher接口向实现ApplicationListener接口的bean发布。
* 通过HierarchicalBeanFactory接口加载多个（层次）上下文，使每个上下文集中在一个特定的层上，例如应用程序的Web层。

## 1.15.1 MessageSource国际化

ApplicationContext接口扩展了一个名为MessageSource的接口，因此提供国际化（“i18n”）功能。Spring还提供了层次化的消息源接口，可以对消息进行层次化的解析。这些接口共同提供了Spring效应消息解决的基础。在这些接口上定义的方法包括：

* String getMessage(String code, Object\[] args, String default, Locale loc):从MessageSource获取消息的基本方法，如果在提供的locale中没有找到消息，那么将使用默认的消息。通过使用标准库提供的MessageFormat功能，传入的任何参数都将成为替换值。
* String getMessage(String code, Object\[] args, Locale loc):基本上和上面方法一样，但是有一个区别：没有默认小消息。如果消息没找到，那么NoSuchMessageException将抛出。
* String getMessage(MessageSourceResolvable resolvable, Locale locale): 所有的之前方法的属性，都被封装在名为MessageSourceResolvable的类中，你可以使用它的方法。

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

Spring提供了两个MessageSource实现，ResourceBundleMessageSource和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标准方式处理。在本例中，假设上述两个资源包文件的内容如下：

```
# in format.properties
message=Alligators rock!

# in 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", null);
    System.out.println(message);
}
```

运行结果如下：

```
Alligators rock!
```

总而言之，MessageSource是在一个名为beans.xml的文件中定义的，该文件位于类路径的根目录下。MessageSource bean定义通过其basename属性引用许多资源束。在列表中传递给basename属性的三个文件作为文件存在于类路径的根目录中，分别称为format.properties、exceptions.properties和windows.properties。

下一个示例显示传递给消息查找的参数。这些参数被转换成字符串对象并插入到查找消息中的占位符中。

```
<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.something.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);
    }
}
```

execute()的执行结果如下：

```
The userDao argument is required.
```

关于国际化（“i18n”），Spring的各种消息源实现遵循与标准JDK ResourceBundle相同的区域设置解析和回退规则。简而言之，继续前面定义的示例MessageSource，如果要针对英国（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.
```

你还可以使用MessageSourceAware接口获取对已定义的任何MessageSource的引用。在ApplicationContext中定义的、实现MessageSourceAware接口的任何bean在创建和配置bean时都会与应用程序上下文的MessageSource一起注入。

> 作为ResourceBundleMessageSource的替代方案，Spring提供了一个ReloadableResourceBundleMessageSource类。此变体支持相同的包文件格式，但比基于JDK的标准ResourceBundleMessageSource实现更灵活。特别是，它允许从任何Spring资源位置（不仅是从类路径）读取文件，并支持捆绑属性文件的热重新加载（同时有效地在两者之间缓存它们）。有关详细信息，请参阅ReloadableResourceBundleMessageSource javadoc。

## 1.15.2 标准和自定义Events

ApplicationContext中的事件处理通过ApplicationEvent类和ApplicationListener接口提供。如果实现ApplicationListener接口的bean部署到上下文中，则每次ApplicationEvent发布到ApplicationContext时，都会通知该bean。本质上，这是标准的观测者设计模式。

> 从Spring4.2开始，事件基础结构得到了显著的改进，提供了一个基于注解的模型以及发布任意事件的能力（也就是说，不一定从ApplicationEvent扩展的对象）。当这样一个对象被发布时，我们将它包装在一个事件中。

下表描述了Spring提供的标准事件描述：

| Event                 | 解释                                                                                                                                                                                                                                                                              |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ContextRefreshedEvent | 在初始化或刷新ApplicationContext时发布（例如，通过在ConfigurableApplicationContext接口上使用refresh（）方法）。这里，“初始化”意味着加载所有bean，检测并激活后处理器bean，预先实例化单例，并且ApplicationContext对象准备好使用。只要上下文未关闭，只要所选的ApplicationContext实际上支持此类“热”刷新，就可以多次触发刷新。例如，XMLWebApplicationContext支持热刷新，但GenericApplicationContext不支持。 |
| ContextStartedEvent   | 在可配置的ApplicationContext接口上使用start（）方法启动ApplicationContext时发布。这里，“启动”意味着所有生命周期bean都会收到一个显式的启动信号。通常，此信号用于在显式停止后重新启动bean，但也可以用于启动尚未配置为自动启动的组件（例如，初始化时尚未启动的组件）。                                                                                                                     |
| ContextStoppedEvent   | 在可配置的ApplicationContext接口上使用stop（）方法停止ApplicationContext时发布。这里，“停止”意味着所有生命周期bean都会收到一个明确的停止信号。停止的上下文可以通过start（）调用重新启动。                                                                                                                                                          |
| ContextClosedEvent    | 在可配置的ApplicationContext接口上使用close（）方法关闭ApplicationContext时发布。这里，“关闭”意味着所有的单例beans都被销毁了。封闭的环境达到了生命的尽头。无法刷新或重新启动。                                                                                                                                                                 |
| RequestHandledEvent   | 一个特定于Web的事件，告诉所有bean HTTP请求已被服务。此事件在请求完成后发布。此事件仅适用于使用Spring的DispatcherServlet的Web应用程序。                                                                                                                                                                                          |

你也可以创建自己的事件。下面显示了继承ApplicationEvent的例子：

```
public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

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

    // accessor and other methods...
}
```

若要发布自定义ApplicationEvent，请在ApplicationEventPublisher上调用PublishEvent（）方法。通常，这是通过创建一个实现ApplicationEventPublisherAware的类并将其注册为SpringBean来完成的。下面的示例显示了这样的类：

```
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 content) {
        if (blackList.contains(address)) {
            publisher.publishEvent(new BlackListEvent(this, address, content));
            return;
        }
        // send email...
    }
}
```

在配置时，Spring容器检测到EmailService实现了ApplicationEventPublisherAware，并自动调用setApplicationEventPublisher（）。实际上，传入的参数是Spring容器本身。你正在通过其applicationEventPublisher接口与应用程序上下文进行交互。

要接收定制的applicationEvent，可以创建一个实现applicationListener的类，并将其注册为SpringBean。下面的示例显示了这样的类：

```
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）参数化的。这意味着onApplicationEvent（）方法可以保持类型安全，避免任何向下强制转换的需要。你可以注册任意多个事件侦听器，但请注意，默认情况下，事件侦听器同步接收事件。这意味着publishEvent（）方法将一直阻塞，直到所有侦听器完成对事件的处理。这种同步和单线程方法的一个优点是，当侦听器接收到事件时，如果事务上下文可用，它将在发布服务器的事务上下文中操作。如果需要事件发布的另一个策略，请参阅JavaDoc for Spring的ApplicationEventMulticaster接口。

下面的示例显示了用于注册和配置上述每个类的bean定义：

```
<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="blacklist@example.org"/>
</bean>
```

综上所述，当调用emailService bean的sendEmail（）方法时，如果有任何电子邮件应被列入黑名单，则会发布一个blacklistevent类型的自定义事件。黑名单通知bean注册为ApplicationListener并接收黑名单事件，此时它可以通知相应的方。

> Spring的事件机制是为同一应用程序上下文中SpringBean之间的简单通信而设计的。然而，对于更复杂的企业集成需求，单独维护的Spring Integration为构建基于众所周知的Spring编程模型的轻量级、面向模式、事件驱动的体系结构提供了完整的支持。

**基于注解的Event Listeners**

从Spring4.2开始，你可以使用EventListener注解在托管bean的任何公共方法上注册事件侦听器。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() {
    ...
}
```

还可以使用定义spEL表达式的注解的条件属性添加其他运行时筛选，该表达式应与实际调用特定事件的方法相匹配。

以下示例显示了如何重写通知程序，以便仅在事件的内容属性等于我的事件时调用：

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

每个spEL表达式都根据专用上下文进行计算。下表列出了上下文可用的项，以便你可以将它们用于条件事件处理：

| name            | Location           | 描述                                                                             | 例子                                      |
| --------------- | ------------------ | ------------------------------------------------------------------------------ | --------------------------------------- |
| Event           | root object        | 真实的ApplicationEvent                                                            | #root.event                             |
| Arguments array | root object        | 调用目标的参数                                                                        | #root.args\[0]                          |
| Argument name   | evaluation context | 任何方法参数的名称。如果由于某种原因，名称不可用（例如，因为没有调试信息），参数名称也可以在 #a<#arg>下使用，其中#arg表示参数索引（从0开始）。 | #blEvent or #a0 (也可以使用 #p0 or #p<#arg>) |

请注意，root.event允许你访问基础事件，即使你的方法签名实际上引用了已发布的任意对象。

如果在处理另一个事件时需要发布事件，可以更改方法签名以返回应发布的事件，如下例所示：

```
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}
```

> 异步Listener不支持整个特性

此新方法为上述方法处理的每个BlackListEvent发布一个新的ListUpdateEvent。如果需要发布多个事件，可以返回事件集合。

**异步侦听器**

如果希望特定的侦听器异步处理事件，可以重用常规的@Async支持。以下示例显示了如何执行此操作：

```
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
    // BlackListEvent is processed in a separate thread
}
```

注意下面的异步事件限制：

* 如果事件listener抛出Exception，异常并不会传递给调用者。参考AsyncUncaughtExceptionHandler。
* 这样的事件listener不能发送回复。作为listener的结果，如果你想发送另外的事件，调用ApplicationEventPublisher来手动发送。

**Listeners排序**

如果需要先调用一个监听器，然后再调用另一个监听器，则可以将@order注解添加到方法声明中，如下例所示：

```
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress...
}
```

**一般事件**

你还可以使用泛型进一步定义事件的结构。考虑使用EntityCreatedEvent，其中t是创建的实际实体的类型。例如，你可以创建以下侦听器定义以仅接收某个人的EntityCreatedEvent：

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

由于类型擦除，只有在激发的事件解析事件侦听器筛选的通用参数（比如类PersonCreatedEvent扩展EntityCreatedEvent…）时，此操作才有效。

在某些情况下，如果所有事件都遵循相同的结构（如前一个示例中的事件一样），则这可能会变得相当乏味。在这种情况下，你可以实现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，还适用于作为事件发送的任意对象。

## 1.15.3 方便获取低级资源

为了优化应用程序上下文的使用和理解，你应该熟悉Spring的资源抽象，如参考资料中所述。

应用程序上下文是可用于加载资源对象的ResourceLoader。资源本质上是JDK java.net.URL类的功能更丰富的版本。实际上，资源的实现在适当的情况下包装了java.net.url的实例。Resource可以以透明的方式从几乎任何位置获取低级资源，包括从类路径、文件系统位置、使用标准URL可描述的任何位置以及其他一些变体。如果资源位置字符串是没有任何特殊前缀的简单路径，那么这些资源的来源是特定的，并且适合于实际的应用程序上下文类型。

可以将部署到应用程序上下文中的bean配置为实现特殊的回调接口ResourceLoaderAware，以便在初始化时自动回调，并将应用程序上下文本身作为ResourceLoader传入。你还可以公开资源类型的属性，以用于访问静态资源。它们像其他任何性质一样被注入其中。你可以将这些资源属性指定为简单的字符串路径，并在部署bean时依赖于从这些文本字符串到实际资源对象的自动转换。

提供给ApplicationContext构造函数的一个或多个位置路径实际上是资源字符串，并且以简单的形式根据特定的上下文实现进行适当的处理。例如，ClassPathXMLApplicationContext将简单位置路径视为类路径位置。也可以使用带有特殊前缀的位置路径（资源字符串）强制从类路径或URL加载定义，而不管实际的上下文类型如何。

## 1.15.4 方便的Web应用程序应用上下文实例化

可以通过使用ContextLoader等声明性地创建ApplicationContext实例。当然，你也可以使用ApplicationContext实现之一以编程方式创建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>
```

侦听器检查ContextConfigLocation参数。如果参数不存在，侦听器将使用/WEB-INF/ApplicationContext.xml作为默认值。当参数确实存在时，侦听器使用预定义的分隔符（逗号、分号和空格）分隔字符串，并将这些值用作搜索应用程序上下文的位置。还支持Ant-style 的路径模式。例如/WEB-INF/\_context.xml（对于名称以context.xml结尾并位于WEB-INF目录中的所有文件）和/WEB-INF/\*\*/\_context.xml（对于WEB-INF的任何子目录中的所有此类文件）。

## 1.15.5将ApplicationContext部署为Java EE RAR文件

可以将Spring ApplicationContext作为RAR文件部署，在JavaEE RAR部署单元中封装上下文及其所有所需的bean类和库JAR。这相当于引导一个独立的ApplicationContext（仅托管在JavaEE环境中）能够访问JavaEE服务器设施。RAR部署是一种更为自然的替代方案，用于部署一个headless WAR文件——实际上，一个没有任何HTTP入口点的WAR文件，它仅用于在JavaEE环境中引导Spring ApplicationContext。

RAR部署非常适合不需要HTTP入口点，而是只包含消息端点和计划作业的应用程序上下文。在这种上下文中，bean可以使用应用服务器资源，如jta事务管理器和jndi绑定的jdbc DataSource实例和jms ConnectionFactory实例，还可以通过Spring的标准事务管理和jndi和jmx支持设施在平台的jmx服务器-注册。应用程序组件还可以通过Spring的TaskExecutor抽象与应用服务器的JCA WorkManager交互。

请参阅SpringContextResourceAdapter类的JavaDoc，了解RAR部署中涉及的配置详细信息。

对于Spring ApplicationContext作为JavaEE RAR文件的简单部署：

1. 将所有应用程序类打包成一个rar文件（这是一个具有不同文件扩展名的标准jar文件）。将所有必需的库jar添加到rar存档的根目录中。.添加一个META-INF/ra.xml部署描述符（如SpringContextResourceAdapter的JavaDoc中所示）和相应的SpringXML bean定义文件（通常为'META-INF/applicationContext.xml）。
2. 将生成的rar文件放到应用程序服务器的部署目录中。

> 这种RAR部署单元通常是独立的。它们不向外部世界公开组件，甚至不向同一应用程序的其他模块公开组件。与基于rar的ApplicationContext的交互通常通过与其他模块共享的JMS目标进行。例如，基于rar的ApplicationContext还可以调度一些作业或对文件系统（或类似系统）中的新文件做出反应。如果需要允许从外部进行同步访问，它可以（例如）导出RMI端点，该端点可以由同一台计算机上的其他应用程序模块使用。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/1.the-ioc-container/1.15additional-capabilities-of-the-applicationcontext.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
