2.15 Spring Framework 5.x 之ApplicationContext附加功能

1.15 附加功能ApplicationContext

正如章节介绍中所讨论的,该org.springframework.beans.factory 包提供了管理和操作bean的基本功能,包括以编程方式。除了扩展其他接口以外 ,该org.springframework.context软件包还添加了 ApplicationContext扩展BeanFactory接口的接口,以更加面向应用程序框架的方式提供附加功能。许多人ApplicationContext以完全声明的方式使用它,甚至不以编程方式创建它,而是依赖于支持类,例如ContextLoader自动实例化 ApplicationContext作为Java EE Web应用程序的正常启动过程的一部分。

为了BeanFactory以更加面向框架的样式增强功能,上下文包还提供以下功能:

  • 通过MessageSource界面访问i18n风格的消息。

  • 通过ResourceLoader界面访问URL和文件等资源。

  • 事件发布,即ApplicationListener通过使用接口实现接口的bean ApplicationEventPublisher。

加载多个(分层)上下文,让每个上下文通过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则抛出a。

  • String getMessage(MessageSourceResolvable resolvable, Locale locale):前面方法中使用的所有属性也包装在一个名为的类中 MessageSourceResolvable,您可以使用此方法。

当ApplicationContext被加载时,它自动搜索MessageSource 在上下文中定义的bean。bean必须具有名称messageSource。如果找到这样的bean,则对前面方法的所有调用都被委托给消息源。如果未找到任何消息源,则ApplicationContext尝试查找包含具有相同名称的bean的父级。如果是,它将使用该bean作为MessageSource。如果 ApplicationContext找不到任何消息源,DelegatingMessageSource则实例化为空 以便能够接受对上面定义的方法的调用。

春天提供了两种MessageSource实现方式,ResourceBundleMessageSource和 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 在类路径中定义。解决消息的任何请求都以JDK标准的方式处理,通过ResourceBundle对象解析消息。出于示例的目的,假设上述两个资源包文件的内容如下:

# 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", null);
    System.out.println(message);
}

上述程序产生的结果如下:

Alligators rock!

总而言之,它MessageSource是在一个名为的文件中定义的,该文件beans.xml存在于类路径的根目录中。该messageSourcebean定义是指通过它的一些资源包的basenames属性。这是在列表中传递的三个文件basenames属性存在于你的classpath根目录的文件,被称为format.properties,exceptions.properties和 windows.properties分别。

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

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <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", null);
        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已定义的任何引用的引用 。在创建和配置bean时,将使用应用程序上下文注入ApplicationContext实现MessageSourceAware接口的任何bean MessageSource。

作为替代ResourceBundleMessageSource,Spring提供了一个 ReloadableResourceBundleMessageSource类。此变体支持相同的包文件格式,但比基于标准JDK的ResourceBundleMessageSource实现更灵活 。特别是,它允许从任何Spring资源位置(不仅从类路径)读取文件,并支持bundle属性文件的热重新加载(同时在其间有效地缓存它们)。有关ReloadableResourceBundleMessageSource 详细信息,请参阅javadoc。

1.15.2 标准和自定义事件

ApplicationContext通过ApplicationEvent 类和ApplicationListener接口提供事件处理。如果将实现ApplicationListener接口的bean 部署到上下文中,则每次 ApplicationEvent将其发布到该ApplicationContextbean时,都会通知该bean。从本质上讲,这是标准的Observer设计模式。

从Spring 4.2开始,事件基础结构得到了显着改进,并提供了基于注释的模型以及发布任意事件(即不一定从中扩展的对象ApplicationEvent)的能力。当发布这样的对象时,我们将它包装在一个事件中。

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

表7.内置事件

事件 说明
ContextRefreshedEvent ApplicationContext初始化或刷新时发布(例如,通过refresh()在ConfigurableApplicationContext接口上使用该方法)。这里,“初始化”意味着加载所有bean,检测并激活后处理器bean,预先实例化单例,并且ApplicationContext对象已准备好使用。只要上下文尚未关闭,只要所选择的ApplicationContext实际支持这种“热”刷新,就可以多次触发刷新。例如,XmlWebApplicationContext支持热刷新,但GenericApplicationContext不支持 。
ContextStartedEvent ApplicationContext通过start()在ConfigurableApplicationContext接口上使用方法 启动时发布。这里,“已启动”意味着所有Lifecycle bean都会收到明确的启动信号。通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未为自动启动配置的组件(例如,在初始化时尚未启动的组件)。
ContextStoppedEvent ApplicationContext通过stop()在ConfigurableApplicationContext接口上使用方法 停止时发布。这里,“停止”意味着所有Lifecycle bean都会收到明确的停止信号。可以通过start()呼叫重新启动已停止的上下文
ContextClosedEvent ApplicationContext通过close()在ConfigurableApplicationContext接口上使用方法 关闭时发布。这里,“关闭”意味着所有单例bean都被销毁。封闭的环境达到其寿命终结。它无法刷新或重新启动。
RequestHandledEvent 一个特定于Web的事件,告诉所有bean已经为HTTP请求提供服务。请求完成后发布此事件。此事件仅适用于使用Spring的Web应用程序DispatcherServlet。

您还可以创建和发布自己的自定义事件。以下示例显示了一个扩展Spring的ApplicationEvent基类的简单类:

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...
}

要发布自定义ApplicationEvent,请在publishEvent()方法上调用该方法 ApplicationEventPublisher。通常,这是通过创建一个实现ApplicationEventPublisherAware并将其注册为Spring bean 的类来完成的 。以下示例显示了这样一个类:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> 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容器检测到EmailService实现 ApplicationEventPublisherAware并自动调用 setApplicationEventPublisher()。实际上,传入的参数是Spring容器本身。您正通过其ApplicationEventPublisher界面与应用程序上下文进行 交互。

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

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }
}

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

以下示例显示了用于注册和配置上述每个类的bean定义:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>[email protected]</value>
            <value>[email protected]</value>
            <value>[email protected]</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="[email protected]"/>
</bean>

总而言之,当调用bean 的sendEmail()方法时emailService,如果有任何应列入黑名单的电子邮件消息,BlackListEvent则会发布类型的自定义事件 。该blackListNotifierbean被注册为 ApplicationListener与接收BlackListEvent,此时它可以通知有关各方。

Spring的事件机制是为在同一应用程序上下文中的Spring bean之间的简单通信而设计的。但是,对于更复杂的企业集成需求,单独维护的 Spring Integration项目为构建基于众所周知的Spring编程模型的轻量级,面向模式,事件驱动的体系结构提供了完整的支持 。

基于注释的事件监听器
从Spring 4.2开始,您可以使用EventListener注释在托管bean的任何公共方法上注册事件侦听器。该BlackListNotifier可改写如下:

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() {
    ...
}

还可以通过使用condition定义SpEL表达式的注释的属性来添加额外的运行时过滤,该属性应该匹配以实际调用特定事件的方法。

以下示例显示了仅当content事件的属性等于时,如何重写我们的通知程序才能被调用my-event:

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

每个SpEL表达式都针对专用上下文进行评估。下表列出了可用于上下文的项目,以便您可以将它们用于条件事件处理:

表8.事件SpEL可用元数据

名称 地点 描述
Event root object 实际的ApplicationEvent #root.event
Arguments array root object 用于调用目标的参数(作为数组)。
Argument name evaluation #root.args[0] context 任何方法参数的名称。如果由于某种原因,名称不可用(例如,因为没有调试信息),参数名称也可以在#a<#arg> where #arg参数索引(从0开始)下找到。

请注意#root.event,即使您的方法签名实际引用已发布的任意对象,也可以访问基础事件。

如果您需要作为处理其他事件的结果发布事件,则可以更改方法签名以返回应发布的事件,如以下示例所示:

@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

asynchronous listeners 异步侦听器不支持这个

这个新方法ListUpdateEvent为BlackListEvent上述方法处理的每个方法发布一个新的方法。如果您需要发布多个事件,则可以返回一个Collection事件。

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

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

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

如果事件侦听器抛出一个Exception,则它不会传播给调用者。有关AsyncUncaughtExceptionHandler详细信息,请参阅。

此类事件监听器无法发送回复。如果您需要作为处理结果发送另一个事件,请注入ApplicationEventPublisher以手动发送事件。

Ordering Listeners

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

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

Generic Events

您还可以使用泛型来进一步定义事件的结构。考虑使用 EntityCreatedEventwhere T是创建的实际实体的类型。例如,您可以创建以下侦听器定义只接收EntityCreatedEvent了 Person:

@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您作为事件发送的任何任意对象。

1.15.3 方便地访问低级资源

为了最佳地使用和理解应用程序上下文,您应该熟悉Spring的Resource抽象,如 参考资料中所述。

应用程序上下文是a ResourceLoader,可用于加载Resource对象。A Resource本质上是JDK java.net.URL类的功能更丰富的版本。实际上,在适当Resource的情况下包装一个实例的实现java.net.URL。A Resource可以透明的方式从几乎任何位置获取低级资源,包括从类路径,文件系统位置,任何可用标准URL描述的位置,以及一些其他变体。如果资源位置字符串是没有任何特殊前缀的简单路径,那么这些资源来自特定且适合于实际应用程序上下文类型。

您可以配置部署到应用程序上下文中的bean来实现特殊的回调接口,ResourceLoaderAware在初始化时自动回调,应用程序上下文本身作为传入 ResourceLoader。您还可以公开Resource要用于访问静态资源的类型属性。它们像任何其他属性一样被注入其中。您可以将这些Resource属性指定为简单String路径,并依赖特殊的JavaBean PropertyEditor(由上下文自动注册),以便Resource在部署Bean时将这些文本字符串转换为实际对象。

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

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

您可以ApplicationContext使用例如a以声明方式创建实例 ContextLoader。当然,您也可以ApplicationContext使用其中一个ApplicationContext实现以编程方式创建实例。

您可以ApplicationContext使用ContextLoaderListener,注册一个,如下例所示:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

监听器检查contextConfigLocation参数。如果参数不存在,则侦听器将/WEB-INF/applicationContext.xml默认使用。当参数确实存在时,侦听器String使用预定义的分隔符(逗号,分号和空格)分隔,并将值用作搜索应用程序上下文的位置。还支持Ant样式的路径模式。示例是/WEB-INF/*Context.xml(对于名称Context.xml 以WEB-INF目录结尾并位于目录/WEB-INF/**/*Context.xml中的所有文件)和(对于任何子目录中的所有此类文件WEB-INF)。

1.15.5 将Spring部署ApplicationContext为Java EE RAR文件

可以将Spring部署ApplicationContext为RAR文件,将上下文及其所有必需的bean类和库JAR封装在Java EE RAR部署单元中。这相当于ApplicationContext能够访问Java EE服务器设施的独立引导(仅在Java EE环境中托管)。RAR部署是部署无头WAR文件的一种更自然的替代方案 - 实际上是一个没有任何HTTP入口点的WAR文件,仅用于ApplicationContext在Java EE环境中引导Spring 。

RAR部署非常适用于不需要HTTP入口点但仅包含消息端点和预定作业的应用程序上下文。在这样的上下文中的Bean可以使用应用程序服务器资源,例如JTA事务管理器和JNDI绑定的JDBC DataSource实例和JMS ConnectionFactory实例,并且还可以通过Spring的标准事务管理以及JNDI和JMX支持工具向平台的JMX服务器注册。应用程序组件还可以WorkManager通过Spring的TaskExecutor抽象与应用程序服务器的JCA交互。

有关SpringContextResourceAdapter RAR部署中涉及的配置详细信息,请参阅该类的javadoc 。

对于将Spring ApplicationContext简单部署为Java EE RAR文件:

将所有应用程序类打包到一个RAR文件(这是一个具有不同文件扩展名的标准JAR文件)。。将所有必需的库JAR添加到RAR存档的根目录中。。添加 META-INF/ra.xml部署描述符(如javadoc中SpringContextResourceAdapter所示)和相应的Spring XML bean定义文件(通常为“META-INF / applicationContext.xml”)。

将生成的RAR文件放入应用程序服务器的部署目录中。

这种RAR部署单元通常是独立的。它们不会将组件暴露给外部世界,甚至不会暴露给同一应用程序的其他模块。与基于RAR的交互ApplicationContext通常通过与其他模块共享的JMS目标进行。ApplicationContext例如,基于RAR的还可以调度一些作业或对文件系统(或类似物)中的新文件作出反应。如果它需要允许来自外部的同步访问,它可以(例如)导出RMI端点,这可以由同一台机器上的其他应用程序模块使用。

猜你喜欢

转载自blog.csdn.net/hadues/article/details/86487709