Spring Framework核心技术(Core Technologies)-(三)

1.14. ApplicationContext的额外功能

正如在介绍章节所讨论的,org.springframework.beans.factory包提供对管理和维护bean基本功能,包括使用程序化的方式。org.springframework.context包添加ApplicationContext接口,其扩展了BeanFactory接口,此外还扩展其他接口,以更面向应用程序的风格提供额外的功能。许多人以完全声明式地风格使用ApplicationContext,甚至不会程序化地创建它,而是依赖像是ContextLoader这样支持的类来自动化实例一个ApplicationContext作为Jakarta EE web应用程序普通启动过程的一部分。

要以更加应用程序化的风格来改善BeanFactory功能,上下文包也提供以下功能:

  • 以i18n格式访问信息,通过MessageSource接口。
  • 访问资源,例如URL和文件,通过ResourceLoader接口
  • 事件发布,即实现ApplicationListener接口的bean,通过使用ApplicationEventPublisher接口。
  • 多(分层)上下文加载,让每一个关注一个特定的层次,例如应用程序web层,通过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实现,ResourceBundleMessageSourceReloadableResourceBundleMessageSourceStaticMessageSource。他们三个所有实现了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,其存在于你的类路径根路径。messageSourcebean定义通过它的beasenames属性依赖多个资源包。列表中的三个文件被传入到basenames属性作为你的根类路径文件存在,依次被称为format.propertiesexceptions.propertieswindows.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()方法启动时发布。这里,“启动”意味着所有Lifecyclebean接收了一个明确的启动信号。通常,这个信号用于在一个明确停止之后重启bean,而且它也可以用于启动那么没有配置自动启动的组件。(例如在初始化阶段没有启动的组件)
ContextStoppedEvent ApplicationContext通过ConfigurableApplicationContext接口上使用stop()方法停止时发布。这里,“停止”意味着所有Lifecyclebean接收一个明显的停止信号。停止的上下文可以通过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>

将他们放到一起,当调用emailServicebean的sendEmail()方法时,如果存在任何应该阻塞的email消息,类型BlockedListEvent的自定义事件则发布。blockedListNotifierbean被注册为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是实际创建的实体类型。例如,你可以创建以下监听器定义来接收一个PersonEntityCreatedEvent

@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数据:

  • 应用程序上下文周期(基础包扫描,配置类管理)
  • bean生命周期(实例化,初始化,前缀处理)
  • 应用程序事件处理

以下是在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使用,可能会有变化。

方便的ApplicationContext实例化Web应用程序

你可以声明式地(例如,通过使用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所有子目录所有这样的文件)。

部署一个Spring 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文件:

  1. 打包所有应用程序类到RAR文件(其是一个带有不同文件扩展名的标准的JAR文件)。
  2. 将所有需要的类库JAR添加到RAR归档的跟路径。
  3. 添加一个META-INF/ra.xml部署描述符(正如在SpringContextResourceAdapter的javadoc所展示的)和对应的Spring XML bean定义文件(通常META-INF/applicationContext.xml)。
  4. 将最终的RAR文件放入到你的应用程序服务器的部署目录。

这样RAR部署单元通常是自包含的。他们不会暴露组件到外部,甚至不会暴露到相同应用程序的其他模块。与基于RARApplicationContext交互通常通过与其他模块分享JMS端点进行。例如,一个基于RAR的ApplicationContext也可以调度一些job或者对文件系统中(或者类似的)的新文件作出反应。如果它需要允许从外部同步访问,例如,它可以导出RMI端点,其通常用于在相同的机器上的其他应用程序模块。

1.15. BeanFactory API

BeanFactory API提供Spring的IOC功能的底层基础。它的明确的合约差不多用于与Spring的其他部分和第三方框架交互,并且它的DefaultListableBeanFactory实现是在高级别GenericApplicationContext容器内部的一个关键委托。

BeanFactory和相关接口(例如BeanFactoryAware,InitializingBean,DisposableBean)是与其他框架组件非常的重要的交互点。由于不需要任何注解甚至反射,他们允许容器和它的组件之间进行非常高效的交互。应用程序级别的bean可能适用相同的回调接口,但是通常优先使用依赖注入代替,或者通过注解或者通过程序化配置。

注意,核心BeanFactory API级别和它的DefaultListableBeanFactory实现不会对要使用的配置格式或者任何组件注解作出假设。所有的这些风格都是通过扩展(例如XmlBeanDefinitionReaderAutowiredAnnotationBeanPostProcessor)实现的,共享的BeanDefinition对象作为核心的元数据展现进行操作。这是使Spring的容器如此灵活和扩展性的本质。

BeanFactory或者ApplicationContext

此部分解释了BeanFactoryApplicationContext容器级别之间的不同,以及他们对引导启动的影响。

你应该使用一个ApplicationContext,除非你有一个充分的原因不使用它,使用GenericApplicationContext和它的子类AnnotationConfigApplicationContext作为常用的自定义引导实现。这些是Spring的核心容器的核心的入口点,用于:配置文件加载,触发类路径扫描,程序化注册bean定义和注解的类,并且(从5.0开始)注册函数性bean定义。

因为ApplicationContext包含所有的BeanFactory的功能,所以除了需要完全控制bean处理的场景,通常建议使用它,而不是普通的BeanFactory。在ApplicationContext内部(例如GenericApplicationContext实现),几种bean通过约定检测(即,通过bean名称或者bean类型–特别是前置处理),而普通的DefaultListableBeanFactory不知道任何特殊的bean。

对于许多扩展的容器特性,例如注解过程和AOP代理,BeanPostProcessor扩展点是必不可少的。如果你只使用普通的DefaultListableBeanFactory,这样前置处理不会得到检测并默认激活。此情况可能是疑惑的,因为你的bean配置没有实际报错。然而,在这样的一个情况下,需要通过额外的设置来完全引导容器。

以下表格列出来BeanFactoryApplicationContext接口和实现提供的特性:
|特性| 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的原因,特别是在典型的企业级设置依赖BeanFactoryPostProcessorBeanPostProcessor实例来扩展容器功能时。

AnnotationConfigApplicationContext已经注册了所有常用的注解前置处理并且通过配置注解引入了额外的处理器,例如@EnableTransactionManagement。在Spring的基于注解的配置模型抽象层,bean后置处理的概念变成了仅仅是内容容器细节。

你可能感兴趣的:(spring,java,后端)