Spring 5 中文解析核心篇 -IoC 容器之 ApplicationContext 与 BeanFactory
1.15 ApplicationContext
的其它功能
像在这个章节讨论的,org.springframework.beans.factory
包提供基本的管理和操作bean的功能,包含编程式方式。org.springframework.context
包添加ApplicationContext接口,它拓展BeanFactory
接口,此外还扩展了其他接口以提供更多面向应用程序框架的样式的附加功能。许多人使用ApplicationContext
以完全声明的方式,甚至没有以编程方式创建它,但是取而代之的是依靠诸如ContextLoader
之类的支持类来自动实例化ApplicationContext
,这是Java EE Web应用程序正常启动过程的一部分。
为了以更加面向框架的方式增强BeanFactory
的功能,上下文包还提供以下功能:
通过
MessageSource
接口获取,在i18n
获取消息通过
ResourceLoader
接口,获取资源,例如URL和文件通过使用
ApplicationEventPublisher
接口,将事件发布到实现ApplicationListener
接口的bean加载多个(分层)上下文,通过
HierarchicalBeanFactory
接口将每个上下文集中在一个特定层上,例如应用程序的Web层
1.15.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
被加载时,它会自动地在上下文中搜索bean定义为MessageSource
的类。这个bean必须有个messageSource
名字。如果bean没有找到,所有调用前面的方法被代理到消息源。如果没有找到消息源,ApplicationContext
尝试去父容器查找相同名称的bean。如果找到,则使用它作为MessageSource
。如果ApplicationContext
没有找到任何消息源,一个空的DelegatingMessageSource
被实例化去接受前面定义的方法调用。
Spring提供两个MessageSource
实现,ResourceBundleMessageSource
和StaticMessageSource
。两者都实现HierarchicalMessageSource
以便进行嵌套消息传递。StaticMessageSource
很少被使用,但是提供编程式的方式去添加消息源。下面例子展示:
这个例子假设在你的类路径定义有三个资源包分别是format
、exceptions
和windows
。任何解析消息的请求都以jdk标准的方式处理,即通过ResourceBundle
对象解析消息。为了这个例子的目的,假设上面两个资源包文件内容分布如下:
下面例子展示一个程序去执行MessageSource
功能。记住,所有的ApplicationContext
实现也是MessageSources
实现并且能够转换为MessageSource
接口。
上面的程序输出结果是:
总而言之,MessageSource
被定义在叫做beans.xml
的文件中,它存在于你的类路径root下。messageSource
bean定义通过它的basenames属性引用一些资源包。列表中传递给basenames属性的三个文件在类路径的根目录下以文件形式存在并且分别称为format.properties
,exceptions.properties
和windows.properties
。
下一个示例显示传递给消息查找的参数。这些参数被转换为String对象并且在查找消息中插入占位符。
调用execute()
方法输出结果:
关于国际化(i18n
), Spring的各种MessageSource
实现遵循与标准JDK ResourceBundle
相同的语言环境解析和后备规则。简而言之,并继续前面定义的示例messageSource
,如果要针对英国(en-GB
)语言环境解析消息,则可以分别创建名为format_en_GB.properties
,exceptions_en_GB.properties
和windows_en_GB.properties
的文件。
通常,语言环境解析由应用程序的周围环境管理。在以下示例中,手动指定了针对其解析(英国)消息的语言环境:
上面的程序输出结果如下:
你也可以使用MessageSourceAware
接口去获取一个引用任意已经被定义的MessageSource
。任何在ApplicationContext
中定义的bean,当MessageSource
bean被创建且被配置时,实现MessageSourceAware
接口将被注入应用上下文的MessageSource
。
作为
ResourceBundleMessageSource
的替代方法,Spring提供一个ReloadableResourceBundleMessageSource
类。这个变体支持相同包文件格式,但是比基于标准JDK的ResourceBundleMessageSource
实现更灵活。特别是,它允许从任何Spring资源为主读取文件并且支持包属性文件的热加载(同时在它们之间有效地缓存它们)。查看ReloadableResourceBundleMessageSourcejavadoc详细信息。代码示例:
com.liyong.ioccontainer.starter.MessageSourceIocContainer
1.15.2 标准和自定义事件
在ApplicationContext
中事件处理是通过ApplicationEvent
和ApplicationListener
接口提供的。如果bean实现ApplicationListener
接口并且部署到上下文中,则每次将ApplicationEvent
发布到ApplicationContext
时,都会通知该bean。实质上,这是一个标准的观察者模式。
从Spring4.2开始,事件基础设施已经被显著地改善并且提供基于注解的模式以及去发布任意事件的能力(也就是说,对象没有必须要从
ApplicationEvent
拓展)。当发布一个对象时,我们包装为事件。
下面表格描述Spring提供的标准事件:
你也可以创建和发布你自己的自定义事件。下面例子展示了一个简单类,它拓展了Spring的ApplicationEvent
基础类:
去发布自定义ApplicationEvent
,在ApplicationEventPublisher
上调用publishEvent()
方法。通常地,通过创建一个类实现ApplicationEventPublisherAware
接口并且把它注册为Spring的bean。下面的例子展示:
在配置时,Spring容器检测EmailService
实现ApplicationEventPublisherAware
并且自动地调用setApplicationEventPublisher()
方法。事实上,传入的参数是Spring容器本身。你正在通过其ApplicationEventPublisher
接口与应用程序上下文进行交互。
去接受自定义ApplicationEvent
,你可以创建一个类实现ApplicationListener
并且注册它作为Spirng的bean。下面例子展示以一个类:
注意,ApplicationListener
通常是用定制事件的类型参数化的(在前面的例子中是BlackListEvent
)。也就是说onApplicationEvent()
方法能保持类型安全,避免任何转换。你可以注册许多你希望的事件监听,但是注意,默认情况,事件监听接受事件时同步地。也就是说publishEvent()
方法阻塞直到所有监听器完成事件处理。这种同步和单线程方法的一个优点是,当监听器接收到事件时,如果事务上下文可用,它将在发布者的事务上下文中进行操作。
如果有必要采用其他发布事件的策略,查看javadoc对应Spring的ApplicationEventMulticaster接口和SimpleApplicationEventMulticaster实现配置。
下面例子显示bean定义使用去注册和配置每个类;
放到一起,当emailService
bean的sendEmail()
方法被调用时,如果这里任何邮件信息需要被例入黑名单,一个类型为BlackListEvent
自定义事件被发布。blackListNotifier
bean作为ApplicationListener
和接受BlackListEvent
被注册,在这一点上,它可以通知有关各方。
Spring的事件机制被设计为在同一个应用上下文中Spring bean之间的简单通信/交流。然而,对于更复杂的企业集成需求,单独维护的
Spring integration
项目提供了对构建轻量级、面向模式、事件驱动的体系结构的完整支持,这些体系结构构建于著名的Spring编程模型之上。
基于注解事件监听器
从Spring4.2后,你可以在任何通过使用@EventListener
注解的bean,公共方法注册一个事件监听器。BlackListNotifier
可以被重写,像下面例子:
这个方法签名再次声明它监听的事件类型,但是,在这里一个灵活的名字和不需要实现特定监听器接口。只要实际事件类型解析了实现层次结构中的泛型参数,就可以通过泛型缩小事件类型。
如果你的方法需要监听一些事件或者如果你想去定义它为无参数,事件类型也可以在注解自身上指定。下面例子展示怎样去做:
也可以通过使用定义SpEL表达式的注释的condition属性来添加其他运行时过滤器,该注释应匹配以针对特定事件实际调用该方法。
以下示例显示了仅当事件的content属性等于my-event
时,才可以重写我们的通知程序以进行调用:
每个SpEL表达式都会根据专用上下文进行评估。下表列出了上下文可用的项,以便你可以将它们用于条件事件处理。
请注意,即使你的方法签名实际上引用了已发布的任意对象,#root.event也允许你可以访问底层事件。
如果你需要发布一个事件作为处理其它事件结果,你可以改变方法签名去返回事件,类似下面例子:
这个特性对于异步监听是不支持的。
这个新方式通过上面的方法为每个BlackListEvent
处理发布一个新ListUpdateEvent
。如果你需要去发布一些事件,你可以返回一个事件Collection
。
异步事件监听器
如果你想一个特定监听器去处理异步事件,你可以重用常规的@Async
支持。下面例子展示怎样使用:
使用异步事件时,请注意以下限制:
如果一个异步事件监听器抛出
Exception
,它不会被传递到调用者。查看AsyncUncaughtExceptionHandler
更多详情。异步事件监听器方法不能通过返回一个值发布一个后续事件。如果你需要发布处理结果的其它事件,注入ApplicationEventPublisher去手动发布事件。
监听器顺序
如果你需要一个监听器调用在另外一个监听器之前,你可以添加@Order
注解到方法声明上,类似下面例子:
泛型事件
你可以使用泛型去进一步定义你的事件结构。考虑使用EntityCreatedEvent<T>
,其中T
是已创建的实际实体的类型。例如,你可以创建下面的监听器定义,为Person
去接受EntityCreatedEvent
:
由于类型擦除,只有在触发的事件解析事件监听器过滤器所基于的通用参数(即,类似于类PersonCreatedEvent
扩展EntityCreatedEvent<Person>{})
的情况下才可以工作。
在某些情况下,如果所有事件都遵循相同的结构,这可能会变得很乏味(就像前面示例中的事件一样)。在这种情况下,你可以实现ResolvableTypeProvider
来指导框架,使其超出运行时环境提供的范围(备注:通过ResolvableTypeProvider
提供更多类相关信息)。下面的事件说明了如何做到这一点:
这不仅适用于
ApplicationEvent
,而且适用于你作为事件发送的任何任意对象。
1.15.3 便捷地访问低级别资源
为了更好的使用和理解应用上下文,你应该熟悉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中加载定义,而不管实际的上下文类型如何。
1.15.4 Web应用的ApplicationContext实例化
你可以通过声明式地创建ApplicationContext
实例,例如:ApplicationContext
。当然,你也可以通过使用ApplicationContext
实现之一编程式地创建ApplicationContext
实例。
你可以通过使用ContextLoaderListener
注册一个ApplicationContext
,类似下面例子:
监听器检查contextConfigLocation
参数。如果参数不存在,监听器使用/WEB-INF/applicationContext.xml
作为默认值。当参数存在时,监听器通过使用预先定义的分隔符和使用值作为应用上下文搜索的路径的分隔字符串。Ant格式路径模式也支持很好。示例包括/WEB-INF/*Context.xml
(适用于所有名称以Context.xml
结尾且位于WEB-INF
目录中的文件)和/WEB-INF/**/*Context.xml
(适用于所有此类文件)文件在WEB-INF
的任何子目录中)。
1.15.5 部署SpringApplicationContext
作为Java EE rar文件
可以将Spring ApplicationContext
部署为RAR文件,并将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这等效于引导独立的ApplicationContext
(仅托管在Java EE环境中)能够访问Java EE服务器功能。RAR部署是部署无头WAR文件的方案的一种更自然的选择,实际上,这种WAR文件没有任何HTTP入口点,仅用于在Java EE环境中引导Spring ApplicationContext
。RAR部署非常适合于不需要HTTP入口点,而只由消息端点和调度任务组成的应用程序上下文。在上下文中的这些bean能使用应用服务资源,例如,JTA事物管理、JNDI绑定JDBC DataSource
实例、JMS ConnectionFactory
实例和注册平台的JMX服务-通过所有Spring的标准事物管理、JNDI、JMX支持的设施。应用组件也可以通过Spring的TaskExecutor
抽象与应用server的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文件拖放到应用程序服务器的部署目录中
此类RAR部署单位通常是独立的。它们不会将组件暴露给外界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的
ApplicationContext
的交互通常是通过与其他模块共享的JMS目标进行的。例如,基于rar的ApplicationContext
还可以调度一些作业或对文件系统中的新文件(或类似的东西)作出反应。如果它需要允许来自外部的同步访问,它可以(例如)导出RMI端点,这些端点可以由同一台机器上的其他应用程序模块使用。
1.16 BeanFactory
BeanFactory
API为Spring IoC工厂提供底层基础。它的特定契约主要用于与Spring的其他部分和相关第三方框架的集成,它的DefaultListableBeanFactory
实现是更高级别的GenericApplicationContext
容器中的一个关键代理。
BeanFactory
和相关的接口(例如:BeanFactoryAware
、InitializingBean
、DisposableBean
)是为其他框架组件非常重要的集成点。通过不需要任何注解,甚至不需要反射,它们可以在容器及其组件之间进行非常有效的交互。应用级别bean可能使用相同回调接口,但通常更喜欢通过注释或通过编程配置进行声明式依赖注入。
请注意,核心BeanFactory
API级别及其DefaultListableBeanFactory
实现不对配置格式或要使用的任何组件注释进行假设。所有这些风格都通过扩展(比如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
)实现,并在共享的BeanDefinition
对象上进行操作,作为核心元数据表示。这是使Spring的容器如此灵活和可扩展的本质所在。
1.16.1 BeanFactory
或ApplicationContext
本节说明BeanFactory
和ApplicationContext
容器级别之间的区别以及对引导的影响。
除非有充分的理由,否则应使用ApplicationContext
,除非将GenericApplicationContext
和其子类AnnotationConfigApplicationContext
作为自定义引导的常见实现,否则应使用ApplicationContext
。这些是用于所有常见目的的Spring核心容器的主要入口点:加载配置文件、触发类路径扫描、以编程方式注册Bean定义和带注解的类,以及(从5.0版本开始)注册功能性Bean定义。
因为ApplicationContext
包含BeanFactory
的所有功能,所以通常建议在普通BeanFactory中使用,除非需要完全控制Bean处理的方案。在ApplicationContext
(例如GenericApplicationContext
实现)中,按照约定(即,按bean名称或按bean类型(尤其是后处理器))检测到几种bean,而普通的DefaultListableBeanFactory
则与任何特殊bean无关。
对于许多扩展的容器功能,例如注解处理和AOP代理,BeanPostProcessor
扩展点是必不可少的。如果仅使用普通的DefaultListableBeanFactory
,则默认情况下不会检测到此类后处理器并将其激活。这种情况可能会造成混淆,因为你的bean配置实际上并没有错。而是在这种情况下,需要通过其他设置完全引导容器。
下表列出了BeanFactory
和ApplicationContext
接口和实现提供的功能。
要向DefaultListableBeanFactory
显式注册Bean后处理器,需要以编程方式调用addBeanPostProcessor
,如以下示例所示:
要将BeanFactoryPostProcessor
应用于普通的DefaultListableBeanFactory
,你需要调用其postProcessBeanFactory
方法,如以下示例所示:
在这两种情况下,显式的注册步骤都不方便,这就是为什么在Spring支持的应用程序中,各种ApplicationContext
变量比普通的DefaultListableBeanFactory
更为可取的原因,尤其是在典型企业设置中依赖BeanFactoryPostProcessor
和BeanPostProcessor
实例来扩展容器功能时。
AnnotationConfigApplicationContext
已注册了所有常见的注解后处理器,并且可以通过配置注解(例如@EnableTransactionManagement
)在幕后引入其他处理器。在Spring基于注解的配置模型的抽象级别上,bean后处理器的概念仅是内部容器详细信息。
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公众号:
版权声明: 本文为 InfoQ 作者【青年IT男】的原创文章。
原文链接:【http://xie.infoq.cn/article/2531020fad05110e754bea569】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论