SpringFramework核心技术一(IOC:ApplicationContext的附加功能)

简介: 标题正如本章介绍中所讨论的,该org.springframework.beans.factory 包提供了用于管理和操作bean的基本功能,包括以编程方式。

标题

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

  • 通过MessageSource界面访问i18n风格的消息。
  • 通过ResourceLoader界面访问资源,如URL和文件。
  • 事件发布到即实现ApplicationListener接口的bean ,通过使用ApplicationEventPublisher接口。

加载多个(分层)上下文,通过HierarchicalBeanFactory接口允许每个上下文关注某个特定层,例如应用程序的Web层 。


一、使用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则会实例化一个空 以便能够接受对上面定义的方法的调用。

Spring提供了两个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。任何解析消息的请求都将以通过ResourceBundles解析消息的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", null);
    System.out.println(message);
}

从上述程序产生的输出将是…

Alligators rock!

总之,这个MessageSource被定义在一个叫做的文件中beans.xml,它存在于你的类路径的根目录下。该messageSourcebean定义是指通过它的一些资源包的basenames属性。这是在列表中传递的三个文件basenames属性存在于你的classpath根目录的文件,被称为format.properties,exceptions.properties和 windows.properties分别。
下一个示例显示传递给消息查找的参数; 这些参数将转换为字符串并插入查找消息中的占位符。

<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.foo.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分别。

通常,区域设置解析由应用程序的周围环境管理。在这个例子中,(英国)消息将被解析的地区是手动指定的。

#在exceptions_en_GB.properties中
argument.required = Ebagum lad,我认为需要{0}参数。
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实现MessageSourceAware接口的bean中定义 MessageSource的bean都会在创建和配置bean时注入应用程序上下文。

作为一种选择ResourceBundleMessageSource,Spring提供了一个 ReloadableResourceBundleMessageSource类。该变体支持相同的包文件格式,但比标准的基于JDK的ResourceBundleMessageSource实现更灵活 。特别是,它允许从任何Spring资源位置(而不仅仅是从类路径)读取文件,并支持热重载bundle属性文件(同时有效地缓存它们)。

二、标准和自定义事件

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

Event Explanation
ContextRefreshedEvent 在ApplicationContext上下文中初始化或者刷新。例如,使用ConfigurableApplicationContext中的refresh()方法。这里的“初始化”意味着所有的bean都被加载,检测并激活后处理器bean,单例被预先实例化,并且该ApplicationContext对象已准备好使用。只要上下文尚未关闭,刷新可以多次触发,前提是所选内容ApplicationContext实际上支持“热”刷新。例如,XmlWebApplicationContext支持热点刷新,但GenericApplicationContext不支持 。
ContextStartedEvent 在ApplicationContext启动时发布,使用ConfigurableApplicationContext上下文中的start()方法。这里的“开始”意味着所有的Lifecycle bean都会收到明确的启动信号。通常,此信号用于在显式停止后重新启动Bean,但它也可用于启动尚未配置为自动启动的组件,例如尚未启动初始化的组件。
ContextStoppedEvent 在ApplicationContext停止时发布,使用ConfigurableApplicationContext上下文中的stop()方法。这里“停止”意味着所有的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 test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }

    // accessor and other methods...
}
  • 发布
    要发布自定义ApplicationEvent,请调用ApplicationEventPublisher中的publishEvent()方法 。通常这是通过创建一个实现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 text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            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>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="blacklist@example.org"/>
</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表达式,该表达式应匹配以实际调用特定事件的方法。

例如,如果test事件的属性等于foo:我们的通知器可以被重写为仅被调用:

@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
    // notify appropriate parties via notificationAddress...
}

每个SpEL表达式再次评估一个专用的上下文。下表列出了可用于上下文的项目,以便可以将它们用于条件事件处理:
Table 8. Event SpEL available metadata

Name Location Description Example
Event root object The actual ApplicationEvent root.event
root.event root.event 用于调用目标的参数(如数组) 用于调用目标的参数(如数组)
Argument name Argument name 任何方法参数的名称。如果由于某种原因名称是不可用(例如,没有调试信息),参数名称也是在现有的#a<#arg> 地方#arg代表的说法指数(从0开始)。 blEvent或者#a0(也可以使用#p0或#p<#arg>标记作为别名)

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

如果您需要发布一个事件作为处理另一个事件的结果,只需更改方法签名以返回应该发布的事件,如下所示:

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

异步监听器不支持这个东西
这种新方法将为上述方法的ListUpdateEvent每个BlackListEvent处理发布一个新的方法。如果您需要发布多个事件,则只需返回一些Collection事件。

四、异步监听器

如果您希望特定的侦听器异步处理事件,simply reuse the regular @Async support:

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

六、通用事件

您也可以使用泛型来进一步定义事件的结构。考虑 创建实际实体的类型 EntityCreatedEvent<T>在哪里T。您可以创建以下侦听器定义以仅接收EntityCreatedEvent以下内容 Person:

@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
    ...
}

由于类型擦除,只有当被触发的事件解析了事件侦听器过滤的泛型参数时(这是类似的class PersonCreatedEvent extends EntityCreatedEvent<Person> { …​ }),这才会起作用 。

在某些情况下,如果所有事件都遵循相同的结构(这应该是上述事件的情况),则这可能变得非常乏味。在这种情况下,您可以实现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()));
    }
}

This works not only for ApplicationEvent but any arbitrary object that you’d send as an event.

七、方便地访问低水平资源

为了最佳使用和理解应用程序上下文,用户通常应该熟悉Spring的Resource抽象,如“ 资源 ”一章所述 。

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

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

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

八、方便的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用作默认值。当参数确实存在时,侦听器使用预定义的分隔符(逗号,分号和空白)来分隔字符串,并将这些值用作应用程序上下文将被搜索的位置。也支持Ant风格的路径模式。例子是/WEB-INF/Context.xml名称以“Context.xml”结尾的所有文件,驻留在“WEB-INF”目录中,并且/WEB-INF/*/*Context.xml对于“WEB-INF”的任何子目录中的所有这些文件。

九、将Spring ApplicationContext部署为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 DataSources和JMS ConnectionFactory实例,也可以通过Spring的标准事务管理和JNDI和JMX支持工具向平台的JMX服务器注册。应用程序组件还可以通过Spring的TaskExecutor抽象与应用程序服务器的JCA WorkManager进行交互。

要将Spring ApplicationContext简单部署为Java EE RAR文件:将所有应用程序类打包到RAR文件中,该文件是具有不同文件扩展名的标准JAR文件。将所有必需的库JAR添加到RAR归档的根目录中。添加一个“META-INF / ra.xml”部署描述符(如SpringContextResourceAdapters javadoc中所示)和相应的Spring XML bean定义文件(通常为“META-INF / applicationContext.xml”),并放弃生成的RAR文件到您的应用程序服务器的部署目录。

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

好啦,ApplicationContext的附加功能,就是这么多东西,平时多看看,在用的时候,就能得心应手。

目录
相关文章
|
2月前
|
XML Java 数据格式
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
编织Spring魔法:解读核心容器中的Beans机制【beans 一】
42 0
|
6月前
|
XML Java 数据格式
深入解析Spring框架的ApplicationContext体系结构
深入解析Spring框架的ApplicationContext体系结构
50 0
|
Java Spring
Spring文件第四课SpringIOC 的三种方案利用注解的方式(二)
Spring文件第四课SpringIOC 的三种方案利用注解的方式(二)
80 0
|
XML 存储 Java
【Spring专题】「原理系列」全方面解析SpringFramework的Bean对象的深入分析和挖掘指南
【Spring专题】「原理系列」全方面解析SpringFramework的Bean对象的深入分析和挖掘指南
128 0
【Spring专题】「原理系列」全方面解析SpringFramework的Bean对象的深入分析和挖掘指南
|
Java 程序员 网络安全
spring4.1.8扩展实战之七:控制bean(BeanPostProcessor接口)
实战在容器初始化的时候对bean实例做设置
115 0
spring4.1.8扩展实战之七:控制bean(BeanPostProcessor接口)
|
缓存 Java Spring
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(中)
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(中)
|
Java Spring 容器
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(上)
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(上)
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(上)
|
Java Spring
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(下)
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(下)
Spring依赖注入(DI)核心接口AutowireCandidateResolver深度分析,解析@Lazy、@Qualifier注解的原理【享学Spring】(下)
|
缓存 安全 Java
Spring 5 中文解析核心篇-IoC容器之ApplicationContext与BeanFactory
本章节主要描述:Spring 5 中文解析核心篇-IoC容器之ApplicationContext与BeanFactory。
314 0
|
Java Spring 前端开发
Spring核心 ApplicationContext的加载过程
我们在使用Spring的时候,一般来说都会通过这个方式来实例化一个applicationContext ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:applicationContext.
1523 0