ApplicationContext
的额外功能正如在介绍章节所讨论的,org.springframework.beans.factory
包提供对管理和维护bean基本功能,包括使用程序化的方式。org.springframework.context
包添加ApplicationContext
接口,其扩展了BeanFactory
接口,此外还扩展其他接口,以更面向应用程序的风格提供额外的功能。许多人以完全声明式地风格使用ApplicationContext
,甚至不会程序化地创建它,而是依赖像是ContextLoader
这样支持的类来自动化实例一个ApplicationContext
作为Jakarta EE web应用程序普通启动过程的一部分。
要以更加应用程序化的风格来改善BeanFactory
功能,上下文包也提供以下功能:
MessageSource
接口。ResourceLoader
接口ApplicationListener
接口的bean,通过使用ApplicationEventPublisher
接口。HierarchicalBeanFactory
接口。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
。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方式处理的。对于此示例的目的,假设以上资源包文件的两个内容如下:
# 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", Locale.ENGLISH);
System.out.println(message);
}
以上程序结果输出以下:
Alligators rock!
总而言之,在称为beans.xml
的文件中定义的MessageSource
,其存在于你的类路径根路径。messageSource
bean定义通过它的beasenames
属性依赖多个资源包。列表中的三个文件被传入到basenames
属性作为你的根类路径文件存在,依次被称为format.properties
,exceptions.properties
和windows.properties
。
接下来的示例展示了传到消息查找的参数。这些参数被转换到String
对象并插入到查找消息的占位符中。
<beans>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
bean>
<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", Locale.ENGLISH);
System.out.println(message);
}
}
execute()
方法调用的结果输出正如以下:
The userDao argument is required.
关于国际化(“i18n”),Spring的各种MessageSource
实现遵守与标准JDK ResourceBundle
相同的区域解决方案并且回退规则。简而言之,继续前面定义的示例messageSource
,如果你想要针对British(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
中定义的任意bean,当bean被创建和配置时,其实现了MessageSouceAware
接口使用应用程序上下文的MessageSource
注入。
因为Spring的
MessageSource
是基于Java的ResourceBundle
,它不会将相同的base name合并绑定包,但是只使用首先发现的绑定包。相同base名称的随后的消息绑定包会被忽略。
作为
ResourceBundleMessageSource
的替代方案,Spring提供了ReloadableResourceBundleMessageSource
类。此变体支持相同的绑定文件格式,但是相比基于ResourceBundleMessageSource
标准的JDK实现更加灵活。特别是,它允许从任意Spring资源位置读取文件并且支持绑定属性文件的热重载(同时在两者之间有效地缓存他们)。请查看ReloadableResourceBundleMessageSource
了解详情。
在ApplicationContext
中的事件处理通过ApplicationEvent
类和ApplicationListener
接口提供。如果实现了ApplicationListener
接口的bean发布到上下文,每次ApplicationEvent
发布到ApplicationContext
,通知该bean。本质上,这是标准的观察者设计模式。
从Spring 4.2开始,事件底层架构得到了显著提升并且提供了一个基于注解模式以及有能力发布任何任意事件(即,不必扩展
ApplicationEvent
的对象)。当已经发布这样的对象,我们将它封装到一个事件中。
以下表格描述了Spring提供的标准事件。
事件 | 解释 |
---|---|
ContextRefreshedEvent |
当ApplicationContext 初始化或者刷新时发布(例如,在ConfigurableApplicationContext 接口上使用refresh() 方法)。这里初始化意味着所有的bean已经载入,前置处理bean已经检测和激活,单例已经预实例化,并且ApplicationContext 对象已经可以使用。只要上下文没有关闭,只有所选择的ApplicationContext 实际支持这样的“热”刷新,refresh可以触发多次。例如,XmlWebApplicationContext 支持热刷新,但是GenericApplicationContext 不支持 |
ContextStartedEvent |
当ApplicationContext 通过在ConfigurableApplicationContext 接口上使用start() 方法启动时发布。这里,“启动”意味着所有Lifecycle bean接收了一个明确的启动信号。通常,这个信号用于在一个明确停止之后重启bean,而且它也可以用于启动那么没有配置自动启动的组件。(例如在初始化阶段没有启动的组件) |
ContextStoppedEvent |
当ApplicationContext 通过ConfigurableApplicationContext 接口上使用stop() 方法停止时发布。这里,“停止”意味着所有Lifecycle bean接收一个明显的停止信号。停止的上下文可以通过start() 调用重启。 |
ContextClosedEvent |
当ApplicationContext 通过使用在ConfigurableApplicationContext 接口的close() 方法或者通过JVM关停钩子关闭时发布。这里,“关闭”意味着所有单例bean将被销毁。一旦上下文关闭,它到达生命结尾并且不能刷新和重启。 |
RequestHandledEvent |
特定于web事件告诉所有的bean,一个HTTP请求已经开始服务。此事件在请求完成时发布。此事件仅适用于使用Spring的DispatcherServlet 的web应用程序。 |
ServletRequestHandledEvent |
RequestHandledEvent 的子类,添加了特定于Servlet的上下文信息 |
你也可以创建和发布自己的自定义事件。以下示例展示了一个简单的类,其扩展了Spring的ApplicationEvent
基类:
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容器检测EmailService
实现ApplicationEventPublisherAware
并自动化调用setApplicationEventPublisher()
。实际上,被传入的参数是Spring容器本身。你通过应用程序的ApplicationEventPublisher
接口与应用程序上下文进行交互。
要接收自定义的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...
}
}
注意,ApplicationListener
使用你的自定义类型的泛型(在上述事例中的BlockedListEvent
)。这意味着onApplicationEvent()
方法可以保持类型安全,避免任何向下转换的需要。你可以注册你希望的任意数量的事件监听器,但是注意的是,默认情况下,事件监听器同步接收事件。这意味着publishEvent()
方法阻塞,直到所有监听器已经完成处理事件。同步和单线程的一个优势是,当一个监听器接收一个事件,如果事件上下文可用,它将在发布者的事务上下文操作。如果事件发布另外一个策略变得必须,请查看java文档了解Spring的ApplicationEventMulticaster
接口和SimpleApplicationEventMulticaster
实现了解配置选项。
以下示例展示了用于注册和配置以上每一个类的bean定义:
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>[email protected]value>
<value>[email protected]value>
<value>[email protected]value>
list>
property>
bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="[email protected]"/>
bean>
将他们放到一起,当调用emailService
bean的sendEmail()
方法时,如果存在任何应该阻塞的email消息,类型BlockedListEvent
的自定义事件则发布。blockedListNotifier
bean被注册为ApplicationListener
并接收BlockedListEvent
,然后通知相关方。
Spring的事件机制被设计用于简单的相同应用程序上下文内Springbean之间的通讯。但是,对于更加负责企业集成需要,单独维护Spring Integration项目为构建轻量化,面向模式,事件驱动架构提供了完整支持,这些体系构建在知名的Spring程序模式之上。
你可以通过使用@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
表达式注解的condition
属性添加额外的运行时过滤器,其应该与实际调用特定事件的方法匹配。
以下示例展示了我们的通知方如何被重写只有当事件的content
属性等于my-event
时被调用:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每一个SpEL
表达式根据识别的上下文进行评估。以下表格列出了上下文可用的条目,以便你可以使用他们来条件性事件处理:
名称 | 位置 | 描述 | 举例 |
---|---|---|---|
Event | 根对象 | 实际ApplicationEvent |
#root.event 或者event |
参数数组 | 根对象 | 用于调用方法的参数(作为对象数组) | #root.args 或者args ;args[0] 访问第一个参数等等 |
参数名称 | 评价环境 | 任何方法参数的名称。如果,处于一些原因,名称不可用(例如,因为在编译的字节码中没有调试信息),使用#a<#arg> 语法个别的参数也是可用的,其中<#arg> 表示参数索引(从0开始)。 |
#blEvent 或者#a0 (你也可以使用#p0 或者#p<#arg> )参数符号作为别名。 |
注意,#root.event
给你的对底层事件的访问,即使你的方法签名实际引用一个发布的任意的对象。
如果你需要发布一个事件作为处理另外一个事件的结果,你可以修改方法签名来返回要发布的事件,正如以下示例所示:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
这个特性不支持异步监听器。
handleBlockedListEvent()
方法对每一个它处理的BlockedListEvent
发布一个新的ListUpdateEvent
。如果你需要发布多个事件,你可以返回一个Collection
或者事件数组代替。
如果你想要一个特定的监听器来异步处理事件,你可以重用常规的@Async
支持。以下示例展示了如何这样做:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
当使用异步事件时,关注以下限制:
Exception
,它不会传递到调用者。请查看AsyncUncaughtExceptionHandler
了解更多详情。ApplicationEventPublisher
来手工发布事件。如果你需要一个监听器在另外一个之前调用,你可以添加@Order
注解到方法声明,正如以下示例所示:
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
你也可以使用泛型化来进一步定义你的事件结构。考虑使用EntityCreatedEvent
,其中T
是实际创建的实体类型。例如,你可以创建以下监听器定义来接收一个Person
的EntityCreatedEvent
:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
由于类型擦除,只有触发了事件解析了事件监听器过滤器的泛型参数才有效。(也就是说,就像class PersonCreatedEvent extends 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
有效,而且对任何作为事件发送的任意的对象有效。
对于最佳用法和应用程序上下文的理解,你应该使你自己熟悉Spring的Resource
抽象,正如在Resources
中描述的。
一个应用程序的上下文是ResourceLoader
,其可以用于加载Resource
对象。Resource
本质是JDK的java.net.URL
类更丰富的版本。实际上,Resource
实现在适当的地方封装了java.net.URL
实例。Resource
可以使用一种透明方式从差不多任何位置获取低级别资源,包括类路径,文件系统位置,标准URL可描述的任何位置和其他变体。如果资源位置字符串是一个简单的路径,没有任何前缀,这些资源来自的位置是特定的并且对于实际应用程序上下文类型是合适的。
你可以配置一个部署到应用程序上下文的bean来实现特殊的回调接口,ResourceLoaderAware
,在初始化时自动化回调,同时将应用程序上下文本身作为ResourceLoader
传入。你也可以暴露类型Resource
的属性,用于访问静态资源。他们被注入到它,就像其他属性。当bean被发布时,你可以指定这些Resource
属性作为简单的String
路径并依赖从这些文本字符串到实际Resource
对象的自动转换。
提供到ApplicationContext
构造器的位置路径实际上是资源字符串,并且使用简单的格式,根据特定的上下文实现进行适当的处理。
例如ClassPathXmlApplicationContext
将简单的位置路径视为类路径。你也可以使用带前缀的位置路径(资源字符串)来强制从类路径或者URL加载定义,无论实际的上下文类型。
ApplicationContext
管理Spring应用程序的生命周期和围绕组件提供一个丰富的的程序模型。结果是,复杂的应用程序具有同样复杂的组件图和启动阶段。
使用特定的指标追踪应用程序启动步骤可以帮助理解在启动期间时间消耗在哪里,而且它也可以用于更好理解整个上下文生命周期的一种方式。
AbstractApplicationContext
(以及子类)使用ApplicationStartup
进行检测,其收集了关于各种各样的启动步骤的StartupStep
数据:
以下是在AnnotationConfigApplicationContext
中仪表化的示例:
// create a startup step and start recording
StartupStep scanPackages = this.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();
应用程序上下文已经使用多个步骤进行了仪表化。一旦记录,这些启动步骤可以使用特定的工具被收集,展示和解析。对于一个完整已存在的启动步骤的列表,你可以查看专用的附录章节。
默认的ApplicationStartup
实现是一个无操作的变体,为了最小化损耗。这意味着默认情况下在应用程序启动之间没有指标进行收集。Spring Framework带有一个使用Java Flight Recorder追踪启动步骤的实现:FlightRecorderApplicationStartup
。要使用这个变体,你必须在创建ApplicationContext
后立即配置它的一个实例到ApplicationContext
。
如果他们提供了他们自己的AbstractApplicationContext
子类或者他们希望收集更准确的数据,开发者也可以使用ApplicationStartup
基础设施。
ApplicationStartup
注定只能用于应用程序启动期间并且用于核心容器;这绝不是Java分析器和指标库(Micrometer)的替代品。
要开始收集自定义的StartupStep
,组件可以直接从应用程序上下文获取ApplicationStartup
实例,使他们的组件实现ApplicationStartupAware
,或者在任意注入点访问ApplicationStartup
类型。
当创建自定义启动步骤时,开发者不应该使用
"spring.*"
命名空间。命名空间保留给内部Spring使用,可能会有变化。
你可以声明式地(例如,通过使用ContextLoader
)创建ApplicationContext
实例。当然,你也可以通过使用ApplicationContext
实现的其中一个程序化创建ApplicationContext
实例。
你可以通过使用ContextLoaderListener
注册ApplicationContext
,正如以下示例所示:
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
此监听器会审查contextConfigLocation
属性。如果属性不存在,监听器使用/WEB-INF/applicationContext.xml
作为默认值作为默认值。当参数存在,监听器通过使用预定义的分隔符分隔(逗号,分号和空格)并且使用这些值作为查找应用程序上下文所在位置。Ant风格路径格式也同样支持。例如/WEB-INF/*Context.xml
(对于所有以Context.xml
结尾的名称的文件并位于WEB-INF
目录)和/WEB-INF/**/*Context.xml
(对于在WEB-INF
所有子目录所有这样的文件)。
ApplicationContext
作为Jakarta EE RAR文件将Spring ApplicationContext
部署为RAR文件是可以的,将上下文和所有它需要的bean类和类库JAR封装到Jakarta EE RAR部署单元。这样等价于引导启动的独立的ApplicationContext
可以访问Jakarta EE服务器设施。RAR部署是部署一个无领导的WAR文件更加自然的替代方案–实际上,不带任何HTTP入口的WAR文件仅用于引导启动Jakarta EE环境中的一个Spring ApplicationContext
。
RAR部署非常适合那些不需要HTTP入口的应用程序上下文,而是只有消息端点和定时任务。在这样上下文中的bean可以使用应用程序服务器资源,例如JTA事务管理和JNDI绑定的JDBC DataSouce
实例和JMS ConnectionFactory
实例并且也可以注册平台的JMX服务器 – 所有通过Spring的标准的事务管理和JNDI和JMX支持设备。应用程序组件也可以通过Spring的TaskExecutor
抽象与应用程序服务器的JCA WorkManager
互动。
请查看SpringContextResourceAdapter
类的javadoc来了在RAR部署中涉及的配置详情。
为了Spring ApplicationContext的简单部署作为Jakarta EE RAR文件:
META-INF/ra.xml
部署描述符(正如在SpringContextResourceAdapter的javadoc所展示的)和对应的Spring XML bean定义文件(通常META-INF/applicationContext.xml
)。这样RAR部署单元通常是自包含的。他们不会暴露组件到外部,甚至不会暴露到相同应用程序的其他模块。与基于RAR
ApplicationContext
交互通常通过与其他模块分享JMS端点进行。例如,一个基于RAR的ApplicationContext
也可以调度一些job或者对文件系统中(或者类似的)的新文件作出反应。如果它需要允许从外部同步访问,例如,它可以导出RMI端点,其通常用于在相同的机器上的其他应用程序模块。
BeanFactory
APIBeanFactory
API提供Spring的IOC功能的底层基础。它的明确的合约差不多用于与Spring的其他部分和第三方框架交互,并且它的DefaultListableBeanFactory
实现是在高级别GenericApplicationContext
容器内部的一个关键委托。
BeanFactory
和相关接口(例如BeanFactoryAware
,InitializingBean
,DisposableBean
)是与其他框架组件非常的重要的交互点。由于不需要任何注解甚至反射,他们允许容器和它的组件之间进行非常高效的交互。应用程序级别的bean可能适用相同的回调接口,但是通常优先使用依赖注入代替,或者通过注解或者通过程序化配置。
注意,核心BeanFactory
API级别和它的DefaultListableBeanFactory
实现不会对要使用的配置格式或者任何组件注解作出假设。所有的这些风格都是通过扩展(例如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)实现的,共享的BeanDefinition
对象作为核心的元数据展现进行操作。这是使Spring的容器如此灵活和扩展性的本质。
BeanFactory
或者ApplicationContext
?此部分解释了BeanFactory
和ApplicationContext
容器级别之间的不同,以及他们对引导启动的影响。
你应该使用一个ApplicationContext
,除非你有一个充分的原因不使用它,使用GenericApplicationContext
和它的子类AnnotationConfigApplicationContext
作为常用的自定义引导实现。这些是Spring的核心容器的核心的入口点,用于:配置文件加载,触发类路径扫描,程序化注册bean定义和注解的类,并且(从5.0开始)注册函数性bean定义。
因为ApplicationContext
包含所有的BeanFactory
的功能,所以除了需要完全控制bean处理的场景,通常建议使用它,而不是普通的BeanFactory
。在ApplicationContext
内部(例如GenericApplicationContext
实现),几种bean通过约定检测(即,通过bean名称或者bean类型–特别是前置处理),而普通的DefaultListableBeanFactory
不知道任何特殊的bean。
对于许多扩展的容器特性,例如注解过程和AOP代理,BeanPostProcessor
扩展点是必不可少的。如果你只使用普通的DefaultListableBeanFactory
,这样前置处理不会得到检测并默认激活。此情况可能是疑惑的,因为你的bean配置没有实际报错。然而,在这样的一个情况下,需要通过额外的设置来完全引导容器。
以下表格列出来BeanFactory
和ApplicationContext
接口和实现提供的特性:
|特性| BeanFactory
| ApplicationContext
|
|–|–|
| bean实例和/绑定 | 支持 | 支持 |
| 集成生命周期管理 | 不支持 | 支持 |
| 自动化BeanPostProcessor
注册 | 不支持 | 支持 |
| 自动化BeanFactoryPostProcessor
注册 | 不支持 | 支持 |
| 方便的MessageSource
访问(对于国际化) | 不支持 | 支持 |
| 内建的ApplicationEvent
发行机制 | 不支持 | 支持 |
DefaultListableBeanFactory
要显性地注册一个前置处理,你需要程序化地调用addBeanPostProcessor
,正如以下示例所示:
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 factory
要将BeanFactoryBeanProcessor
应用到普通的DefaultListableBeanFactory
,你需要调用它的postProcessBeanFactory
方法,正如以下示例所示:
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);
在这两个情况中,显性注册步骤是不方便的,这就是在Spring背景的应用程序中各种各样的ApplicationContext
变体优先级高于普通的DefaultListableBeanFactory
的原因,特别是在典型的企业级设置依赖BeanFactoryPostProcessor
和BeanPostProcessor
实例来扩展容器功能时。
AnnotationConfigApplicationContext
已经注册了所有常用的注解前置处理并且通过配置注解引入了额外的处理器,例如@EnableTransactionManagement
。在Spring的基于注解的配置模型抽象层,bean后置处理的概念变成了仅仅是内容容器细节。