Spring 5 中文解析核心篇-IoC容器之ApplicationContext与BeanFactory
1.15 的其它功能
像在讨论的,包提供基本的管理和操作bean的功能,包含编程式方式。包添加接口,它拓展接口,此外还扩展了其他接口以提供更多面向应用程序框架的样式的附加功能。许多人使用以完全声明的方式,甚至没有以编程方式创建它,但是取而代之的是依靠诸如之类的支持类来自动实例化,这是Java EE Web应用程序正常启动过程的一部分。
为了以更加面向框架的方式增强的功能,上下文包还提供以下功能:
通过接口获取,在获取消息
通过接口,获取资源,例如URL和文件
通过使用接口,将事件发布到实现接口的bean
加载多个(分层)上下文,通过接口将每个上下文集中在一个特定层上,例如应用程序的Web层
1.15.1 使用国际化
接口继承一个叫接口并且提供国际化()功能。Spring也提供接口,它能分层地解析消息。这些接口一起提供了Spring影响消息解析的基础。这个方法定义在这些接口上:
:这个基础方法被使用从中获取消息。当指定的位置没有找到消息,默认消息被使用。使用标准库提供的功能,传入的所有参数都将成为替换值。
:实质上类似前面的方法一样,但是有一个不同的地方:没有默认消息被指定。如果这个消息不能不找到,一个被抛出。
:在前面方法中所有使用的属性被一个类名为包装,你可以使用这个方法。
当被加载时,它会自动地在上下文中搜索bean定义为的类。这个bean必须有个名字。如果bean没有找到,所有调用前面的方法被代理到消息源。如果没有找到消息源,尝试去父容器查找相同名称的bean。如果找到,则使用它作为。如果没有找到任何消息源,一个空的被实例化去接受前面定义的方法调用。
Spring提供两个实现,和。两者都实现以便进行嵌套消息传递。很少被使用,但是提供编程式的方式去添加消息源。下面例子展示:
format
exceptions
windows
这个例子假设在你的类路径定义有三个资源包分别是、和。任何解析消息的请求都以jdk标准的方式处理,即通过对象解析消息。为了这个例子的目的,假设上面两个资源包文件内容分布如下:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下面例子展示一个程序去执行功能。记住,所有的实现也是实现并且能够转换为接口。
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!
总而言之,被定义在叫做的文件中,它存在于你的类路径root下。 bean定义通过它的basenames属性引用一些资源包。列表中传递给basenames属性的三个文件在类路径的根目录下以文件形式存在并且分别称为,和。
下一个示例显示传递给消息查找的参数。这些参数被转换为String对象并且在查找消息中插入占位符。
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);
}
}
调用 方法输出结果:
The userDao argument is required.
关于国际化(), Spring的各种实现遵循与标准JDK 相同的语言环境解析和后备规则。简而言之,并继续前面定义的示例,如果要针对英国()语言环境解析消息,则可以分别创建名为,和的文件。
通常,语言环境解析由应用程序的周围环境管理。在以下示例中,手动指定了针对其解析(英国)消息的语言环境:
# 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.
你也可以使用接口去获取一个引用任意已经被定义的。任何在中定义的bean,当 bean被创建且被配置时,实现接口将被注入应用上下文的。
作为的替代方法,Spring提供一个类。这个变体支持相同包文件格式,但是比基于标准JDK的实现更灵活。特别是,它允许从任何Spring资源为主读取文件并且支持包属性文件的热加载(同时在它们之间有效地缓存它们)。查看javadoc详细信息。
代码示例:
1.15.2 标准和自定义事件
在中事件处理是通过和接口提供的。如果bean实现接口并且部署到上下文中,则每次将发布到时,都会通知该bean。实质上,这是一个标准的观察者模式。
从Spring4.2开始,事件基础设施已经被显著地改善并且提供基于注解的模式以及去发布任意事件的能力(也就是说,对象没有必须要从拓展)。当发布一个对象时,我们包装为事件。
下面表格描述Spring提供的标准事件:

你也可以创建和发布你自己的自定义事件。下面例子展示了一个简单类,它拓展了Spring的基础类:
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...
}
去发布自定义,在上调用方法。通常地,通过创建一个类实现接口并且把它注册为Spring的bean。下面的例子展示:
public class EmailService implements ApplicationEventPublisherAware {
private List blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List 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容器检测实现并且自动地调用方法。事实上,传入的参数是Spring容器本身。你正在通过其接口与应用程序上下文进行交互。
去接受自定义,你可以创建一个类实现并且注册它作为Spirng的bean。下面例子展示以一个类:
public class BlackListNotifier implements ApplicationListener {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
注意,通常是用定制事件的类型参数化的(在前面的例子中是)。也就是说方法能保持类型安全,避免任何转换。你可以注册许多你希望的事件监听,但是注意,默认情况,事件监听接受事件时同步地。也就是说方法阻塞直到所有监听器完成事件处理。这种同步和单线程方法的一个优点是,当监听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文中进行操作。
如果有必要采用其他发布事件的策略,查看javadoc对应Spring的接口和实现配置。
下面例子显示bean定义使用去注册和配置每个类;
known.spammer@example.org
known.hacker@example.org
john.doe@example.org
放到一起,当 bean的方法被调用时,如果这里任何邮件信息需要被例入黑名单,一个类型为自定义事件被发布。 bean作为和接受被注册,在这一点上,它可以通知有关各方。
Spring的事件机制被设计为在同一个应用上下文中Spring bean之间的简单通信/交流。然而,对于更复杂的企业集成需求,单独维护的项目提供了对构建轻量级、面向模式、事件驱动的体系结构的完整支持,这些体系结构构建于著名的Spring编程模型之上。
基于注解事件监听器
从Spring4.2后,你可以在任何通过使用注解的bean,公共方法注册一个事件监听器。可以被重写,像下面例子:
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表达式的注释的condition属性来添加其他运行时过滤器,该注释应匹配以针对特定事件实际调用该方法。
以下示例显示了仅当事件的content属性等于时,才可以重写我们的通知程序以进行调用:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个SpEL表达式都会根据专用上下文进行评估。下表列出了上下文可用的项,以便你可以将它们用于条件事件处理。

请注意,即使你的方法签名实际上引用了已发布的任意对象,#root.event也允许你可以访问底层事件。
如果你需要发布一个事件作为处理其它事件结果,你可以改变方法签名去返回事件,类似下面例子:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
这个特性对于异步监听是不支持的。
这个新方式通过上面的方法为每个处理发布一个新。如果你需要去发布一些事件,你可以返回一个事件。
异步事件监听器
如果你想一个特定监听器去处理异步事件,你可以重用常规的支持。下面例子展示怎样使用:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
使用异步事件时,请注意以下限制:
监听器顺序
如果你需要一个监听器调用在另外一个监听器之前,你可以添加注解到方法声明上,类似下面例子:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
泛型事件
你可以使用泛型去进一步定义你的事件结构。考虑使用,其中是已创建的实际实体的类型。例如,你可以创建下面的监听器定义,为去接受:
@EventListener
public void onPersonCreated(EntityCreatedEvent event) {
// ...
}
由于类型擦除,只有在触发的事件解析事件监听器过滤器所基于的通用参数(即,类似于类扩展的情况下才可以工作。
在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像前面示例中的事件一样)。在这种情况下,你可以实现来指导框架,使其超出运行时环境提供的范围(备注:通过提供更多类相关信息)。下面的事件说明了如何做到这一点:
public class EntityCreatedEvent extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
这不仅适用于,而且适用于你作为事件发送的任何任意对象。
1.15.3 便捷地访问低级别资源
为了更好的使用和理解应用上下文,你应该熟悉Spring的抽象,在中描述。
应用上下文是,它可以被使用加载对象。本质上是JDK 类的功能更丰富的版本。事实上,实现包装一个java.net.URL实例,可以透明的方式从几乎任何位置获取低级资源,包含从类路径、文件系统路径、任何描述一个标准URL、以及其他的变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合于实际的应用程序上下文类型。
你可以配置部署到应用程序上下文中的Bean,以实现特殊的回调接口,以便在初始化时自动回调,并将应用程序上下文本身作为传入。你也可以暴露类型属性,被使用获取静态资源。它们像其他属性一样注入其中。当bean被部署时,你可以指定这些属性作为简单String路径并且依靠这些文本字符串自动转换真实对象。
提供给构造函数的位置路径实际上是资源字符串,并且以简单的形式根据特定的上下文实现进行适当的处理。例如,将简单的位置路径视为类路径位置。你也可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或URL中加载定义,而不管实际的上下文类型如何。
1.15.4 Web应用的ApplicationContext实例化
你可以通过声明式地创建实例,例如:。当然,你也可以通过使用实现之一编程式地创建实例。
你可以通过使用注册一个,类似下面例子:
contextConfigLocation
/WEB-INF/daoContext.xml/WEB-INF/applicationContext.xml
org.springframework.web.context.ContextLoaderListener
监听器检查参数。如果参数不存在,监听器使用作为默认值。当参数存在时,监听器通过使用预先定义的分隔符和使用值作为应用上下文搜索的路径的分隔字符串。Ant格式路径模式也支持很好。示例包括(适用于所有名称以结尾且位于目录中的文件)和(适用于所有此类文件)文件在的任何子目录中)。
1.15.5 部署Spring作为Java EE rar文件
可以将Spring 部署为RAR文件,并将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这等效于引导独立的(仅托管在Java EE环境中)能够访问Java EE服务器功能。RAR部署是部署无头WAR文件的方案的一种更自然的选择,实际上,这种WAR文件没有任何HTTP入口点,仅用于在Java EE环境中引导Spring 。RAR部署非常适合于不需要HTTP入口点,而只由消息端点和调度任务组成的应用程序上下文。在上下文中的这些bean能使用应用服务资源,例如,JTA事物管理、JNDI绑定JDBC 实例、JMS 实例和注册平台的JMX服务-通过所有Spring的标准事物管理、JNDI、JMX支持的设施。应用组件也可以通过Spring的抽象与应用server的JCA 相互交互 。
有关RAR部署中涉及的配置详细信息,请参见类的javadoc。
对于将Spring 作为Java EE RAR文件的简单部署:
此类RAR部署单位通常是独立的。它们不会将组件暴露给外界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的的交互通常是通过与其他模块共享的JMS目标进行的。例如,基于rar的还可以调度一些作业或对文件系统中的新文件(或类似的东西)作出反应。如果它需要允许来自外部的同步访问,它可以(例如)导出RMI端点,这些端点可以由同一台机器上的其他应用程序模块使用。
1.16
API为Spring IoC工厂提供底层基础。它的特定契约主要用于与Spring的其他部分和相关第三方框架的集成,它的实现是更高级别的容器中的一个关键代理。
和相关的接口(例如:、、)是为其他框架组件非常重要的集成点。通过不需要任何注解,甚至不需要反射,它们可以在容器及其组件之间进行非常有效的交互。应用级别bean可能使用相同回调接口,但通常更喜欢通过注释或通过编程配置进行声明式依赖注入。
请注意,核心 API级别及其实现不对配置格式或要使用的任何组件注释进行假设。所有这些风格都通过扩展(比如和)实现,并在共享的对象上进行操作,作为核心元数据表示。这是使Spring的容器如此灵活和可扩展的本质所在。
1.16.1 或
本节说明和容器级别之间的区别以及对引导的影响。
除非有充分的理由,否则应使用,除非将和其子类作为自定义引导的常见实现,否则应使用。这些是用于所有常见目的的Spring核心容器的主要入口点:加载配置文件、触发类路径扫描、以编程方式注册Bean定义和带注解的类,以及(从5.0版本开始)注册功能性Bean定义。
因为包含的所有功能,所以通常建议在普通BeanFactory中使用,除非需要完全控制Bean处理的方案。在(例如实现)中,按照约定(即,按bean名称或按bean类型(尤其是后处理器))检测到几种bean,而普通的则与任何特殊bean无关。
对于许多扩展的容器功能,例如注解处理和AOP代理,扩展点是必不可少的。如果仅使用普通的,则默认情况下不会检测到此类后处理器并将其激活。这种情况可能会造成混淆,因为你的bean配置实际上并没有错。而是在这种情况下,需要通过其他设置完全引导容器。
下表列出了和接口和实现提供的功能。

要向显式注册Bean后处理器,需要以编程方式调用,如以下示例所示:
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
要将应用于普通的,你需要调用其方法,如以下示例所示:
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支持的应用程序中,各种变量比普通的更为可取的原因,尤其是在典型企业设置中依赖和实例来扩展容器功能时。
已注册了所有常见的注解后处理器,并且可以通过配置注解(例如)在幕后引入其他处理器。在Spring基于注解的配置模型的抽象级别上,bean后处理器的概念仅是内部容器详细信息。
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
微信公众号:
