> For the complete documentation index, see [llms.txt](https://docs.flydean.com/spring-framework-documentation5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flydean.com/spring-framework-documentation5/core-technologies/1.the-ioc-container/1.15additional-capabilities-of-the-applicationcontext.md).

# 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端点，该端点可以由同一台计算机上的其他应用程序模块使用。
