IoC容器15——ApplicationContext的附加功能

“第2节 注解的监听器“中关于SpEL表达式做为过滤条件的部分须要重看java

正如以前章节介绍的,org.springframework.beans.factory包提供了管理和操做bean的基础功能,包括编程的方式。org.springframework.context包添加了ApplicationContext接口,它扩展了BeanFactory接口,同时也扩展了其它接口以更多的应用程序框架导向的风格来提供附加的功能(???)。许多人以彻底声明的风格使用ApplicationContext,甚至不用编程的方式建立它;做为替代,依赖于像COntextLoader这样类的支持自动实例化ApplicationContext,做为Java EE web应用程序正常启动过程的一部分。web

为了以更加面向框架的风格来加强BeanFactory功能,context包也提供了下面的功能:spring

  • 经过MessageSource接口,访问国际化风格的消息;
  • 经过ResourceLoader接口,访问资源,例如URL和文件;
  • 经过使用ApplicationEventPublisher接口,将事件发布到实现ApplicationListener接口的bean;
  • 经过HierarchicalBeanFactory接口加载多个(结构层次化的)上下文,容许每一个关注一层,例如应用程序的web层。

1 使用MessageSource进行国际化

ApplicationContext接口扩展了MessageSource接口,所以提供了国际化的功能。Spring也提供了HierarchicalMessageSource接口,它能够层次化的解析消息。这些接口一块儿为Spring消息解析提供基础。定义在这些接口中的方法包括:编程

  • String getMessage(String code, Object[] args, String default, Locale loc):用于从MessageSource取得消息的基础方法。当没有找到用于特定locale的消息,将会使用默认的消息。任何传入的参数会变为替代值,使用标准库提供的MessageFormat功能;windows

  • String getMessage(String code, Objectp[] args, Locale loc):本质上与上面的方法相同,不一样点在于:没有指定默认消息,若是不能找到消息会抛出NoSuchMessageException;设计模式

  • 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。任何解析消息的请求都会以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中,这个文件位于类路径的根。messageSource bean定义经过它的basenames属性引用了一系列的资源束。三个文件的名字以列表的形式被传递给basenames属性,这三个文件存在于类路径的根而且名为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,若是想要使用英国的语言环境解析消息,须要分别建立名为format_en_GB.properties、exceptions_en_GB.properties和windows_en_GB.properties的文件。

典型的,语言环境解析被应用程序运行的环境管理。在这个例子中,使用哪一个(British)语言环境手动指定。

# 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中定义的实现了MessageSourceAware接口的bean在建立时都会被注入应用上下的MessageSource。

做为ResourceBundleMessageSource的替代,Spring提供了ReloadableResourceBundleMessageSource类。这个类支持相同的束文件可是比基于JDK的标准ResourceBundleMessageSource实现更加灵活。尤为是他容许从任何Spring资源的位置读取文件(不只仅从classpath)而且支持束配置文件的热重载(同时有效的缓存它们)。详细信息可查看查看ReloadableResourceBundleMessageSource javadoc。

2 标准和自定义事件

ApplicationContext中的事件处理经过ApplicationEvent类和ApplicationListener接口提供。若是一个实现了ApplicationListener接口的bean被部署到上下文中,每次一个ApplicationEvent发布到ApplicationContext时,这个bean会被通知。实际上,这是标准的观察者设计模式。

从Spring 4.2开始,事件基础设施被明显的提高而且提供了基于注解的模型和发布任意事件,即对象不须要继承ApplicationEvent。当一个对象被发布,Spring将它包裹到一个事件中。

Spring提供了如下标准事件:

事件 说明
ContextRefreshedEvent 当ApplicationContext被初始化或刷新时发布,例如使用ConfigurableApplicationContext接口的refresh()方法。“初始化”在这里的意思是所用bean被加载,后处理器bean被发现和激活,单例被预初始化,而且ApplicationContext对象可使用。只要上下文没有被关闭,刷新可被屡次出发,只要所选的ApplicationContext支持这种“热”刷新。例如,XmlWebApplicationContext支持热刷新,可是GenericApplicationContext不支持。
ContextStartedEvent 当ApplicationContext开始的时候发布,例如使用ConfigurableApplicationContext接口的start()方法。”开始“在这里的意思是全部LifeCycle bean接受到一个明确的开始信号。典型的,这个信号被用于在明确的中止以后重启bean,但也能够用于开启没有被配置为自动开启的组件,例如在初始化中没有被开启的组件。
ContextStoppedEvent 当ApplicationContext被中止时发布,例如使用ConfigurableApplicationContext接口的stop()方法。“中止”在这里的意思是全部生命周期bean接受到一个明确的中止信号。一个已中止的上下文能够经过调用start()方法重启。
ContextClosedEvent 当ApplicationContext被关闭时发布,例如使用ConfigurableApplicationContext接口的close()方法。“关闭”在这里的意思是全部的单例bean被销毁。已关闭的上下文到达了生命周期的最后,它不能被刷新和重启。
RequestHandleEvent 一个基于web的事件,通知全部的bean一个HTTP请求已被处理。这个事件在请求完成时被发布。这个事件仅仅适用于使用Spring的DispatcherServlet的web应用程序。

也能够建立和发布自定义事件。这个例子展现了一个继承了Spring的ApplcationEvent基类的简单类:

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

}

为了发布一个自定义事件,调用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使用自定义的事件被范型参数化。这意味着onApplicationEvent()方法能够保持类型安全,避免任何向下转型的须要。能够根据须要注册多个事件监听器,可是请注意,默认的事件监听器同步的接收事件。这意味着publishEvent()方法会被阻塞直到所用监听器都完成了对这个事件的处理。这种阻塞和单线程处理的一个好处是当一个监听器接收到事件,若是一个事务上下文是可用的它就在发布者的事务上下文中操做。若是须要另外的事件发布策略,能够参考Spring的ApplicationEventMulticaster接口的javadoc。

下面的例子展现了用于注册和配置上面的类的XML 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>

把它们放在一块儿,当emailServie bean的sendEmail()方法被调用,若是有任何邮箱应被列入黑名单,一个类型为BlackListEvent的自定义事件被发布。blackListNotifier bean被注册为一个ApplicationListener,所以接收到BlackListEvent,此时它能够通知适当的部分。

Spring的事件机制被设计为同一个应用上下文中的Spring bean之间的简单通信。然而,对于更加复杂的企业级集成需求,分开维护的Spring Integration项目提供了对于基于Spring编程模型的构建轻量级、面向模式的事件驱动架构提供了完整的支持。

基于注解的事件监听器

从Spring 4.2开始,一个事件监听器能够被bean的任何标注了EventListener注解的公有方法注册。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表达式由专用的上下文评估。下面的表格列出了可用于上下文的项目,所以能够将它们用于条件事件处理:

名字 位置 描述 例子
事件 根对象 实际的ApplicationEvent #root.event
参数数组 根对象 用于调用对象的参数(数组的形式) #root.args[0]
参数名 评估上下文 任何方法参数的名字。若是因为一些缘由名字不可用(例如没有debug信息),参数名在#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...
}

这个特性在异步监听器中不支持。

这个新方法会在每次处理完BlackListEvent后发布一个新的ListUpdateEvent。若是想发布多个事件,返回事件的Collection便可。

异步监听器

若是须要一个特定的监听器来异步的处理事件,只须要重用标准的@Async支持:

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

使用异步事件时请记得如下限制:

  1. 若是事件监听器抛出异常,它不会传递给调用者,查看AsyncUncaughtExceptionHandler得到更多信息;
  2. 这种事件监听器不能发送回复。若是须要发送一个事件所谓处理的结果,请注入ApplicationEventPublisher来手动发送事件。

监听器排序

若是须要一个监听器比另一个先被调用,能够在方法声明上添加@Order注解:

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

范型事件

可使用范型来进一步定义事件结构。可使用EntityCreatedEvent<T>,其中T是被建立的实体类型。能够建立以下的监听器定义用于仅接收Person的EntityCreatedEvent:

@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()));
    }
}

这种方法不只用于ApplicationEvent,它能够用于任何做为事件而被发送的对象。

3 方便的访问低级资源

为了更好的使用和理解应用上下文,用户应该属性Spring的资源抽象。

一个应用上下问是一个ResourceLoader,能够用于加载Resource。一个Resource实质上JDK类java.net.URL的功能更丰富的版本,实际上,Resource的实如今适当的地方包裹了java.net.URL的一个实例。一个Resource能够从几乎任何位置以透明的方式获取低级资源,包括从类路径、文件系统位置、使用标准URL描述的任何位置,或其它地方。若是一个资源定位字符串是一个简单的路径而没有指定前缀,则他们从何处而来取决于实际的应用上下文类型。

能够配置一个bean部署到应用上下文来实现指定的回调接口,ResourceLoaderAware,在初始化时应用上下文自身所谓ResourceLoader传入时被自动回调。能够暴露Resource类型的属性,用于访问静态资源;它们能够像其它属性同样被注入。能够指定这些Resource属性为简单的字符串路径,依靠被上下文自动注册的、类型为PropertyEditor的特定JavaBean,当bean被部署时将这些文本字符串转换为实际的Resource对象。

提供给ApplicationContext构造函数的位置路径其实是资源字符串,而且以简单的形式根据具体的上下文实现进行恰当的处理。ClassPathXmlApplicationContext将一个简单的定位路径视为类路径位置。也可使用带特定前缀的位置路径强制指定从类路径或URL加载定义,而不考虑实际上下文类型。

4 方便的web应用程序应用上下文实例

能够声明式的建立ApplicationContext,例如使用ContextLoader。固然也可使用一个ApplicationContext实例编程式的建立ApplicationContext实例。

可使用ContextLoaderListener注册ApplicationContext以下:

<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用于匹配在WEB-INF文件夹中以Context.xml结尾的文件,同时/WEB-INF/**/*Context.xml匹配在WEB-INF任何子文件夹中的这样的文件。

5 将Spring应用上下文部署为Java EE RAR文件

能够将Spring应用上下文部署为RAR文件,将上下文、全部须要的bean类、库JAR文件封装进一个Java EE RAR部署单元。这等价于引导一个托管在Java EE环境中的独立的应用上下文,它能够访问Jave EE服务器设施。RAR部署是更天然的方案用于替代无头的WAR文件,实际上,没有任何HTTP入口点的WAR文件仅用于在Java EE环境中引导Spring的应用上下文。

RAR部署是不须要HTTP入口点而只须要消息端点和计划任务的应用上下文的理想选择。在这种上下文中的bean可使用应用服务器资源例如JTA事务管理器、JNDI绑定的JDBC数据源、JMS链接工厂实例和任何经过Spring标准事务管理器和JNDI、JMX支持工具向JMX服务器平台注册的组件。应用程序组件也能够经过Spring的TaskExecutor抽象与应用程序服务器的JCA工做管理器进行交互。

查看SpringContextResourceAdapter类的javadoc来获取RAR部署的配置细节。

对于将Spring应用上下文做为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端点,它固然也能够用于相同机器上的其它应用模块。

相关文章
相关标签/搜索