当前位置: 代码迷 >> 综合 >> Spring Core 之 Additional Capabilities of the ApplicationContext(ApplicationContext的其他功能)
  详细解决方案

Spring Core 之 Additional Capabilities of the ApplicationContext(ApplicationContext的其他功能)

热度:76   发布时间:2023-12-22 14:40:37.0

文章目录

  • 一、Internationalization using MessageSource(使用MessageSource进行国际化)
  • 二、Standard and Custom Events(标准事件和自定义事件)
    • 1、Annotation-based Event Listeners(基于注解的事件监听)
    • 2、Asynchronous Listeners(异步监听)
    • 3、Ordering Listeners(顺序监听)
  • 三、Convenient Access to Low-level Resources(便捷的访问下级资源)
  • 四、Application Startup Tracking(应用启动跟踪)

一、Internationalization using MessageSource(使用MessageSource进行国际化)

The ApplicationContext interface extends an interface called MessageSource and, therefore, provides internationalization (“i18n”) functionality. Spring also provides the HierarchicalMessageSource interface, which can resolve messages hierarchically. Together, these interfaces provide the foundation upon which Spring effects message resolution. The methods defined on these interfaces include:

  • String getMessage(String code, Object[] args, String default, Locale loc): The basic method used to retrieve a message from the MessageSource. When no message is found for the specified locale, the default message is used. Any arguments passed in become replacement values, using the MessageFormat functionality provided by the standard library.
  • String getMessage(String code, Object[] args, Locale loc): Essentially the same as the previous method but with one difference: No default message can be specified. If the message cannot be found, a NoSuchMessageException is thrown.
  • String getMessage(MessageSourceResolvable resolvable, Locale locale): All properties used in the preceding methods are also wrapped in a class named MessageSourceResolvable, which you can use with this method.

ApplicationContext接口也继承了MessageSource接口,因此提供有国际化功能。Spring提供了HierarchicalMessageSource 接口,能够解析消息层次。这些接口一起使用时,提供了Spring消息解析的基础,这些接口中定义的方法有:

  • String getMessage(String code,Object[] args,String default,Locale loc):用于从MessageSource检索一个消息的基本方法,当指定的locale没有找到消息时,将使用默认消息。使用标准库提供的 MessageFormat 功能,传入的任何参数都成为替换值。
  • String getMessage(String code,Object[] args,Locale loc):和上一个方法基本相同,只有一个不同点,不能指定默认消息,如果没有找到消息,将抛出NoSuchMessageException 异常。
  • String getMessage(MessageSourceResolvable resolvable,Locale locale):在前面的方法的参数全部都被封装到了MessageSourceResolvable,你可以在此方法中使用它。

When an ApplicationContext is loaded, it automatically searches for a MessageSource bean defined in the context. The bean must have the name messageSource. If such a bean is found, all calls to the preceding methods are delegated to the message source. If no message source is found, the ApplicationContext attempts to find a parent containing a bean with the same name. If it does, it uses that bean as the MessageSource. If the ApplicationContext cannot find any source for messages, an empty DelegatingMessageSource is instantiated in order to be able to accept calls to the methods defined above.Spring provides three MessageSource implementations, ResourceBundleMessageSource, ReloadableResourceBundleMessageSource and StaticMessageSource. All of them implement HierarchicalMessageSource in order to do nested messaging. The StaticMessageSource is rarely used but provides programmatic ways to add messages to the source. The following example shows ResourceBundleMessageSource:

当ApplicationContext被加载,它会自动搜索定义在上下文中的MessageSourcebean。该bean的名称必须为messageSource,如果找到了这个bean,对上述方法的所有调用都委派给该message source,如果没有message source被找到,ApplicationContext将尝试寻找包含相同名称(messageResource)bean的父bean,找到了就用来作为MessageSource。如果还是没找到,将实例化一个空的DelegatingMessageSource ,以便能够接受上述方法的调用。Spring提供了三个MessageSource实现,ResourceBundleMessageSource,ReloadableResourceBundleMessageSource 和StaticMessageSource。它们都实现了HierarchicalMessageSource,以便进行嵌套消息传递。StaticMessageSource 很少使用,但提供了向源添加消息的编程方式。以下示例显示了 ResourceBundleMessageSource:

@Configuration
public class BeanConfig {
    @Beanpublic MessageSource getMessageSource(){
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();messageSource.setBasenames("format","exceptions","windows");messageSource.setDefaultEncoding("UTF-8");return messageSource;}
}

The example assumes that you have three resource bundles called format, exceptions and windows defined in your classpath. Any request to resolve a message is handled in the JDK-standard way of resolving messages through ResourceBundle objects. For the purposes of the example, assume the contents of two of the above resource bundle files are as follows:

本例中假设你的类路径下有三个分别名为format、exceptions和Windows的properties文件。任何解析消息的请求都以 JDK 标准的方式通过 ResourceBundle 对象解析消息来处理。出于示例的目的,假设上述两个资源包文件的内容如下:

 # in format.propertiesmessage=Alligators rock!
 # in exceptions.propertiesargument.required=The {0} argument is required.

The next example shows a program to run the MessageSource functionality. Remember that all ApplicationContext implementations are also MessageSource implementations and so can be cast to the MessageSource interface.

下一个示例显示了一个运行 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);
}

The next example shows arguments passed to the message lookup. These arguments are converted into String objects and inserted into placeholders in the lookup message.

下一个示例显示传递给消息查找的参数。这些参数被转换为 String 对象并插入到查找消息中的占位符中。

@Component
public class Example {
    private MessageSource messages;@Autowiredpublic 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.

With regard to internationalization (“i18n”), Spring’s various MessageSource implementations follow the same locale resolution and fallback rules as the standard JDK ResourceBundle. In short, and continuing with the example messageSource defined previously, if you want to resolve messages against the British (en-GB) locale, you would create files called format_en_GB.properties, exceptions_en_GB.properties, and windows_en_GB.properties, respectively.

关于国际化(“i18n”),Spring 的各种 MessageSource 实现遵循与标准 JDK ResourceBundle 相同的区域设置解析和回调规则。简而言之,继续之前定义的示例 messageSource,如果你想针对英国 (en-GB) 语言环境解析消息,你可以分别创建名为 format_en_GB.properties、exceptions_en_GB.properties 和 windows_en_GB.properties 的文件。如果想针对中文语言环境,则分别创建名为 format_zh.properties、exceptions_zh.properties 和 windows_zh.properties 的文件。因此,在getMessage方法中传入locale为Locale.UK时,就会从format_en_GB.properties、exceptions_en_GB.properties 和 windows_en_GB.properties 搜索消息,而传入locale为Locale.CHINES时,就会从 format_zh.properties、exceptions_zh.properties 和 windows_zh.properties 搜索消息,如果传入的locale为null,将使用系统默认的locale,通常就是所在地区语言,例如中国地区就是Locale.CHINES或Locale.CHINA。

二、Standard and Custom Events(标准事件和自定义事件)

Event handling in the ApplicationContext is provided through the ApplicationEvent class and the ApplicationListener interface. If a bean that implements the ApplicationListener interface is deployed into the context, every time an ApplicationEvent gets published to the ApplicationContext, that bean is notified. Essentially, this is the standard Observer design pattern.

事件处理在ApplicationContext中通过ApplicationEvent类和ApplicationListener接口提供。如果一个bean实现了ApplicationListener接口并部署到容器上下文中,每次有ApplicationEvent被发布到ApplicationContext时,该bean就会收到通知。本质上来说,这是一个监听者设计模式。

You can also create and publish your own custom events. The following example shows a simple class that extends Spring’s ApplicationEvent base class:

你可以创建和发布自己的自定义事件。以下示例显示了一个扩展 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;}public String getAddress() {
    return address;}public String getContent() {
    return content;}
}

To publish a custom ApplicationEvent, call the publishEvent() method on an ApplicationEventPublisher. Typically, this is done by creating a class that implements ApplicationEventPublisherAware and registering it as a Spring bean. The following example shows such a class:

为了发布一个自定义ApplicationEvent,需要调用ApplicationEventPublisher的publishEvent()方法。通常是通过实现ApplicationEventPublisherAware 并将其注册为一个spring的bean。以下示例显示了这样一个类:

@Service
public class EmailService implements ApplicationEventPublisherAware {
    private List<String> blockedList;private ApplicationEventPublisher publisher;public void setBlockedList(List<String> blockedList) {
    this.blockedList = blockedList;}@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    this.publisher = applicationEventPublisher;}public void sendEmail(String address,String content){
    if(blockedList.contains(address)){
    publisher.publishEvent(new BlockedListEvent(this,address,content));return;}System.out.println("send email==============>" + content);}
}

To receive the custom ApplicationEvent, you can create a class that implements ApplicationListener and register it as a Spring bean. The following example shows such a class:

要接收自定义 ApplicationEvent,你可以创建一个实现 ApplicationListener 的类并将其注册为 Spring bean。以下示例显示了这样一个类:

@Component
public class SpringEventListener implements ApplicationListener<BlockedListEvent> {
    @Overridepublic void onApplicationEvent(BlockedListEvent blockedListEvent) {
    System.out.println("BlockedListEvent =====> " + blockedListEvent.getAddress());}
}

效果测试:

@ComponentScan
public class SpringBootPracticeApplication {
    public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringBootPracticeApplication.class);List<String> list = new ArrayList<>();list.add("known.spammer@example.org");list.add("known.hacker@example.org");list.add("john.doe@example.org");EmailService service = (EmailService) context.getBean("emailService");service.setBlockedList(list);service.sendEmail("john.doe@example.org","send email");}}

Notice that ApplicationListener is generically parameterized with the type of your custom event (BlockedListEvent in the preceding example). This means that the onApplicationEvent() method can remain type-safe, avoiding any need for downcasting. You can register as many event listeners as you wish, but note that, by default, event listeners receive events synchronously. This means that the publishEvent() method blocks until all listeners have finished processing the event. One advantage of this synchronous and single-threaded approach is that, when a listener receives an event, it operates inside the transaction context of the publisher if a transaction context is available.

请注意,ApplicationListener 通常使用自定义事件的类型(在前面的示例中为 BlockedListEvent)进行参数化。这意味着 onApplicationEvent() 方法可以保持类型安全,避免任何向下转换。你可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,事件侦听器会同步接收事件。这意味着 publishEvent() 方法会阻塞,直到所有侦听器都完成对事件的处理。这种同步和单线程方法的一个优点是,当侦听器接收到事件时,如果事务上下文可用,它就会在发布者的事务上下文中运行。

The following list describes the standard events that Spring provides:

下表描述了 Spring 提供的标准事件:

  • ContextRefreshedEvent:在 ApplicationContext 初始化或刷新时发布(例如,通过使用 ConfigurableApplicationContext 接口上的 refresh() 方法)。
  • ContextStartedEvent:使用 ConfigurableApplicationContext 接口上的 start() 方法启动 ApplicationContext 时发布。
  • ContextStoppedEvent:在通过使用 ConfigurableApplicationContext 接口上的 stop() 方法停止 ApplicationContext 时发布。
  • ContextClosedEvent:在通过使用 ConfigurableApplicationContext 接口上的 close() 方法或通过 JVM 关闭挂钩关闭 ApplicationContext 时发布。
  • RequestHandledEvent:此事件在请求完成后发布。此事件仅适用于使用 了Spring DispatcherServlet 的 Web 应用程序。
  • ServletRequestHandledEvent:RequestHandledEvent 的子类,用于添加特定于 Servlet 的上下文信息。

1、Annotation-based Event Listeners(基于注解的事件监听)

You can register an event listener on any method of a managed bean by using the @EventListener annotation. The SpringEventListener can be rewritten as follows:

你可以使用 @EventListener 注解在被容器管理的 bean 的任何方法上注册事件侦听器。 SpringEventListener 可以重写如下:

@Component
public class SpringEventListener  {
    @EventListenerpublic void processBlockedListEvent(BlockedListEvent event) {
    System.out.println("BlockedListEvent =====> " + event.getAddress());}}

The method signature once again declares the event type to which it listens, but, this time, with a flexible name and without implementing a specific listener interface. The event type can also be narrowed through generics as long as the actual event type resolves your generic parameter in its implementation hierarchy.If your method should listen to several events or if you want to define it with no parameter at all, the event types can also be specified on the annotation itself. The following example shows how to do so:

方法参数声明了它监听的事件类型,但是,这一次,使用灵活的名称并且没有实现特定的侦听器接口。只要实际事件类型在其实现层次结构中解析你的泛型参数,也可以通过泛型缩小事件类型。如果你的方法应该侦听多个事件,或者你想定义它时根本不带参数,则还可以在注释本身上指定事件类型。以下示例显示了如何执行此操作:

@EventListener({
    ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
    // ...
}

It is also possible to add additional runtime filtering by using the condition attribute of the annotation that defines a SpEL expression , which should match to actually invoke the method for a particular event.The following example shows how our notifier can be rewritten to be invoked only if the content attribute of the event is equal to my-event:

还可以通过使用 SpEL 表达式在注解的条件属性来添加额外的运行时过滤,该属性会匹配满足条件的实际调用事件。以下示例显示了如何重写我们的通知程序以仅在事件的内容属性等于 my-event 时才被调用:

@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

2、Asynchronous Listeners(异步监听)

If you want a particular listener to process events asynchronously, you can reuse the regular @Async support. The following example shows how to do so:

如果你希望特定的侦听器异步处理事件,则可以使用常规的 @Async 进行支持。以下示例显示了如何执行此操作:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
    // BlockedListEvent is processed in a separate thread
}

Be aware of the following limitations when using asynchronous events:

  • If an asynchronous event listener throws an Exception, it is not propagated to the caller.
  • Asynchronous event listener methods cannot publish a subsequent event by returning a value. If you need to publish another event as the result of the processing, inject an ApplicationEventPublisher to publish the event manually.

使用异步事件时请注意以下限制:

  • 如果异步事件侦听器抛出异常,则不会将其传播给调用者。
  • 异步事件侦听器方法无法通过返回值来发布后续事件。如果您需要发布另一个事件作为处理结果,请注入 ApplicationEventPublisher 以手动发布事件。

3、Ordering Listeners(顺序监听)

If you need one listener to be invoked before another one, you can add the @Order annotation to the method declaration, as the following example shows:

如果你需要在另一个侦听器之前调用一个侦听器,则可以在方法声明中添加 @Order 注释,如下例所示:

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
    // notify appropriate parties via notificationAddress...
}

三、Convenient Access to Low-level Resources(便捷的访问下级资源)

An application context is a ResourceLoader, which can be used to load Resource objects. A Resource is essentially a more feature rich version of the JDK java.net.URL class. In fact, the implementations of the Resource wrap an instance of java.net.URL, where appropriate. A Resource can obtain low-level resources from almost any location in a transparent fashion, including from the classpath, a filesystem location, anywhere describable with a standard URL, and some other variations. If the resource location string is a simple path without any special prefixes, where those resources come from is specific and appropriate to the actual application context type.

一个应用上下文就是一个ResourceLoader,用于加载Resource 对象。一个Resource本质上是JDK java.net.URL类的功能增强版。事实上,Resource的实现类在合适的地方可以转换为一个JDK java.net.URL实例对象。一个Resource能够从大部分位置透明的获取低级资源,包括从类路径,文件系统中的地址,标准URL等等。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源的来源是特定的并且适合实际的应用程序上下文类型。

You can configure a bean deployed into the application context to implement the special callback interface, ResourceLoaderAware, to be automatically called back at initialization time with the application context itself passed in as the ResourceLoader. You can also expose properties of type Resource, to be used to access static resources. They are injected into it like any other properties. You can specify those Resource properties as simple String paths and rely on automatic conversion from those text strings to actual Resource objects when the bean is deployed.

你可以通过实现特殊的ResourceLoaderAware接口并提供来配置一个bean部署到应用上下文,在应用上下文初始化时将自动调用,且应用上下文自身作为ResourceLoader传入。你还可以暴露Resource 类型的属性,用于访问静态资源。它们像任何其他属性一样被注入其中。可以将这些 Resource 属性指定为简单的 String 路径,并在部署 bean 时依赖于从这些文本字符串到实际 Resource 对象的自动转换。

The location path or paths supplied to an ApplicationContext constructor are actually resource strings and, in simple form, are treated appropriately according to the specific context implementation. For example ClassPathXmlApplicationContext treats a simple location path as a classpath location. You can also use location paths (resource strings) with special prefixes to force loading of definitions from the classpath or a URL, regardless of the actual context type.

提供给 ApplicationContext 构造函数的位置路径实际上是资源位置描述字符串,并且以简单的形式根据特定的上下文实现进行适当处理。例如 ClassPathXmlApplicationContext 将简单的位置路径视为类路径位置。你也可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或 URL 加载定义,而不管实际的上下文类型。

四、Application Startup Tracking(应用启动跟踪)

Tracking the application startup steps with specific metrics can help understand where time is being spent during the startup phase, but it can also be used as a way to better understand the context lifecycle as a whole.The AbstractApplicationContext (and its subclasses) is instrumented with an ApplicationStartup, which collects StartupStep data about various startup phases:

  • application context lifecycle (base packages scanning, config classes management)
  • beans lifecycle (instantiation, smart initialization, post processing)
  • application events processing

Here is an example of instrumentation in the AnnotationConfigApplicationContext:

根据特殊的指标跟踪应用启动阶段能够帮助理解在启动阶段的各种用时,也可以用来更好的理解整个上下文周期。AbstractApplicationContext 和它的子类通过ApplicationStartup被仪表化,它收集各个启动阶段的 StartupStep 数据:

  • 应用程序上下文生命周期(基本包扫描、配置类管理)
  • bean 生命周期(实例化、智能初始化、后处理) - 应用事件处理
    应用事件处理

这是一个 AnnotationConfigApplicationContext 中的记录示例:

 public AnnotationConfigApplicationContext(String... basePackages) {
    StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");this.reader = new AnnotatedBeanDefinitionReader(this);createAnnotatedBeanDefReader.end();this.scanner = new ClassPathBeanDefinitionScanner(this);Assert.notEmpty(basePackages, "At least one base package must be specified");StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan").tag("packages", () -> {
    return Arrays.toString(basePackages);});this.scanner.scan(basePackages);scanPackages.end();this.refresh();}

The application context is already instrumented with multiple steps. Once recorded, these startup steps can be collected, displayed and analyzed with specific tools. The default ApplicationStartup implementation is a no-op variant, for minimal overhead. This means no metrics will be collected during application startup by default. Spring Framework ships with an implementation for tracking startup steps with Java Flight Recorder: FlightRecorderApplicationStartup. To use this variant, you must configure an instance of it to the ApplicationContext as soon as it’s been created.To start collecting custom StartupStep, components can either get the ApplicationStartup instance from the application context directly, make their component implement ApplicationStartupAware, or ask for the ApplicationStartup type on any injection point.

应用程序上下文已经通过多个步骤进行了检测。记录后,可以使用特定工具收集、显示和分析这些启动步骤。默认的 ApplicationStartup 实现是无操作变体,以实现最小的开销。这意味着默认情况下不会在应用程序启动期间收集任何指标。Spring Framework 附带了一个使用 Java Flight Recorder 跟踪启动步骤的实现:FlightRecorderApplicationStartup。要使用此变体,您必须在创建后立即将其实例配置到 ApplicationContext。要开始收集自定义的 StartupStep,组件可以直接从应用程序上下文中获取 ApplicationStartup 实例,通过使他们的组件实现 ApplicationStartupAware,或者在任何注入点上请求 ApplicationStartup 类型。

  相关解决方案