Appearance
Spring 核心:IoC 容器 - Part5
Tip:基于 Spring Core 5.3.30 版本。
1. ApplicationContext 的附加功能
正如Spring IoC 容器和 Bean 简介中讨论的,org.springframework.beans.factory 包提供了管理和操作 Bean 的基本功能,包括以编程方式。org.springframework.context 包增加了 ApplicationContext 接口,它扩展了 BeanFactory 接口,同时还扩展了其他接口,以提供更多面向应用程序框架风格的功能。许多人完全以声明式的方式使用 ApplicationContext,甚至不是以编程方式创建它,而是依赖于像 ContextLoader 这样的支持类,在 Java EE Web 应用程序的正常启动过程中自动实例化一个 ApplicationContext。
为了以更面向框架的风格增强 BeanFactory 的功能,context 包还提供了以下功能:
通过
MessageSource接口以 i18n 风格访问消息;通过
ResourceLoader接口访问资源,例如 URL 和文件;通过使用
ApplicationEventPublisher接口,向实现ApplicationListener接口的 Bean 发布事件;通过
HierarchicalBeanFactory接口加载多个(层次化的)上下文,让每个上下文专注于一个特定层,例如应用程序的 Web 层;
1.1. 使用 MessageSource 进行国际化
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 的 MessageSource Bean。如果找到了这样的 Bean,所有对前述方法的调用都会委托给消息源。如果找不到消息源,ApplicationContext 会尝试找到包含同名 Bean 的父对象。如果找到了,它就会使用那个 Bean 作为 MessageSource。如果 ApplicationContext 找不到任何消息源,那么就会实例化一个空的 DelegatingMessageSource,以便能够接受对上述方法的调用。
Spring 提供了三种 MessageSource 实现,分别是 ResourceBundleMessageSource、ReloadableResourceBundleMessageSource 和 StaticMessageSource。它们都实现了 HierarchicalMessageSource,以便进行嵌套消息传递。StaticMessageSource 很少使用,但提供了以编程方式向源添加消息的方法。下面的示例展示了 ResourceBundleMessageSource:
XML
<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>1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
该示例假设你在类路径中定义了三个名为 format、exceptions 和 windows 的资源包。任何解析消息的请求都是通过 ResourceBundle 对象以 JDK 标准方式处理的。为了说明这个示例,假设上述两个资源包文件的内容如下:
format.properties:Propertiesmessage=Alligators rock!exceptions.properties:Propertiesargument.required=The {0} argument is required.
下一个示例展示了一个运行 MessageSource 功能的程序。请记住,所有的 ApplicationContext 实现也都是 MessageSource 的实现,因此可以被强制转换为 MessageSource 接口。
Java
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);
}1
2
3
4
5
2
3
4
5
上述程序的输出结果如下:
Text
Alligators rock!总的来说,MessageSource 是在一个名为 beans.xml 的文件中定义的,该文件位于你的类路径的根目录。messageSource Bean 定义通过其 basenames 属性引用了一些资源包。传递给 basenames 属性列表中的三个文件作为类路径根目录下的文件存在,分别称为 format.properties、exceptions.properties 和 windows.properties。
下一个示例展示了传递给消息查找的参数。这些参数被转换为 String 对象,并插入到查找消息的占位符中。
XML
<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>1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Java
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", Locale.ENGLISH);
System.out.println(message);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
调用 execute() 方法的输出结果如下:
Text
The userDao argument is required.关于国际化(“i18n”),Spring 的各种 MessageSource 实现遵循与标准 JDK ResourceBundle 相同的语言环境解析和回退规则。简单来说,继续以前面定义的 messageSource 为例,如果你想针对英国(en-GB)语言环境解析消息,你需要创建名为 format_en_GB.properties、exceptions_en_GB.properties 和 windows_en_GB.properties 的文件。
通常,语言环境解析是由应用程序的周围环境管理的。在下面的示例中,手动指定了要解析的(英国)消息的语言环境:
exceptions_en_GB.properties:Propertiesargument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
Java
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);
}1
2
3
4
5
6
2
3
4
5
6
运行上述程序的输出结果如下:
Text
Ebagum lad, the 'userDao' argument is required, I say, required.你也可以使用 MessageSourceAware 接口来获取对任何已定义的 MessageSource 的引用。在 ApplicationContext 中定义的任何实现了 MessageSourceAware 接口的 Bean,在创建和配置 Bean 时,都会注入应用程序上下文的 MessageSource。
Note:因为 Spring 的
MessageSource是基于 Java 的ResourceBundle,所以它不会合并具有相同基本名称的包,而只会使用找到的第一个包。具有相同基本名称的后续消息包将被忽略。
Tip:作为
ResourceBundleMessageSource的替代方案,Spring 提供了一个ReloadableResourceBundleMessageSource类。这个变体支持相同的包文件格式,但比基于标准 JDK 的ResourceBundleMessageSource实现更灵活。特别是,它允许从任何 Spring 资源位置(不仅仅是类路径)读取文件,并支持热重载包属性文件(同时在其中有效地缓存它们)。详情请参阅ReloadableResourceBundleMessageSource的 JavaDoc。
1.2. 标准和自定义事件
ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果一个实现了 ApplicationListener 接口的 Bean 被部署到上下文中,每次 ApplicationEvent 被发布到 ApplicationContext 时,该 Bean 都会收到通知。本质上,这就是标准的观察者设计模式。
Note:从 Spring 4.2 开始,事件基础设施得到了显著改进,提供了基于注解的模型,以及发布任意事件(即,不一定要从
ApplicationEvent扩展的对象)的能力。当这样的对象被发布时,我们会为你将它包装在一个事件中。
下方描述了 Spring 提供的标准事件:
ContextRefreshedEvent:当ApplicationContext初始化或刷新时发布(例如,通过在ConfigurableApplicationContext接口上使用refresh()方法)。在这里,“initialized” 意味着所有的 Bean 都已加载,后处理器 Bean 被检测并激活,单例被预实例化,ApplicationContext对象已准备好使用。只要上下文没有被关闭,就可以多次触发刷新,前提是所选的ApplicationContext实际上支持这样的 “热” 刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持;ContextStartedEvent:当通过在ConfigurableApplicationContext接口上使用start()方法启动ApplicationContext时发布。在这里,“started” 意味着所有的LifecycleBean 接收到一个明确的启动信号。通常,这个信号用于在明确停止后重新启动 Bean,但也可以用于启动没有配置为自动启动的组件(例如,初始化时还没有启动的组件);ContextStoppedEvent:当通过在ConfigurableApplicationContext接口上使用stop()方法停止ApplicationContext时发布。在这里,“stopped” 意味着所有的LifecycleBean 接收到一个明确的停止信号。一个停止的上下文可以通过start()调用重新启动;ContextClosedEvent:当通过在ConfigurableApplicationContext接口上使用close()方法或通过 JVM 关闭钩子关闭ApplicationContext时发布。在这里,“closed” 意味着所有的单例 Bean 将被销毁。一旦上下文被关闭,它就到达了生命周期的终点,不能被刷新或重新启动;RequestHandledEvent:一个特定于 Web 的事件,告诉所有的 Bean 一个 HTTP 请求已经被处理。这个事件在请求完成后发布。这个事件只适用于使用 Spring 的DispatcherServlet的 Web 应用程序;ServletRequestHandledEvent:RequestHandledEvent的一个子类,添加了特定于 Servlet 的上下文信息;
你也可以创建并发布你自己的自定义事件。下面的示例展示了一个简单的类,它扩展了 Spring 的 ApplicationEvent 基类:
Java
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...
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
要发布一个自定义的 ApplicationEvent,在 ApplicationEventPublisher 上调用 publishEvent() 方法。通常,这是通过创建一个实现了 ApplicationEventPublisherAware 的类并将其注册为 Spring Bean 来完成的。下面的示例展示了这样一个类:
Java
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...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在配置时,Spring 容器检测到 EmailService 实现了 ApplicationEventPublisherAware,并自动调用 setApplicationEventPublisher()。实际上,传入的参数就是 Spring 容器本身。你是通过它的 ApplicationEventPublisher 接口与应用程序上下文进行交互的。
要接收自定义的 ApplicationEvent,你可以创建一个实现了 ApplicationListener 的类并将其注册为 Spring Bean。下面的示例展示了这样一个类:
Java
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...
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
注意,ApplicationListener 是以你的自定义事件的类型(在前面的示例中是 BlockedListEvent)进行泛型参数化的。这意味着 onApplicationEvent() 方法可以保持类型安全,避免任何需要向下转型的情况。你可以注册尽可能多的事件监听器,但请注意,默认情况下,事件监听器是同步接收事件的。这意味着 publishEvent() 方法会阻塞,直到所有的监听器都完成了事件的处理。这种同步和单线程方法的一个优点是,当一个监听器接收到一个事件时,如果有事务上下文可用,它会在发布者的事务上下文内操作。如果需要另一种事件发布策略,可以参考 Spring 的 ApplicationEventMulticaster 接口和 SimpleApplicationEventMulticaster 实现以获取配置选项。
下面的示例展示了用于注册和配置上述每个类的 Bean 定义:
XML
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
总的来说,当调用 emailService Bean 的 sendEmail() 方法时,如果有任何电子邮件消息应该被阻止,就会发布一个类型为 BlockedListEvent 的自定义事件。blockedListNotifier Bean 被注册为 ApplicationListener 并接收 BlockedListEvent,此时它可以通知适当的当事方。
Tip:Spring 的事件机制是为了在同一应用程序上下文中的 Spring Bean 之间进行简单的通信而设计的。然而,对于更复杂的企业集成需求,单独维护的 Spring Integration 项目为构建基于著名的 Spring 编程模型的轻量级、模式导向、事件驱动的架构提供了完整的支持。
1.2.1. 基于注解的事件监听器
你可以使用 @EventListener 注解在托管 Bean 的任何方法上注册事件监听器。BlockedListNotifier 可以被重写如下:
Java
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...
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
方法签名再次声明了它监听的事件类型,但这次使用了灵活的名称,并且没有实现特定的监听器接口。只要实际的事件类型在其实现层次结构中解析了您的泛型参数,事件类型也可以通过泛型进行缩小。
如果您的方法应该监听多个事件,或者您想完全不带参数地定义它,事件类型也可以在注解本身上指定。下面的示例展示了如何做到这一点:
Java
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}1
2
3
4
2
3
4
也可以通过使用定义 SpEL 表达式的注解的 condition 属性来添加额外的运行时过滤,这个表达式应该匹配实际调用特定事件的方法。
下面的示例展示了如何重写我们的通知器,只有当事件的 content 属性等于 my-event 时才会被调用:
Java
@EventListener(condition = "#event.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}1
2
3
4
2
3
4
每个 SpEL 表达式都针对一个专用的上下文进行评估。下表列出了提供给上下文的项目,你可以使用它们进行条件事件处理:
| 名称 | 位置 | 描述 | 示例 |
|---|---|---|---|
| 事件 | 根对象 | 实际的 ApplicationEvent | #root.event 或 event |
| 参数数组 | 根对象 | 用于调用方法的参数(作为对象数组) | #root.args 或 args;args[0] 用于访问第一个参数,等等 |
| 参数名称 | 评估上下文 | 方法参数的任何名称。如果出于某种原因,名称不可用(例如,因为在编译的字节码中没有调试信息),也可以使用 #a<#arg> 语法获取单个参数,其中 <#arg> 代表参数索引(从 0 开始) | #blEvent 或 #a0(你也可以使用 #p0 或 #p<#arg> 参数表示法作为别名) |
请注意,即使你的方法签名实际上引用了发布的任意对象,#root.event 也可以让你访问底层的事件。
如果你需要在处理另一个事件的结果中发布一个事件,你可以更改方法签名以返回应该发布的事件,如下面的示例所示:
Java
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}1
2
3
4
5
2
3
4
5
Tip:这个功能不支持异步监听器。
handleBlockedListEvent() 方法会为它处理的每一个 BlockedListEvent 发布一个新的 ListUpdateEvent。如果你需要发布多个事件,你可以返回一个 Collection 或者一个事件数组。
1.2.2. 异步监听器
如果你希望特定的监听器异步处理事件,你可以重用常规的 @Async 支持。以下示例展示了如何做到这一点:
Java
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}1
2
3
4
5
2
3
4
5
在使用异步事件时,请注意以下限制:
如果异步事件监听器抛出一个
Exception,它不会传播给调用者。有关更多详细信息,请参见AsyncUncaughtExceptionHandler;异步事件监听器方法不能通过返回值发布后续事件。如果你需要发布另一个事件作为处理结果,需要注入一个
ApplicationEventPublisher来手动发布事件;
1.2.3. 排序监听器
如果你需要一个监听器在另一个监听器之前被调用,你可以在方法声明中添加 @Order 注解,如下面的示例所示:
Java
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}1
2
3
4
5
2
3
4
5
1.2.4. 泛型事件
你也可以使用泛型来进一步定义你的事件的结构。可以考虑使用 EntityCreatedEvent<T>,其中 T 是实际创建的实体的类型。例如,你可以创建以下监听器定义,只接收 Person 的 EntityCreatedEvent:
Java
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}1
2
3
4
2
3
4
由于类型擦除,这只有在触发的事件解析了事件监听器过滤的泛型参数时才有效(也就是说,类似于 class PersonCreatedEvent extends EntityCreatedEvent<Person> { … } 这样的情况)。
在某些情况下,如果所有事件都遵循相同的结构(就像前面示例中的事件那样),这可能会变得相当繁琐。在这种情况下,你可以实现 ResolvableTypeProvider 来引导框架超越运行时环境提供的内容。以下事件展示了如何做到这一点:
Java
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()));
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Note:这不仅适用于
ApplicationEvent,还适用于您作为事件发送的任何任意对象。
1.3. 低级资源的便捷访问
为了最优地使用和理解应用上下文,你应该熟悉 Spring 的 Resource 抽象,如在《Spring 核心:资源》中所描述的。
一个应用上下文是一个 ResourceLoader,可以用来加载 Resource 对象。Resource 本质上是 JDK java.net.URL 类的一个更具特性的版本。事实上,Resource 的实现在适当的地方包装了一个 java.net.URL 的实例。Resource 可以以透明的方式从几乎任何位置获取低级资源,包括从类路径、文件系统位置、可以用标准 URL 描述的任何地方,以及其他一些变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源来自哪里是特定的,适合于实际的应用上下文类型。
你可以配置一个部署到应用上下文中的 Bean 来实现特殊的回调接口 ResourceLoaderAware,以便在初始化时自动被回调,并将应用程序上下文本身作为 ResourceLoader 传入。您还可以公开类型为 Resource 的属性,以用于访问静态资源。它们像任何其他属性一样被注入。您可以像简单 String 路径一样指定这些 Resource 属性,并依赖于在部署 Bean 时从这些文本字符串到实际 Resource 对象的自动转换。
提供给 ApplicationContext 构造函数的位置路径实际上是资源字符串,在简单形式下,根据具体上下文实现的方式适当地处理。例如,ClassPathXmlApplicationContext 将一个简单的位置路径视为类路径位置。您还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或 URL 加载定义,而不管实际的上下文类型是什么。
1.4. 应用程序启动跟踪
ApplicationContext 管理着 Spring 应用程序的生命周期,并为组件提供了丰富的编程模型。因此,复杂的应用程序可能会有同样复杂的组件关系图和启动阶段。
通过跟踪应用程序启动步骤的具体指标,可以帮助理解在启动阶段时间是如何被花费的,同时也可以作为一种更好地理解整个上下文生命周期的手段。
AbstractApplicationContext(及其子类)通过 ApplicationStartup 进行了检测,它收集有关各种启动阶段 StartupStep 数据:
- 应用程序上下文生命周期(基础包扫描,配置类管理);
- Bean 生命周期(实例化,智能初始化,后处理);
- 应用程序事件处理;
以下是 AnnotationConfigApplicationContext 中检测的一个示例:
Java
// create a startup step and start recording
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
应用程序上下文已经被多个步骤进行了检测。一旦记录下来,这些启动步骤可以使用特定的工具进行收集、显示和分析。如果你想查看现有启动步骤的完整列表,你可以查看专门的附录部分。
默认的 ApplicationStartup 实现是一个无操作的变体,以最小化开销。这意味着在应用程序启动期间默认不会收集任何指标。Spring 框架附带了一个用于跟踪启动步骤的 Java Flight Recorder 实现:FlightRecorderApplicationStartup。要使用这个变体,你必须在 ApplicationContext 创建后尽快配置一个实例。
如果开发者提供了自己的 AbstractApplicationContext 子类,或者他们希望收集更精确的数据,他们也可以使用 ApplicationStartup 基础设施。
Note:
ApplicationStartup仅在应用程序启动和核心容器中使用,这绝不是 Java 分析器或像 Micrometer 这样的度量库的替代品。
要开始收集自定义的 StartupStep,组件可以直接从应用程序上下文中获取 ApplicationStartup 实例,使他们的组件实现 ApplicationStartupAware,或者在任何注入点上请求 ApplicationStartup 类型。
Note:开发者在创建自定义启动步骤时,不应使用
spring.*命名空间。这个命名空间是为 Spring 内部使用而保留的,可能会发生变化。
1.5. Web 应用程序的 ApplicationContext 便捷实例化
你可以通过使用例如 ContextLoader 这样的工具以声明方式创建 ApplicationContext 实例。当然,你也可以通过使用 ApplicationContext 的实现之一以编程方式创建 ApplicationContext 实例。
你可以使用 ContextLoaderListener 来注册一个 ApplicationContext,如下面的示例所示:
XML
<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>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
监听器会检查 contextConfigLocation 参数。如果该参数不存在,监听器会使用 /WEB-INF/applicationContext.xml 作为默认值。当参数存在时,监听器会使用预定义的分隔符(逗号、分号和空格)来分割字符串,并使用这些值作为搜索应用程序上下文的位置。也支持 Ant 风格的路径模式。例如,/WEB-INF/*Context.xml(适用于所有以 Context.xml 结尾并位于 WEB-INF 目录中的文件)和 /WEB-INF/**/*Context.xml(适用于 WEB-INF 的任何子目录中的所有这样的文件)。
1.6. 将 Spring ApplicationContext 部署为 Java EE RAR 文件
可以将 Spring ApplicationContext 作为 RAR 文件进行部署,将上下文及其所有所需的 Bean 类和库 JAR 封装在 Java EE RAR 部署单元中。这相当于引导一个独立的 ApplicationContext(只在 Java EE 环境中托管),能够访问 Java EE 服务器的设施。RAR 部署是部署无头 WAR 文件的更自然的替代方案 —— 实际上,WAR 文件没有任何 HTTP 入口点,只用于在 Java EE 环境中引导 Spring ApplicationContext。
对于不需要 HTTP 入口点,而只由消息端点和计划任务组成的应用程序上下文,RAR 部署是理想的选择。这样的上下文中的 Bean 可以使用应用服务器资源,如 JTA 事务管理器和绑定到 JNDI 的 JDBC DataSource 实例以及 JMS ConnectionFactory 实例,也可以通过 Spring 的标准事务管理和 JNDI 和 JMX 支持设施在平台的 JMX 服务器上注册。应用程序组件还可以通过 Spring 的 TaskExecutor 抽象与应用服务器的 JCA WorkManager 进行交互。
有关 RAR 部署涉及的配置详细信息,请参阅 SpringContextResourceAdapter 类的 JavaDoc。
要将 Spring ApplicationContext 作为 Java EE RAR 文件进行简单部署:
将所有应用程序类打包成 RAR 文件(这是一个标准的 JAR 文件,只是文件扩展名不同);
将所有所需的库 JAR 文件添加到 RAR 存档的根目录中;
添加一个
META-INF/ra.xml部署描述符(如SpringContextResourceAdapter的 JavaDoc 所示)和相应的 Spring XML Bean 定义文件(通常是META-INF/applicationContext.xml);将生成的 RAR 文件放入你的应用服务器的部署目录中;
Note:这样的 RAR 部署单元通常是自包含的。它们不会向外界暴露组件,甚至不会向同一应用的其他模块暴露。与基于 RAR 的
ApplicationContext的交互通常通过它与其他模块共享的 JMS 目标进行。例如,基于 RAR 的ApplicationContext也可能安排一些作业,或者对文件系统中的新文件(或类似的)做出反应。如果它需要允许来自外部的同步访问,它可以(例如)导出 RMI 端点,这些端点可以被同一机器上的其他应用模块使用。
2. BeanFactory API
BeanFactory API 为 Spring 的 IoC 功能提供了基础支持。它的特定契约主要用于与 Spring 的其他部分以及相关的第三方框架进行集成,而它的 DefaultListableBeanFactory 实现是高级 GenericApplicationContext 容器中的关键代理。
BeanFactory 和相关接口(如 BeanFactoryAware,InitializingBean,DisposableBean)是其他框架组件的重要集成点。它们不需要任何注解甚至反射,允许容器和其组件之间进行非常高效的交互。应用级别的 Bean 可能会使用相同的回调接口,但通常更倾向于声明性依赖注入,无论是通过注解还是通过编程配置。
请注意,核心 BeanFactory API 层级和其 DefaultListableBeanFactory 实现并不对配置格式或任何要使用的组件注解做出假设。所有这些风格都是通过扩展(如 XmlBeanDefinitionReader 和 AutowiredAnnotationBeanPostProcessor)引入的,并在共享的 BeanDefinition 对象上操作作为核心元数据表示。这就是使 Spring 的容器如此灵活和可扩展的本质。
2.1. BeanFactory 还是 ApplicationContext?
本节解释了 BeanFactory 和 ApplicationContext 容器级别之间的差异以及引导过程的影响。
除非你有充分的理由不这样做,否则你应该使用 ApplicationContext,其中 GenericApplicationContext 及其子类 AnnotationConfigApplicationContext 是自定义引导的常见实现。这些是 Spring 核心容器的主要入口点,用于所有常见的目的:加载配置文件、触发类路径扫描、以编程方式注册 Bean 定义和注解类,以及(从 5.0 开始)注册功能性 Bean 定义。
因为 ApplicationContext 包含了 BeanFactory 的所有功能,所以通常推荐使用 ApplicationContext 而不是普通的 BeanFactory,除非在需要完全控制 Bean 处理的场景中。在 ApplicationContext(如 GenericApplicationContext 实现)中,几种类型的 Bean 是按照约定(即按照 Bean 名称或 Bean 类型 —— 特别是后处理器)被检测到的,而普通的 DefaultListableBeanFactory 对任何特殊的 Bean 都是不可知的。
对于许多扩展的容器特性,如注解处理和 AOP 代理,BeanPostProcessor 扩展点是必不可少的。如果你只使用普通的 DefaultListableBeanFactory,这样的后处理器默认情况下不会被检测到并激活。这种情况可能会令人困惑,因为你的 Bean 配置实际上没有任何问题。相反,在这种情况下,容器需要通过额外的设置进行完全引导。
以下表格列出了 BeanFactory 和 ApplicationContext 接口及其实现提供的特性。
| 功能 | BeanFactory | ApplicationContext |
|---|---|---|
| Bean 实例化/装配 | √ | √ |
| 集成的生命周期管理 | × | √ |
自动的 BeanPostProcessor 注册 | × | √ |
自动的 BeanFactoryPostProcessor 注册 | × | √ |
方便的 MessageSource 访问(用于国际化) | × | √ |
内置的 ApplicationEvent 发布机制 | × | √ |
要在 DefaultListableBeanFactory 中显式注册一个 Bean 后处理器,你需要以编程方式调用 addBeanPostProcessor,如下面的示例所示:
Java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
要将 BeanFactoryPostProcessor 应用到普通的 DefaultListableBeanFactory,你需要调用它的 postProcessBeanFactory 方法,如下面的示例所示:
Java
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
在这两种情况下,显式的注册步骤都是不方便的,这就是为什么在支持 Spring 的应用程序中,各种 ApplicationContext 变体比普通的 DefaultListableBeanFactory 更受欢迎,特别是在依赖 BeanFactoryPostProcessor 和 BeanPostProcessor 实例来扩展容器功能的典型企业设置中。
Note:
AnnotationConfigApplicationContext已注册了所有常见的注解后处理器,并可能通过配置注解(如@EnableTransactionManagement)在后台引入额外的处理器。在 Spring 的基于注解的配置模型的抽象级别上,Bean 后处理器的概念变成了一个纯粹的内部容器细节。