添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Integration

参考文档的这一部分涵盖了 Spring Framework 与许多 Java EE(及相关)技术的集成。

1.使用 Spring 进行远程处理和 Web 服务

Spring 具有集成类,用于通过各种技术来远程支持。远程支持简化了由常规(Spring)POJO 实施的启用远程服务的开发。当前,Spring 支持以下远程技术:

  • 远程方法调用(RMI) :通过使用 RmiProxyFactoryBean RmiServiceExporter ,Spring 支持传统的 RMI(具有 java.rmi.Remote 接口和 java.rmi.RemoteException )以及通过 RMI 调用者进行透明远程处理(具有任何 Java 接口)。

  • Spring 的 HTTP 调用程序 :Spring 提供了一种特殊的远程处理策略,该策略允许通过 HTTP 进行 Java 序列化,从而支持任何 Java 接口(就像 RMI 调用程序一样)。相应的支持类别为 HttpInvokerProxyFactoryBean HttpInvokerServiceExporter

  • Hessian :通过使用 Spring 的 HessianProxyFactoryBean HessianServiceExporter ,您可以通过 Caucho 提供的基于 HTTP 的轻量级二进制协议透明地公开您的服务。

  • JAX-WS :Spring 通过 JAX-WS(Java EE 5 和 Java 6 中引入的 JAX-RPC 的继承者)为 Web 服务提供远程支持。

  • JMS :通过 JmsInvokerServiceExporter JmsInvokerProxyFactoryBean 类支持使用 JMS 作为基础协议进行远程处理。

  • AMQP :Spring AMQP 项目支持使用 AMQP 作为基础协议进行远程处理。

  • 在讨论 Spring 的远程功能时,我们使用以下域模型和相应的服务:

    public class Account implements Serializable{
        private String name;
        public String getName(){
            return name;
        public void setName(String name) {
            this.name = name;
      
    public interface AccountService {
        public void insertAccount(Account account);
        public List<Account> getAccounts(String name);
      
    // the implementation doing nothing at the moment
    public class AccountServiceImpl implements AccountService {
        public void insertAccount(Account acc) {
            // do something...
        public List<Account> getAccounts(String name) {
            // do something...
      

    本节首先使用 RMI 将服务公开给远程 Client 端,然后再谈谈使用 RMI 的缺点。然后 continue 以使用 Hessian 作为协议的示例。

    1.1. 使用 RMI 公开服务

    通过使用 Spring 对 RMI 的支持,您可以通过 RMI 基础结构透明地公开服务。进行了此设置之后,除了不存在对安全上下文传播或远程事务传播的标准支持这一事实之外,您基本上具有与远程 EJB 相似的配置。当您使用 RMI 调用程序时,Spring 确实为此类附加调用上下文提供了钩子,因此,例如,您可以插入安全框架或自定义安全凭证。

    1.1.1. 使用 RmiServiceExporter 导出服务

    使用RmiServiceExporter,我们可以将 AccountService 对象的接口公开为 RMI 对象。可以使用RmiProxyFactoryBean来访问该接口,或者在传统 RMI 服务的情况下可以通过普通 RMI 来访问该接口。 RmiServiceExporter明确支持通过 RMI 调用程序公开任何非 RMI 服务。

    我们首先必须在 Spring 容器中设置服务。以下示例显示了如何执行此操作:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>
      

    接下来,我们必须使用RmiServiceExporter公开我们的服务。以下示例显示了如何执行此操作:

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <!-- does not necessarily have to be the same name as the bean to be exported -->
        <property name="serviceName" value="AccountService"/>
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
        <!-- defaults to 1099 -->
        <property name="registryPort" value="1199"/>
    </bean>
      

    在前面的示例中,我们覆盖了 RMI 注册表的端口。通常,您的应用服务器还维护一个 RMI 注册表,因此最好不要干涉该注册表。此外,服务名称用于绑定服务。因此,在前面的示例中,服务绑定在'rmi://HOST:1199/AccountService'处。稍后,我们将使用此 URL 链接到 Client 端的服务。

    servicePort属性已被省略(默认为 0)。这意味着将使用匿名端口与服务进行通信。

    1.1.2. 在 Client 端链接服务

    我们的 Client 是一个简单的对象,它使用AccountService来 Management 帐户,如以下示例所示:

    public class SimpleObject {
        private AccountService accountService;
        public void setAccountService(AccountService accountService) {
            this.accountService = accountService;
        // additional methods using the accountService
      

    为了在 Client 端上链接服务,我们创建了一个单独的 Spring 容器,其中包含以下简单对象和服务链接配置位:

    <bean class="example.SimpleObject">
        <property name="accountService" ref="accountService"/>
    </bean>
    <bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
      

    这就是我们要在 Client 端上支持远程帐户服务所需要做的一切。 Spring 透明地创建一个调用程序,并通过RmiServiceExporter远程启用帐户服务。在 Client 端,我们使用RmiProxyFactoryBean将其链接。

    1.2. 使用 Hessian 通过 HTTP 远程调用服务

    Hessian 提供了一个基于 HTTP 的二进制远程协议。它由 Caucho 开发,您可以在http://www.caucho.com上找到有关 Hessian 本身的更多信息。

    1.2.1. 为 Hessian 连接 DispatcherServlet

    Hessian 通过 HTTP 进行通信,并通过使用自定义 servlet 进行通信。通过使用 Spring 的DispatcherServlet原理(请参阅[webmvc#mvc-servlet]),我们可以连接这样的 servlet 以公开您的服务。首先,我们必须在应用程序中创建一个新的 servlet,如以下web.xml的摘录所示:

    <servlet>
        <servlet-name>remoting</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>remoting</servlet-name>
        <url-pattern>/remoting/*</url-pattern>
    </servlet-mapping>
      

    如果您熟悉 Spring 的DispatcherServlet原理,那么您可能知道现在必须在WEB-INF目录中创建一个名为remoting-servlet.xml(在 Servlet 名称之后)的 Spring 容器配置资源。下一节将使用应用程序上下文。

    或者,考虑使用 Spring 的简单HttpRequestHandlerServlet。这样做使您可以将远程导出程序定义嵌入到根应用程序上下文中(默认情况下,在WEB-INF/applicationContext.xml中),而单个 Servlet 定义则指向特定的导出程序 bean。在这种情况下,每个 servlet 名称都必须与其目标导出器的 bean 名称相匹配。

    1.2.2. 使用 HessianServiceExporter 公开您的 Bean

    在新创建的名为remoting-servlet.xml的应用程序上下文中,我们创建一个HessianServiceExporter以导出我们的服务,如以下示例所示:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>
    <bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
      

    现在,我们准备在 Client 端链接服务。没有指定显式处理程序 Map(将请求 URLMap 到服务),因此我们使用BeanNameUrlHandlerMapping。因此,该服务将在包含DispatcherServlet实例的 Map(如先前定义)http://HOST:8080/remoting/AccountService中通过其 bean 名称指示的 URL 导出。

    另外,您可以在根应用程序上下文中(例如,在WEB-INF/applicationContext.xml中)创建HessianServiceExporter,如以下示例所示:

    <bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
      

    在后一种情况下,您应该在web.xml中为此导出程序定义一个相应的 servlet,最终结果相同:导出程序 Map 到/remoting/AccountService的请求路径。注意,servlet 名称需要与目标导出器的 bean 名称匹配。以下示例显示了如何执行此操作:

    <servlet>
        <servlet-name>accountExporter</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>accountExporter</servlet-name>
        <url-pattern>/remoting/AccountService</url-pattern>
    </servlet-mapping>
      

    1.2.3. 在 Client 端上链接服务

    通过使用HessianProxyFactoryBean,我们可以在 Client 端链接服务。与 RMI 示例相同的原理适用。我们创建一个单独的 bean 工厂或应用程序上下文,并通过使用AccountService来 Management 帐户来提及SimpleObject所在的以下 bean,如以下示例所示:

    <bean class="example.SimpleObject">
        <property name="accountService" ref="accountService"/>
    </bean>
    <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
        <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
      

    1.2.4. 将 HTTP 基本身份验证应用于通过 Hessian 公开的服务

    Hessian 的优点之一是我们可以轻松地应用 HTTP 基本身份验证,因为这两种协议都是基于 HTTP 的。例如,可以通过使用web.xml安全功能来应用常规的 HTTP 服务器安全性机制。通常,您无需在此处使用每个用户的安全凭证。相反,您可以使用在HessianProxyFactoryBean级别定义的共享凭据(类似于 JDBC DataSource),如以下示例所示:

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
        <property name="interceptors" ref="authorizationInterceptor"/>
    </bean>
    <bean id="authorizationInterceptor"
            class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
        <property name="authorizedRoles" value="administrator,operator"/>
    </bean>
      

    在前面的示例中,我们明确提到BeanNameUrlHandlerMapping并设置了一个拦截器,以仅让 Management 员和操作员调用此应用程序上下文中提到的 bean。

    前面的示例未显示灵活的安全基础结构。有关安全性的更多选项,请查看位于http://projects.spring.io/spring-security/的 Spring Security 项目。

    1.3. 使用 HTTP 调用程序公开服务

    与 Hessian 相反,Spring HTTP 调用程序都是轻量级协议,它们使用自己的苗条序列化机制,并使用标准 Java 序列化机制通过 HTTP 公开服务。如果您的参数和返回类型是无法通过使用 Hessian 使用的序列化机制进行序列化的复杂类型,则这将具有巨大的优势(选择远程处理技术时,请参阅下一节以获得更多注意事项)。

    在幕后,Spring 使用 JDK 或 Apache HttpComponents提供的标准功能来执行 HTTP 调用。如果您需要更高级且更易于使用的功能,请使用后者。有关更多信息,请参见hc.apache.org/httpcomponents-client-ga/

    Warning

    注意由于不安全的 Java 反序列化而导致的漏洞:在反序列化步骤中,操纵的 Importing 流可能导致服务器上有害的代码执行。因此,请勿将 HTTP 调用方终结点暴露给不受信任的 Client 端。而是仅在您自己的服务之间公开它们。通常,强烈建议您改用其他任何消息格式(例如 JSON)。

    如果您担心由 Java 序列化引起的安全漏洞,请考虑在核心 JVM 级别上使用通用序列化筛选器机制,该机制最初是为 JDK 9 开发的,但同时又移植到 JDK 8、7 和 6.参见https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_ahttp://openjdk.java.net/jeps/290

    1.3.1. 公开服务对象

    为服务对象设置 HTTP 调用程序基础结构与使用 Hessian 进行设置的方法非常相似。由于 Hessian 支持提供了HessianServiceExporter,Spring 的 HttpInvoker 支持提供了org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter

    为了在 Spring Web MVC DispatcherServlet中公开AccountService(前面提到),需要在调度程序的应用程序上下文中进行以下配置,如以下示例所示:

    <bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
      

    关于黑森 State 的部分所述,此类导出程序定义通过DispatcherServlet实例的标准 Map 工具公开。

    另外,您可以在根应用程序上下文中(例如,在'WEB-INF/applicationContext.xml'中)创建HttpInvokerServiceExporter,如以下示例所示:

    <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
      

    另外,您可以在web.xml中为此导出程序定义一个相应的 servlet,该 servlet 名称与目标导出程序的 bean 名称匹配,如以下示例所示:

    <servlet>
        <servlet-name>accountExporter</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>accountExporter</servlet-name>
        <url-pattern>/remoting/AccountService</url-pattern>
    </servlet-mapping>
      

    1.3.2. 在 Client 端链接服务

    同样,从 Client 端链接服务与使用 Hessian 时的方式非常相似。通过使用代理,Spring 可以将您对 HTTP POST 请求的调用转换为指向导出服务的 URL。以下示例显示如何配置此安排:

    <bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
      

    如前所述,您可以选择要使用的 HTTPClient 端。默认情况下,HttpInvokerProxy使用 JDK 的 HTTP 功能,但是您也可以通过设置httpInvokerRequestExecutor属性来使用 Apache HttpComponentsClient 端。以下示例显示了如何执行此操作:

    <property name="httpInvokerRequestExecutor">
        <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/>
    </property>
      

    1.4. Web Services

    Spring 提供了对标准 Java Web 服务 API 的全面支持:

  • 使用 JAX-WS 公开 Web 服务

  • 使用 JAX-WS 访问 Web 服务

  • 除了在 Spring Core 中对 JAX-WS 的库存支持之外,Spring 产品组合还具有SpringWeb Service,这是一种针对 Contract 优先,文档驱动的 Web 服务的解决方案。

    1.4.1. 使用 JAX-WS 公开基于 Servlet 的 Web 服务

    Spring 为 JAX-WS servlet 端点实现提供了一个方便的 Base Class:SpringBeanAutowiringSupport。为了公开AccountService,我们扩展 Spring 的SpringBeanAutowiringSupport类并在此处实现我们的业务逻辑,通常将调用委派给业务层。我们使用 Spring 的@Autowired注解来表达对 SpringManagement 的 bean 的依赖。以下示例显示了扩展SpringBeanAutowiringSupport的类:

    * JAX-WS compliant AccountService implementation that simply delegates * to the AccountService implementation in the root web application context. * This wrapper class is necessary because JAX-WS requires working with dedicated * endpoint classes. If an existing service needs to be exported, a wrapper that * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through * the @Autowired annotation) is the simplest JAX-WS compliant way. * This is the class registered with the server-side JAX-WS implementation. * In the case of a Java EE 5 server, this would simply be defined as a servlet * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting * accordingly. The servlet name usually needs to match the specified WS service name. * The web service engine manages the lifecycle of instances of this class. * Spring bean references will just be wired in here. import org.springframework.web.context.support.SpringBeanAutowiringSupport; @WebService(serviceName="AccountService") public class AccountServiceEndpoint extends SpringBeanAutowiringSupport { @Autowired private AccountService biz; @WebMethod public void insertAccount(Account acc) { biz.insertAccount(acc); @WebMethod public Account[] getAccounts(String name) { return biz.getAccounts(name);

    我们的AccountServiceEndpoint需要在与 Spring 上下文相同的 Web 应用程序中运行,以允许访问 Spring 的设施。在 Java EE 5 环境中,默认情况下就是这种情况,使用用于 JAX-WS servlet 端点部署的标准协定。有关详细信息,请参见各种 Java EE 5 Web 服务教程。

    1.4.2. 使用 JAX-WS 导出独立的 Web 服务

    Oracle JDK 随附的内置 JAX-WS 提供程序通过使用 JDK 中也包含的内置 HTTP 服务器来支持 Web 服务公开。 Spring 的SimpleJaxWsServiceExporter检测到 Spring 应用程序上下文中所有带有@WebServiceComments 的 bean,并将它们通过默认的 JAX-WS 服务器(JDK HTTP 服务器)导出。

    在这种情况下,端点实例被定义和 Management 为 Spring Bean 本身。它们已在 JAX-WS 引擎中注册,但是它们的生命周期取决于 Spring 应用程序上下文。这意味着您可以将 Spring 功能(例如显式依赖项注入)应用于端点实例。通过@Autowired进行 Comments 驱动的注入也有效。以下示例显示了如何定义这些 bean:

    <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
        <property name="baseAddress" value="http://localhost:8080/"/>
    </bean>
    <bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
    </bean>
      

    AccountServiceEndpoint可以但不必从 Spring 的SpringBeanAutowiringSupport派生,因为此示例中的端点是完全由 SpringManagement 的 bean。这意味着端点实现可以如下所示(不声明任何超类,并且仍然采用 Spring 的@Autowired配置 Comments):

    @WebService(serviceName="AccountService")
    public class AccountServiceEndpoint {
        @Autowired
        private AccountService biz;
        @WebMethod
        public void insertAccount(Account acc) {
            biz.insertAccount(acc);
        @WebMethod
        public List<Account> getAccounts(String name) {
            return biz.getAccounts(name);
      

    1.4.3. 使用 JAX-WS RI 的 Spring 支持导出 Web 服务

    作为 GlassFish 项目的一部分开发的 Oracle JAX-WS RI,将 Spring 支持作为其 JAX-WS Commons 项目的一部分。这允许将 JAX-WS 端点定义为 SpringManagement 的 bean,类似于previous section中讨论的独立模式,但这次是在 Servlet 环境中。

    这在 Java EE 5 环境中不可移植。它主要用于将 JAX-WS RI 嵌入为 Web 应用程序一部分的非 EE 环境,例如 Tomcat。

    与导出基于 servlet 的端点的标准样式的不同之处在于,端点实例本身的生命周期由 SpringManagement,并且在web.xml中仅定义了一个 JAX-WS servlet。使用标准的 Java EE 5 样式(如前所示),每个服务端点都有一个 servlet 定义,每个端点通常委派给 Spring Bean(如前所述,通过使用@Autowired)。

    有关设置和使用方式的详细信息,请参见https://jax-ws-commons.java.net/spring/

    1.4.4. 使用 JAX-WS 访问 Web 服务

    Spring 提供了两个工厂 bean 来创建 JAX-WS Web 服务代理,即LocalJaxWsServiceFactoryBeanJaxWsPortProxyFactoryBean。前者只能返回一个 JAX-WS 服务类供我们使用。后者是完整版本,可以返回实现我们的业务服务接口的代理。在以下示例中,我们再次使用JaxWsPortProxyFactoryBeanAccountService端点创建代理:

    <bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
        <property name="serviceInterface" value="example.AccountService"/> (1)
        <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
        <property name="namespaceUri" value="http://example/"/>
        <property name="serviceName" value="AccountService"/>
        <property name="portName" value="AccountServiceEndpointPort"/>
    </bean>
       
  • (1) 其中serviceInterface是 Client 使用的我们的业务界面。
  • wsdlDocumentUrl是 WSDL 文件的 URL。 Spring 在启动时需要使用它来创建 JAX-WS 服务。 namespaceUri对应于.wsdl 文件中的targetNamespaceserviceName对应于.wsdl 文件中的服务名称。 portName对应于.wsdl 文件中的端口名称。

    访问 Web 服务很容易,因为我们有一个供其使用的 bean 工厂,它将它公开为名为AccountService的接口。以下示例说明了如何在 Spring 中进行连接:

    <bean id="client" class="example.AccountClientImpl">
        <property name="service" ref="accountWebService"/>
    </bean>
      

    从 Client 端代码,我们可以像访问普通类一样访问 Web 服务,如以下示例所示:

    public class AccountClientImpl {
        private AccountService service;
        public void setService(AccountService service) {
            this.service = service;
        public void foo() {
            service.insertAccount(...);
        

    上面的内容略有简化,因为 JAX-WS 需要使用@WebService@SOAPBinding etcComments 对端点接口和实现类进行 Comments。这意味着您不能(轻松)使用纯 Java 接口和实现类作为 JAX-WS 端点工件。您需要首先对它们进行 Comments。查看 JAX-WS 文档以获取有关这些需求的详细信息。

    1.5. 通过 JMS 公开服务

    您还可以通过使用 JMS 作为基础通信协议来透明地公开服务。 Spring 框架中的 JMS 远程支持非常基本。它在same thread上和同一非事务Session中发送和接收。结果,吞吐量取决于实现方式。请注意,这些单线程和非事务性约束仅适用于 Spring 的 JMS 远程支持。请参阅JMS(Java 消息服务)以获取有关 Spring 对基于 JMS 的消息传递的丰富支持的信息。

    服务器和 Client 端均使用以下接口:

    package com.foo;
    public interface CheckingAccountService {
        public void cancelAccount(Long accountId);
      

    在服务器端使用上述接口的以下简单实现:

    package com.foo;
    public class SimpleCheckingAccountService implements CheckingAccountService {
        public void cancelAccount(Long accountId) {
            System.out.println("Cancelling account [" + accountId + "]");
      

    以下配置文件包含在 Client 机和服务器上共享的 JMS 基础结构 Bean:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
            <property name="brokerURL" value="tcp://ep-t43:61616"/>
        </bean>
        <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg value="mmm"/>
        </bean>
    </beans>
      

    1.5.1. 服务器端配置

    在服务器上,您需要公开使用JmsInvokerServiceExporter的服务对象,如以下示例所示:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="checkingAccountService"
                class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
            <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
            <property name="service">
                <bean class="com.foo.SimpleCheckingAccountService"/>
            </property>
        </bean>
        <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
            <property name="connectionFactory" ref="connectionFactory"/>
            <property name="destination" ref="queue"/>
            <property name="concurrentConsumers" value="3"/>
            <property name="messageListener" ref="checkingAccountService"/>
        </bean>
    </beans>
      
    package com.foo;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    public class Server {
        public static void main(String[] args) throws Exception {
            new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"});
      

    1.5.2. Client 端配置

    Client 端只需要创建一个 Client 端代理即可实现约定的接口(CheckingAccountService)。

    以下示例定义了可以注入到其他 Client 端对象中的 Bean(代理负责通过 JMS 将调用转发到服务器端对象):

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="checkingAccountService"
                class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
            <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
            <property name="connectionFactory" ref="connectionFactory"/>
            <property name="queue" ref="queue"/>
        </bean>
    </beans>
      
    package com.foo;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    public class Client {
        public static void main(String[] args) throws Exception {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
                    new String[] {"com/foo/client.xml", "com/foo/jms.xml"});
            CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
            service.cancelAccount(new Long(10));
      

    1.6. AMQP

    有关更多信息,请参见Spring AMQP 参考指南的“使用 AMQP 进行远程处理”部分

    远程接口未实现自动检测

    对于远程接口,不会自动检测已实现接口的主要原因是为了避免为远程调用者打开太多门。目标对象可能实现内部回调接口,例如InitializingBeanDisposableBean,而这些接口将不会向调用者公开。

    在本地情况下,提供具有目标所实现的所有接口的代理通常无关紧要。但是,当导出远程服务时,应公开特定的服务接口,并提供用于远程使用的特定操作。除了内部回调接口之外,目标还可以实现多个业务接口,其中只有一个用于远程公开。由于这些原因,我们需要指定这样的服务接口。

    这是配置便利性与内部方法意外暴露风险之间的折衷方案。始终指定服务接口并不会花费太多精力,这使您可以安全地控制特定方法的使用。

    1.7. 选择技术时的注意事项

    这里介绍的每种技术都有其缺点。选择一种技术时,应仔细考虑您的需求,公开的服务以及通过网络发送的对象。

    使用 RMI 时,除非通过隧道传送 RMI 流量,否则无法通过 HTTP 协议访问对象。 RMI 是一个重量级协议,因为它支持全对象序列化,当您使用需要通过网络进行序列化的复杂数据模型时,RMI 非常重要。但是,RMI-JRMP 绑定到 JavaClient 端。它是 Java 到 Java 的远程解决方案。

    如果您需要基于 HTTP 的远程处理而且还依赖 Java 序列化,那么 Spring 的 HTTP 调用程序是一个不错的选择。它与 RMI 调用程序共享基本的基础结构,但使用 HTTP 作为传输。请注意,HTTP 调用程序不仅限于 Java 到 Java 远程处理,还不仅限于 Client 端和服务器端的 Spring。 (后者也适用于非 RMI 接口的 Spring RMI 调用程序.)

    在异类环境中运行时,Hessian 可能会提供重要的价值,因为它们明确允许使用非 JavaClient 端。但是,非 Java 支持仍然有限。已知的问题包括 Hibernate 对象的序列化以及延迟初始化的集合。如果您有这样的数据模型,请考虑使用 RMI 或 HTTP 调用程序而不是 Hessian。

    JMS 可用于提供服务集群,并使 JMS 代理负责负载平衡,发现和自动故障转移。默认情况下,Java 序列化用于 JMS 远程处理,但是 JMS 提供程序可以使用其他机制进行线路格式化,例如 XStream,以使服务器可以用其他技术实现。

    最后但并非最不重要的一点是,EJB 具有优于 RMI 的优势,因为它支持基于标准角色的身份验证和授权以及远程事务传播。尽管核心 Spring 并没有提供 RMI 调用程序或 HTTP 调用程序来支持安全上下文传播,但也有可能。 Spring 仅提供用于插入第三方或自定义解决方案的钩子。

    1.8. REST 端点

    Spring 框架提供了两种选择来调用 REST 端点:

  • Using RestTemplate:具有同步模板方法 API 的原始 Spring RESTClient 端。

  • WebClient:一种非阻塞的,Reactive 的替代方案,它支持同步和异步以及流方案。

  • 从 5.0 开始,无阻塞,响应式WebClient提供了RestTemplate的现代替代方案,并有效支持同步和异步以及流方案。 RestTemplate将在将来的版本中弃用,并且以后将不会添加主要的新功能。

    1.8.1. 使用 RestTemplate

    RestTemplate通过 HTTPClient 端库提供了更高级别的 API。它使在一行中轻松调用 REST 端点变得容易。它公开了以下几组重载方法:

    表 1. RestTemplate 方法

    patchForObject 通过使用 PATCH 更新资源,并从响应中返回表示形式。请注意,JDK HttpURLConnection不支持PATCH,但是 Apache HttpComponents 和其他支持。 delete 使用 DELETE 删除指定 URI 处的资源。 optionsForAllow 通过使用 ALLOW 检索资源的允许的 HTTP 方法。 exchange 前述方法的通用性强(且不那么固执)版本,可在需要时提供额外的灵 Active。它接受RequestEntity(包括 HTTP 方法,URL,Headers 和正文作为 Importing),并返回ResponseEntity


    这些方法允许使用ParameterizedTypeReference而不是Class来指定具有泛型的响应类型。
    | execute |执行请求的最通用方法,完全控制通过回调接口进行的请求准备和响应提取。

    Initialization

    默认构造函数使用java.net.HttpURLConnection执行请求。您可以使用ClientHttpRequestFactory实现切换到其他 HTTP 库。内置支持以下内容:

  • Apache HttpComponents

  • Netty

  • OkHttp

  • 例如,要切换到 Apache HttpComponents,可以使用以下命令:

    RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
      

    每个ClientHttpRequestFactory都公开特定于基础 HTTPClient 端库的配置选项,例如用于凭证,连接池和其他详细信息。

    请注意,访问表示错误的响应状态(例如 401)时,HTTP 请求的java.net实现可能引发异常。如果这是一个问题,请切换到另一个 HTTPClient 端库。

    许多RestTemplate方法都接受 URI 模板和 URI 模板变量,它们可以作为String变量参数或Map<String,String>

    下面的示例使用一个String变量参数:

    String result = restTemplate.getForObject(
            "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
      

    以下示例使用Map<String, String>

    Map<String, String> vars = Collections.singletonMap("hotel", "42");
    String result = restTemplate.getForObject(
            "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
      

    请注意,URI 模板是自动编码的,如以下示例所示:

    restTemplate.getForObject("http://example.com/hotel list", String.class);
    // Results in request to "http://example.com/hotel%20list"
      

    您可以使用RestTemplateuriTemplateHandler属性来自定义 URI 的 encodings。或者,您可以准备java.net.URI并将其传递到接受URIRestTemplate方法之一。

    有关使用和编码 URI 的更多详细信息,请参见URI Links

    Headers

    您可以使用exchange()方法来指定请求 Headers,如以下示例所示:

    String uriTemplate = "http://example.com/hotels/{hotel}";
    URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);
    RequestEntity<Void> requestEntity = RequestEntity.get(uri)
            .header(("MyRequestHeader", "MyValue")
            .build();
    ResponseEntity<String> response = template.exchange(requestEntity, String.class);
    String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
    String body = response.getBody();
      

    您可以通过许多返回ResponseEntityRestTemplate方法变体来获取响应 Headers。

    HttpMessageConverter的帮助下,通过RestTemplate方法传递和返回的对象将与原始内容进行转换。

    在 POST 上,Importing 对象被序列化到请求主体,如以下示例所示:

    URI location = template.postForLocation("http://example.com/people", person);
      

    您无需显式设置请求的 Content-TypeHeaders。在大多数情况下,您可以找到基于源Object类型的兼容消息转换器,并且所选消息转换器会相应地设置 Content Type。如有必要,可以使用exchange方法显式提供Content-Type请求 Headers,从而影响选择哪个消息转换器。

    在 GET 上,响应的主体反序列化为输出Object,如以下示例所示:

    Person person = restTemplate.getForObject("http://example.com/people/{id}", Person.class, 42);
      

    不需要明确设置请求的AcceptHeaders。在大多数情况下,可以根据预期的响应类型找到兼容的消息转换器,这有助于填充AcceptHeaders。如有必要,可以使用exchange方法显式提供AcceptHeaders。

    默认情况下,RestTemplate注册所有内置的message converters,具体取决于有助于确定存在哪些可选转换库的 Classpath 检查。您还可以将消息转换器设置为显式使用。

    Message Conversion

    与 Spring WebFlux 中的相同

    spring-web模块包含HttpMessageConverter协定,用于通过InputStreamOutputStream读写 HTTP 请求和响应的正文。 HttpMessageConverter实例用于 Client 端(例如RestTemplate)和服务器端(例如 Spring MVC REST 控制器)。

    框架中提供了主要媒体(MIME)类型的具体实现,默认情况下,它们在 Client 端的RestTemplate和服务器端的RequestMethodHandlerAdapter注册(请参见配置消息转换器)。

    以下各节介绍了HttpMessageConverter的实现。对于所有转换器,都使用默认的媒体类型,但是您可以通过设置supportedMediaTypes bean 属性来覆盖它。下表描述了每种实现:

    表 2. HttpMessageConverter 实现

    StringHttpMessageConverter 可以从 HTTP 请求和响应读取和写入String实例的HttpMessageConverter实现。默认情况下,此转换器支持所有文本媒体类型(text/*)并以Content-Typetext/plain写入。 FormHttpMessageConverter 可以从 HTTP 请求和响应中读取和写入表单数据的HttpMessageConverter实现。默认情况下,此转换器读取和写入application/x-www-form-urlencoded媒体类型。从MultiValueMap<String, String>读取表格数据并将其写入。 ByteArrayHttpMessageConverter 可以从 HTTP 请求和响应读取和写入字节数组的HttpMessageConverter实现。默认情况下,此转换器支持所有媒体类型(*/*)并以Content-Type application/octet-stream写入。您可以通过设置supportedMediaTypes属性并覆盖getContentType(byte[])来覆盖它。 MarshallingHttpMessageConverter 可以使用org.springframework.oxm包中的 Spring 的MarshallerUnmarshaller抽象来读取和写入 XML 的HttpMessageConverter实现。该转换器需要使用MarshallerUnmarshaller才能使用。您可以通过构造函数或 bean 属性注入它们。默认情况下,此转换器支持text/xmlapplication/xmlMappingJackson2HttpMessageConverter 可以使用 Jackson 的ObjectMapper读取和写入 JSON 的HttpMessageConverter实现。您可以根据需要使用 Jackson 提供的 Comments 来自定义 JSONMap。当您需要进一步控制时(对于需要为特定类型提供自定义 JSON 序列化器/反序列化器的情况),可以通过ObjectMapper属性注入自定义ObjectMapper。默认情况下,此转换器支持application/jsonMappingJackson2XmlHttpMessageConverter 可以使用Jackson XMLextensionsXmlMapper读写 XML 的HttpMessageConverter实现。您可以根据需要使用 JAXB 或 Jackson 提供的 Comments 来自定义 XMLMap。当您需要进一步控制时(对于需要为特定类型提供自定义 XML 序列化器/反序列化器的情况),可以通过ObjectMapper属性注入自定义XmlMapper。默认情况下,此转换器支持application/xmlSourceHttpMessageConverter 可以从 HTTP 请求和响应中读取和写入javax.xml.transform.SourceHttpMessageConverter实现。仅支持DOMSourceSAXSourceStreamSource。默认情况下,此转换器支持text/xmlapplication/xmlBufferedImageHttpMessageConverter 可以从 HTTP 请求和响应中读取和写入java.awt.image.BufferedImageHttpMessageConverter实现。该转换器读取和写入 Java I/O API 支持的媒体类型。
    Jackson JSON 视图

    您可以指定JacksonJSON 视图来仅序列化对象属性的一个子集,如以下示例所示:

    MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
    value.setSerializationView(User.WithoutPasswordView.class);
    RequestEntity<MappingJacksonValue> requestEntity =
        RequestEntity.post(new URI("http://example.com/user")).body(value);
    ResponseEntity<String> response = template.exchange(requestEntity, String.class);
      
    Multipart

    要发送 Multipart 数据,您需要提供一个MultiValueMap<String, ?>,其值要么是代表 Component 内容的Object实例,要么是代表 Component 内容和头的HttpEntity实例。 MultipartBodyBuilder提供了一个方便的 API 来准备 Multipart 请求,如以下示例所示:

    MultipartBodyBuilder builder = new MultipartBodyBuilder();
        builder.part("fieldPart", "fieldValue");
        builder.part("filePart", new FileSystemResource("...logo.png"));
        builder.part("jsonPart", new Person("Jason"));
        MultiValueMap<String, HttpEntity<?>> parts = builder.build();
      

    在大多数情况下,您不必为每个部分指定Content-Type。Content Type 是根据要序列化的HttpMessageConverter自动确定的,对于Resource则根据文件 extensions 自动确定。如有必要,您可以通过重载的生成器part方法之一显式提供MediaType供每个 Component 使用。

    MultiValueMap准备就绪后,您可以将其传递给RestTemplate,如以下示例所示:

    MultipartBodyBuilder builder = ...;
        template.postForObject("http://example.com/upload", builder.build(), Void.class);
      

    如果MultiValueMap包含至少一个非String值,该值也可以表示常规表单数据(即application/x-www-form-urlencoded),则无需将Content-Type设置为multipart/form-data。当您使用MultipartBodyBuilder来确保HttpEntity包装器时,总是如此。

    1.8.2. 使用 AsyncRestTemplate(不建议使用)

    AsyncRestTemplate已弃用。对于所有您可能考虑使用AsyncRestTemplate的用例,请改用WebClient

    2.企业 JavaBeans(EJB)集成

    作为轻量级容器,Spring 通常被认为是 EJB 的替代品。我们确实相信,对于许多(即使不是大多数)应用程序和用例,Spring 作为容器,结合其在事务,ORM 和 JDBC 访问领域的丰富支持功能,比通过 EJB 实现等效功能是更好的选择。容器和 EJB。

    但是,请务必注意,使用 Spring 不会阻止您使用 EJB。实际上,Spring 使访问 EJB 以及在其中实现 EJB 和功能变得更加容易。另外,使用 Spring 访问 EJB 提供的服务可以使这些服务的实现稍后在本地 EJB,远程 EJB 或 POJO(普通旧 Java 对象)变体之间透明切换,而不必更改 Client 端代码。

    在本章中,我们将研究 Spring 如何帮助您访问和实现 EJB。当访问 Stateless 会话 Bean(SLSB)时,Spring 提供了特殊的价值,因此我们从讨论这个主题开始。

    2.1. 访问 EJB

    本节介绍如何访问 EJB。

    2.1.1. Concepts

    要在本地或远程 Stateless 会话 Bean 上调用方法,Client 端代码通常必须执行 JNDI 查找以获取(本地或远程)EJB Home 对象,然后对该对象使用create方法调用以获取实际的(本地或远程)Bean。 )EJB 对象。然后在 EJB 上调用一种或多种方法。

    为了避免重复的低级代码,许多 EJB 应用程序都使用服务定位器和业务委托模式。这些比在整个 Client 端代码中喷射 JNDI 查找要好,但是它们的常规实现有很多缺点:

  • 通常,使用 EJB 的代码取决于 Service Locator 或 Business Delegate 单例,使其难以测试。

  • 在不使用业务委托的情况下使用服务定位器模式的情况下,应用程序代码仍然最终必须在 EJB home 上调用create()方法并处理产生的异常。因此,它仍然与 EJB API 和 EJB 编程模型的复杂性联系在一起。

  • 实现业务委托模式通常会导致大量的代码重复,我们必须编写许多在 EJB 上调用相同方法的方法。

  • Spring 的方法是允许创建和使用代理对象(通常在 Spring 容器内配置),这些代理对象充当无代码的业务委托。除非您在此类代码中实际添加了实际价值,否则您无需在手动编码的业务委托中编写另一个 Service Locator,另一个 JNDI 查找或重复方法。

    2.1.2. 访问本地 SLSB

    假设我们有一个需要使用本地 EJB 的 Web 控制器。我们遵循最佳实践,并使用 EJB 业务方法接口模式,以便 EJB 的本地接口扩展了非 EJB 特定的业务方法接口。我们将此业务方法界面称为MyComponent。以下示例显示了这样的接口:

    public interface MyComponent {
      

    使用业务方法接口模式的主要原因之一是确保本地接口中的方法签名与 bean 实现类之间的同步是自动的。另一个原因是,如果有必要的话,以后可以使我们更轻松地切换到服务的 POJO(普通旧 Java 对象)实现。我们还需要实现本地 home 接口,并提供实现SessionBeanMyComponent业务方法接口的实现类。现在,将 Web 层控制器连接到 EJB 实现所需要做的唯一 Java 编码是在控制器上公开MyComponent类型的 setter 方法。这会将引用另存为控制器中的实例变量。以下示例显示了如何执行此操作:

    private MyComponent myComponent;
    public void setMyComponent(MyComponent myComponent) {
        this.myComponent = myComponent;
      

    随后,我们可以在控制器中的任何业务方法中使用此实例变量。现在,假设我们从 Spring 容器中获取控制器对象,我们可以(在相同上下文中)配置LocalStatelessSessionProxyFactoryBean实例,它是 EJB 代理对象。我们配置代理,并使用以下配置条目设置控制器的myComponent属性:

    <bean id="myComponent"
            class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
        <property name="jndiName" value="ejb/myBean"/>
        <property name="businessInterface" value="com.mycom.MyComponent"/>
    </bean>
    <bean id="myController" class="com.mycom.myController">
        <property name="myComponent" ref="myComponent"/>
    </bean>
      

    尽管没有强迫您使用 AOP 概念来欣赏结果,但仍要依靠 Spring AOP 框架在幕后进行大量工作。 myComponent bean 定义为 EJB 创建了一个代理,该代理实现了业务方法接口。 EJB 本地主目录在启动时被缓存,因此只有一个 JNDI 查找。每次调用 EJB 时,代理都会在本地 EJB 上调用classname方法,并在 EJB 上调用相应的业务方法。

    myController bean 定义将控制器类的myComponent属性设置为 EJB 代理。

    或者(最好在许多此类代理定义的情况下),考虑在 Spring 的“ jee”名称空间中使用<jee:local-slsb>配置元素。以下示例显示了如何执行此操作:

    <jee:local-slsb id="myComponent" jndi-name="ejb/myBean"
            business-interface="com.mycom.MyComponent"/>
    <bean id="myController" class="com.mycom.myController">
        <property name="myComponent" ref="myComponent"/>
    </bean>
      

    这种 EJB 访问机制极大地简化了应用程序代码。 Web 层代码(或其他 EJBClient 端代码)与 EJB 的使用无关。要用 POJO 或模拟对象或其他测试存根替换该 EJB 引用,我们可以更改myComponent bean 定义而无需更改任何 Java 代码。此外,作为应用程序的一部分,我们不必编写任何一行 JNDI 查找或其他 EJB 管道代码。

    实际应用中的基准和经验表明,这种方法的性能开销(涉及目标 EJB 的反射调用)是最小的,并且在常规使用中是无法检测到的。请记住,无论如何我们都不希望对 EJB 进行细粒度的调用,因为与应用程序服务器中的 EJB 基础结构相关联的成本很高。

    关于 JNDI 查找有一个警告。在 bean 容器中,此类通常最好用作单例(没有理由使其成为原型)。但是,如果该 bean 容器预先实例化了单例(与各种 XML ApplicationContext变体一样),则在 EJB 容器加载目标 EJB 之前加载 bean 容器时可能会出现问题。这是因为 JNDI 查找是在此类的init()方法中执行的,然后进行了缓存,但是 EJB 尚未绑定到目标位置。解决方案是不预先实例化该工厂对象,而是让它在首次使用时创建。在 XML 容器中,您可以使用lazy-init属性来控制它。

    尽管大多数 Spring 用户都不感兴趣,但是那些使用 EJB 进行编程 AOP 的用户可能希望查看LocalSlsbInvokerInterceptor

    2.1.3. 访问远程 SLSB

    除了使用SimpleRemoteStatelessSessionProxyFactoryBean<jee:remote-slsb>配置元素外,访问远程 EJB 与访问本地 EJB 基本相同。当然,无论是否使用 Spring,远程调用语义都适用:调用另一台计算机上另一台 VM 中的对象上的方法时,有时在使用情况和故障处理方面必须区别对待。

    与非 Spring 方法相比,Spring 的 EJBClient 端支持增加了另一个优势。通常,在本地或远程调用 EJB 之间轻松地来回切换 EJBClient 端代码是有问题的。这是因为远程接口方法必须声明它们抛出RemoteException,而 Client 端代码必须对此进行处理,而本地接口方法则不需要。通常需要修改为需要移至远程 EJB 的本地 EJB 编写的 Client 端代码,以添加对远程异常的处理,为需要移至本地 EJB 的远程 EJB 编写的 Client 端代码可以保持不变,但可以执行以下操作:许多不必要的远程异常处理,或进行修改以删除该代码。使用 Spring 远程 EJB 代理,您不能在业务方法接口和实现 EJB 代码中声明任何抛出的RemoteException,具有相同的远程接口(除了它确实抛出RemoteException),并且依靠代理来动态地处理两个接口就像它们一样。也就是说,Client 端代码不必处理已检查的RemoteException类。在 EJB 调用期间抛出的任何实际RemoteException都将重新抛出为未经检查的RemoteAccessException类,该类是RuntimeException的子类。然后,您可以在本地 EJB 或远程 EJB(甚至纯 Java 对象)实现之间随意切换目标服务,而无需了解或关心 Client 端代码。当然,这是可选的:没有什么可以阻止您在业务界面中声明RemoteException

    2.1.4. 访问 EJB 2.x SLSB 与 EJB 3 SLSB

    通过 Spring 访问 EJB 2.x 会话 Bean 和 EJB 3 会话 Bean 在很大程度上是透明的。 Spring 的 EJB 访问器(包括<jee:local-slsb><jee:remote-slsb>设施)在运行时透明地适应实际组件。它们会处理一个 Home 接口(如果找到)(EJB 2.x 样式),或者在没有可用 Home 接口(EJB 3 样式)的情况下执行直接组件调用。

    注意:对于 EJB 3 会话 Bean,您还可以有效地使用JndiObjectFactoryBean/<jee:jndi-lookup>,因为公开了完全可用的组件引用以用于在那里的普通 JNDI 查找。定义明确的<jee:local-slsb><jee:remote-slsb>查找可提供一致且更明确的 EJB 访问配置。

    3. JMS(Java 消息服务)

    Spring 提供了一个 JMS 集成框架,该框架简化了 JMS API 的使用,就像 Spring 对 JDBC API 的集成一样。

    JMS 可以大致分为两个功能区域,即消息的产生和使用。 JmsTemplate类用于消息生成和同步消息接收。对于类似于 Java EE 的消息驱动 bean 样式的异步接收,Spring 提供了许多消息侦听器容器,可用于创建消息驱动 POJO(MDP)。 Spring 还提供了一种声明式方法来创建消息侦听器。

    org.springframework.jms.core软件包提供了使用 JMS 的核心功能。它包含 JMS 模板类,该类通过处理资源的创建和释放来简化 JMS 的使用,就像JdbcTemplate对于 JDBC 一样。 Spring 模板类共有的设计原则是提供帮助器方法来执行常用操作,并且对于更复杂的用法,将处理任务的本质委托给用户实现的回调接口。 JMS 模板遵循相同的设计。这些类提供了各种方便的方法,用于发送消息,同步使用消息以及向用户公开 JMS 会话和消息生成器。

    org.springframework.jms.support软件包提供JMSException翻译功能。转换将已检查的JMSException层次结构转换为未检查的异常的镜像层次结构。如果存在选中的javax.jms.JMSException的任何提供程序特定的子类,则将此异常包装在未选中的UncategorizedJmsException中。

    org.springframework.jms.support.converter包提供MessageConverter抽象以在 Java 对象和 JMS 消息之间进行转换。

    org.springframework.jms.support.destination包提供了用于 ManagementJMS 目的地的各种策略,例如为 JNDI 中存储的目的地提供服务定位器。

    org.springframework.jms.annotation软件包提供了必要的基础结构,以通过使用@JmsListener支持 Comments 驱动的侦听器端点。

    org.springframework.jms.config软件包为jms名称空间提供了解析器实现,并提供了 Java config 支持以配置侦听器容器和创建侦听器端点。

    最后,org.springframework.jms.connection软件包提供了适用于独立应用程序的ConnectionFactory的实现。它还包含用于 JMS 的 Spring PlatformTransactionManager的实现(巧妙地名为JmsTransactionManager)。这允许将 JMS 作为事务资源无缝集成到 Spring 的事务 Management 机制中。

    3.1. 使用 Spring JMS

    本节描述如何使用 Spring 的 JMS 组件。

    3.1.1. 使用 JmsTemplate

    JmsTemplate类是 JMS 核心软件包中的中心类。由于它在发送或同步接收消息时处理资源的创建和释放,因此它简化了 JMS 的使用。

    使用JmsTemplate的代码仅需要实现回调接口,即可为其提供明确定义的高级 Contract。当给JmsTemplate中的调用代码提供Session时,MessageCreator回调接口会创建一条消息。为了允许更复杂地使用 JMS API,SessionCallback提供了 JMS 会话,而ProducerCallback公开了SessionMessageProducer对。

    JMS API 公开了两种类型的发送方法,一种采用交付模式,优先级和生存时间作为服务质量(QOS)参数,另一种不采用 QOS 参数并使用默认值。由于JmsTemplate有许多发送方法,因此设置 QOS 参数已作为 bean 属性公开,以避免重复发送方法。同样,使用setReceiveTimeout属性设置同步接收调用的超时值。

    某些 JMS 提供程序允许通过ConnectionFactory的配置来 Management 默认 QOS 值的设置。这样做的结果是,对MessageProducer实例的send方法(send(Destination destination, Message message))的调用使用与 JMS 规范中指定的 QOS 默认值不同的 QOS 默认值。为了提供对 QOS 值的一致 Management,因此,必须通过将布尔属性isExplicitQosEnabled设置为true来专门使JmsTemplate使用其自己的 QOS 值。

    为方便起见,JmsTemplate还公开了一个基本的请求-答复操作,该操作允许发送消息并 await 作为该操作一部分而创建的临时队列的答复。

    JmsTemplate类的实例一旦配置便是线程安全的。这很重要,因为这意味着您可以配置JmsTemplate的单个实例,然后将该共享引用安全地注入多个协作者中。需要明确的是,JmsTemplate是有状态的,因为它保持对ConnectionFactory的引用,但是此状态不是会话状态。

    从 Spring Framework 4.1 开始,JmsMessagingTemplate构建在JmsTemplate的基础上,并提供了与消息传递抽象(即org.springframework.messaging.Message)的集成。这使您可以创建以通用方式发送的消息。

    3.1.2. Connections

    JmsTemplate要求引用ConnectionFactoryConnectionFactory是 JMS 规范的一部分,并且是使用 JMS 的入口点。Client 端应用程序使用它作为工厂来创建与 JMS 提供程序的连接,并封装各种配置参数,其中许多是特定于供应商的,例如 SSL 配置选项。

    当在 EJB 中使用 JMS 时,供应商提供 JMS 接口的实现,以便它们可以参与声明式事务 Management 并执行连接和会话的池化。为了使用此实现,Java EE 容器通常要求您在 EJB 或 Servlet 部署 Descriptors 中将 JMS 连接工厂声明为resource-ref。为了确保在 EJB 内的JmsTemplate中使用这些功能,Client 端应用程序应确保引用了ConnectionFactory的托管实现。

    缓存消息传递资源

    标准 API 涉及创建许多中间对象。要发送消息,请执行以下“ API”遍历:

    ConnectionFactory->Connection->Session->MessageProducer->send
      

    ConnectionFactorySend操作之间,创建并销毁了三个中间对象。为了优化资源使用并提高性能,Spring 提供了ConnectionFactory的两种实现。

    Using SingleConnectionFactory

    Spring 提供了ConnectionFactory接口SingleConnectionFactory的实现,该接口在所有createConnection()调用中返回相同的Connection,而忽略对close()的调用。这对于测试和独立环境很有用,因此同一连接可用于可能跨越任意数量事务的多个JmsTemplate调用。 SingleConnectionFactory引用了通常来自 JNDI 的标准ConnectionFactory

    Using CachingConnectionFactory

    CachingConnectionFactory扩展了SingleConnectionFactory的功能,并添加了SessionMessageProducerMessageConsumer实例的缓存。初始缓存大小设置为1。您可以使用sessionCacheSize属性来增加缓存的会话数。请注意,由于根据会话的确认模式缓存会话,因此实际缓存的会话数大于该数量,因此,当sessionCacheSize设置为 one 时,最多可以有四个缓存的会话实例(每个确认模式一个)。 MessageProducerMessageConsumer实例被缓存在它们自己的会话中,并且在缓存时还考虑了生产者和使用者的唯一属性。 MessageProducers 将根据其目的地进行缓存。基于由目标,selectors,noLocal 传递标志和持久订阅名称(如果创建持久使用者)组成的键来缓存 MessageConsumers。

    3.1.3. 目的地 Management

    目标是ConnectionFactory实例,是可以在 JNDI 中存储和检索的 JMSManagement 的对象。在配置 Spring 应用程序上下文时,可以使用 JNDI JndiObjectFactoryBean factory 类或<jee:jndi-lookup>对对象对 JMS 目标的引用执行依赖项注入。但是,如果应用程序中有大量目标,或者 JMS 提供程序具有独特的高级目标 Management 功能,则此策略通常很麻烦。这种高级目标 Management 的示例包括动态目标的创建或对目标的分层名称空间的支持。 JmsTemplate将目标名称的解析委托给实现DestinationResolver接口的 JMS 目标对象。 DynamicDestinationResolverJmsTemplate使用的默认实现,并且可以解析动态目标。还提供JndiDestinationResolver充当 JNDI 中包含的目的地的服务定位器,并且可以选择退回到DynamicDestinationResolver中包含的行为。

    通常,仅在运行时才知道 JMS 应用程序中使用的目的地,因此,在部署应用程序时无法通过 Management 方式创建。这通常是因为在交互的系统组件之间存在共享的应用程序逻辑,这些组件根据已知的命名约定在运行时创建目标。即使创建动态目标不属于 JMS 规范的一部分,但大多数供应商都提供了此功能。动态目标是使用用户定义的名称创建的,该名称将它们与临时目标区分开来,并且通常未在 JNDI 中注册。各个提供者之间用于创建动态目的地的 API 有所不同,因为与目的地关联的属性是特定于供应商的。但是,供应商有时会做出一个简单的实现选择,就是忽略 JMS 规范中的警告,并使用方法TopicSession createTopic(String topicName)QueueSession createQueue(String queueName)方法来创建具有默认目标属性的新目标。根据供应商的实现,DynamicDestinationResolver然后还可以创建一个物理目标,而不是仅解决一个物理目标。

    布尔属性pubSubDomain用于在知道正在使用哪个 JMS 域的情况下配置JmsTemplate。默认情况下,此属性的值为 false,指示将使用点对点域Queues。此属性(由JmsTemplate使用)通过DestinationResolver接口的实现确定动态目标解析的行为。

    您还可以通过属性defaultDestinationJmsTemplate配置为默认目标。默认目标是带有不引用特定目标的发送和接收操作。

    3.1.4. 消息侦听器容器

    在 EJB 世界中,JMS 消息最常见的用途之一是驱动消息驱动的 bean(MDB)。 Spring 提供了一种解决方案,以不将用户绑定到 EJB 容器的方式创建消息驱动的 POJO(MDP)。 (有关 Spring 对 MDP 支持的详细介绍,请参见异步接收:消息驱动的 POJO。)从 Spring Framework 4.1 开始,可以用@JmsListenerComments 端点方法,请参见Comments 驱动的侦听器端点以获取更多详细信息。

    消息侦听器容器用于从 JMS 消息队列接收消息,并驱动注入到其中的MessageListener。侦听器容器负责消息接收的所有线程,并分派到侦听器中进行处理。消息侦听器容器是 MDP 与消息传递提供程序之间的中介,并负责注册接收消息,参与事务,资源获取和释放,异常转换等。这使您可以编写与接收消息(并可能对其进行响应)相关的(可能很复杂的)业务逻辑,并将样板 JMS 基础结构问题委托给框架。

    Spring 附带了两个标准的 JMS 消息侦听器容器,每个容器都有其专门的功能集。

  • SimpleMessageListenerContainer

  • DefaultMessageListenerContainer

  • Using SimpleMessageListenerContainer

    此消息侦听器容器是两种标准样式中的简单容器。它在启动时创建固定数量的 JMS 会话和使用者,使用标准 JMS MessageConsumer.setMessageListener()方法注册侦听器,并将其留给 JMS 提供者执行侦听器回调。此变体不允许动态适应运行时需求或参与外部 Management 的事务。在兼容性方面,它非常接近独立 JMS 规范的精神,但通常与 Java EE 的 JMS 限制不兼容。

    尽管SimpleMessageListenerContainer不允许参与外部 Management 的事务,但它支持本机 JMS 事务。要启用此功能,可以将sessionTransacted标志切换为true,或者在 XML 名称空间中将acknowledge属性设置为transacted。然后,从您的侦听器抛出的异常会导致回滚,并重新传递消息。或者,考虑使用CLIENT_ACKNOWLEDGE模式,该模式在出现异常的情况下也可以重新传送,但不使用事务处理的Session实例,因此在事务协议中不包括任何其他Session操作(例如发送响应消息)。

    默认的AUTO_ACKNOWLEDGE模式不能提供适当的可靠性保证。当侦听器执行失败时(由于提供者在侦听器调用之后会自动确认每条消息,没有异常要传播到提供者),或者在侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping标志进行配置),消息可能会丢失。确保出于可靠性需求(例如,为了可靠的队列处理和持久的主题订阅)使用事务处理的会话。

    Using DefaultMessageListenerContainer

    大多数情况下使用此消息侦听器容器。与SimpleMessageListenerContainer相比,此容器变体允许动态适应运行时需求,并且能够参与外部 Management 的事务。配置为JtaTransactionManager时,每个接收到的消息都将注册到 XA 事务中。结果,处理可以利用 XA 事务语义。该侦听器容器在对 JMS 提供程序的低要求,高级功能(例如参与外部 Management 的事务)以及与 Java EE 环境的兼容性之间取得了良好的平衡。

    您可以自定义容器的缓存级别。请注意,当未启用缓存时,将为每个消息接收创建一个新的连接和一个新的会话。将此内容与具有高负载的非持久订阅结合使用可能会导致消息丢失。在这种情况下,请确保使用适当的缓存级别。

    当代理关闭时,此容器还具有可恢复的功能。默认情况下,简单的BackOff实现每五秒钟重试一次。您可以为更细粒度的恢复选项指定自定义BackOff实现。有关示例,请参见 api-spring-framework/util/backoff/ExponentialBackOff.html [+516+]。

    与其同级(SimpleMessageListenerContainer)一样,DefaultMessageListenerContainer支持本机 JMS 事务,并允许自定义确认模式。如果对您的方案可行,则强烈建议在外部 Management 的事务上使用此方法,也就是说,如果 JVM 死亡,您可以偶尔接收重复消息。业务逻辑中的自定义重复消息检测步骤可以解决这种情况,例如以业务实体存在检查或协议表检查的形式。任何这样的安排都比其他安排更为有效:用 XA 事务包装整个处理过程(通过将DefaultMessageListenerContainer配置为JtaTransactionManager)来覆盖 JMS 消息的接收以及消息侦听器中业务逻辑的执行(包括数据库操作等)。

    默认的AUTO_ACKNOWLEDGE模式不能提供适当的可靠性保证。当侦听器执行失败时(由于提供者在侦听器调用之后会自动确认每条消息,没有异常要传播到提供者),或者在侦听器容器关闭时(您可以通过设置acceptMessagesWhileStopping标志进行配置),消息可能会丢失。确保出于可靠性需求(例如,为了可靠的队列处理和持久的主题订阅)使用事务处理的会话。

    3.1.5. TransactionManagement

    Spring 提供了一个JmsTransactionManager,用于 Management 单个 JMS ConnectionFactory的事务。如数据访问一章的事务 Management 部分所述,这使 JMS 应用程序可以利用 Spring 的托管事务功能。 JmsTransactionManager执行本地资源事务,将来自指定ConnectionFactory的 JMS 连接/会话对绑定到线程。 JmsTemplate自动检测此类 Transaction 资源并相应地对其进行操作。

    在 Java EE 环境中,ConnectionFactory汇集了 Connection 和 Session 实例,因此可以有效地在事务之间重用这些资源。在独立环境中,使用 Spring 的SingleConnectionFactory会导致共享 JMS Connection,每个事务都有自己的独立Session。或者,考虑使用提供程序专用的池适配器,例如 ActiveMQ 的PooledConnectionFactory类。

    您还可以将JmsTemplateJtaTransactionManager和具有 XA 功能的 JMS ConnectionFactory结合使用来执行分布式事务。请注意,这需要使用 JTA 事务 Management 器以及正确的 XA 配置的 ConnectionFactory。 (检查您的 Java EE 服务器或 JMS 提供程序的文档.)

    使用 JMS API 从Connection创建Session时,在托管和非托管事务环境中重用代码可能会造成混淆。这是因为 JMS API 只有一个工厂方法来创建Session,并且它需要事务和确认模式的值。在托管环境中,设置这些值是环境的事务基础结构的责任,因此,供应商对 JMS Connection 的包装将忽略这些值。在非托管环境中使用JmsTemplate时,可以通过使用属性sessionTransactedsessionAcknowledgeMode来指定这些值。当您将PlatformTransactionManagerJmsTemplate一起使用时,始终为模板提供事务 JMS Session

    3.2. 发送信息

    JmsTemplate包含许多发送消息的便捷方法。发送方法使用javax.jms.Destination对象指定目标,其他方法通过在 JNDI 查找中使用String指定目标。不使用目标参数的send方法使用默认目标。

    以下示例使用MessageCreator回调从提供的Session对象创建文本消息:

    import javax.jms.ConnectionFactory;
    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.Queue;
    import javax.jms.Session;
    import org.springframework.jms.core.MessageCreator;
    import org.springframework.jms.core.JmsTemplate;
    public class JmsQueueSender {
        private JmsTemplate jmsTemplate;
        private Queue queue;
        public void setConnectionFactory(ConnectionFactory cf) {
            this.jmsTemplate = new JmsTemplate(cf);
        public void setQueue(Queue queue) {
            this.queue = queue;
        public void simpleSend() {
            this.jmsTemplate.send(this.queue, new MessageCreator() {
                public Message createMessage(Session session) throws JMSException {
                    return session.createTextMessage("hello queue world");
      

    在前面的示例中,JmsTemplate是通过将引用传递给ConnectionFactory来构造的。或者,提供零参数构造函数和connectionFactory,它们可用于以 JavaBean 样式(使用BeanFactory或纯 Java 代码)构造实例。或者,考虑从 Spring 的JmsGatewaySupport便捷 Base Class 派生,该 Base Class 为 JMS 配置提供了预构建的 bean 属性。

    send(String destinationName, MessageCreator creator)方法使您可以使用目标的字符串名称发送消息。如果这些名称已在 JNDI 中注册,则应将模板的destinationResolver属性设置为JndiDestinationResolver的实例。

    如果您创建了JmsTemplate并指定了默认目的地,则send(MessageCreator c)会向该目的地发送一条消息。

    3.2.1. 使用消息转换器

    为了方便域模型对象的发送,JmsTemplate具有各种发送方法,这些方法将 Java 对象作为消息数据内容的参数。 JmsTemplate中的重载方法convertAndSend()receiveAndConvert()方法将转换过程委托给MessageConverter接口的实例。该接口定义了一个简单的协定,可以在 Java 对象和 JMS 消息之间进行转换。默认实现(SimpleMessageConverter)支持StringTextMessagebyte[]BytesMesssage以及java.util.MapMapMessage之间的转换。通过使用转换器,您和您的应用程序代码可以专注于通过 JMS 发送或接收的业务对象,而不必担心如何将其表示为 JMS 消息。

    沙箱当前包含一个MapMessageConverter,它使用反射在 JavaBean 和MapMessage之间进行转换。您可能自己实现的其他流行实现选择是使用现有 XML 编组程序包(例如 JAXB,Castor 或 XStream)创建代表对象的TextMessage的转换器。

    为了适应消息属性,Headers 和正文的设置,这些属性通常不能封装在转换器类中,因此MessagePostProcessor接口使您可以在转换消息之后但在发送消息之前对其进行访问。以下示例显示了将java.util.Map转换为消息后如何修改消息头和属性:

    public void sendWithConversion() {
        Map map = new HashMap();
        map.put("Name", "Mark");
        map.put("Age", new Integer(47));
        jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
            public Message postProcessMessage(Message message) throws JMSException {
                message.setIntProperty("AccountID", 1234);
                message.setJMSCorrelationID("123-00001");
                return message;
      

    这将导致以下形式的消息:

    MapMessage={
        Header={
            ... standard headers ...
            CorrelationID={123-00001}
        Properties={
            AccountID={Integer:1234}
        Fields={
            Name={String:Mark}
            Age={Integer:47}
      

    3.2.2. 使用 SessionCallback 和 ProducerCallback

    尽管发送操作涵盖了许多常见的使用场景,但是您有时可能希望对 JMS SessionMessageProducer执行多个操作。 SessionCallbackProducerCallback分别暴露 JMS SessionSession/MessageProducer对。 JmsTemplate上的execute()方法执行这些回调方法。

    3.3. 接收讯息

    这描述了如何在 Spring 中使用 JMS 接收消息。

    3.3.1. 同步接收

    虽然 JMS 通常与异步处理相关联,但是您可以同步使用消息。重载的receive(..)方法提供了此功能。在同步接收期间,调用线程将阻塞,直到消息可用为止。这可能是危险的操作,因为调用线程可能会无限期地被阻塞。 receiveTimeout属性指定接收者在放弃 await 消息之前应该 await 多长时间。

    3.3.2. 异步接收:消息驱动的 POJO

    Spring 还通过使用@JmsListenerComments 支持带 Comments 的侦听器端点,并提供了开放的基础结构以编程方式注册端点。到目前为止,这是设置异步接收器的最便捷方法。有关更多详细信息,请参见启用侦听器端点 Comments

    消息驱动 POJO(MDP)以类似于 EJB 世界中的消息驱动 Bean(MDB)的方式充当 JMS 消息的接收者。 MDP 的一个限制(但请参见Using MessageListenerAdapter)是它必须实现javax.jms.MessageListener接口。请注意,如果您的 POJO 在多个线程上接收消息,则重要的是要确保您的实现是线程安全的。

    以下示例显示了 MDP 的简单实现:

    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.MessageListener;
    import javax.jms.TextMessage;
    public class ExampleListener implements MessageListener {
        public void onMessage(Message message) {
            if (message instanceof TextMessage) {
                try {
                    System.out.println(((TextMessage) message).getText());
                catch (JMSException ex) {
                    throw new RuntimeException(ex);
            else {
                throw new IllegalArgumentException("Message must be of type TextMessage");
      

    实现MessageListener之后,就可以创建消息侦听器容器了。

    以下示例显示如何定义和配置 Spring 附带的消息侦听器容器之一(在本例中为DefaultMessageListenerContainer):

    <!-- this is the Message Driven POJO (MDP) -->
    <bean id="messageListener" class="jmsexample.ExampleListener"/>
    <!-- and this is the message listener container -->
    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="messageListener"/>
    </bean>
      

    有关每个实现所支持功能的完整说明,请参见各种消息侦听器容器(所有这些都实现MessageListenerContainer)的 Spring javadoc。

    3.3.3. 使用 SessionAwareMessageListener 接口

    SessionAwareMessageListener接口是特定于 Spring 的接口,它提供与 JMS MessageListener接口相似的协定,但还使消息处理方法可以访问从中接收Message的 JMS Session。以下 Lists 显示了SessionAwareMessageListener接口的定义:

    package org.springframework.jms.listener;
    public interface SessionAwareMessageListener {
        void onMessage(Message message, Session session) throws JMSException;
      

    如果希望 MDP 能够响应任何接收到的消息(使用onMessage(Message, Session)方法中提供的Session),则可以选择让 MDP 实现此接口(优先于标准 JMS MessageListener接口)。 Spring 附带的所有消息侦听器容器实现都支持实现MessageListenerSessionAwareMessageListener接口的 MDP。实现SessionAwareMessageListener的类带有警告,然后通过接口将它们绑定到 Spring。是否使用它的选择完全由您作为应用程序开发人员或架构师来决定。

    请注意,SessionAwareMessageListener接口的onMessage(..)方法抛出JMSException。与标准 JMS MessageListener接口相反,在使用SessionAwareMessageListener接口时,Client 端代码负责处理所有引发的异常。

    3.3.4. 使用 MessageListenerAdapter

    MessageListenerAdapter类是 Spring 异步消息传递支持中的最后一个组件。简而言之,它使您几乎可以将任何类公开为 MDP(尽管存在一些约束)。

    考虑以下接口定义:

    public interface MessageDelegate {
        void handleMessage(String message);
        void handleMessage(Map message);
        void handleMessage(byte[] message);
        void handleMessage(Serializable message);
      

    请注意,尽管该接口既未扩展MessageListener也未扩展SessionAwareMessageListener接口,但仍可以通过使用MessageListenerAdapter类将其用作 MDP。还要注意如何根据各种Message类型的内容来强类型化各种消息处理方法,它们可以接收和处理。

    现在考虑MessageDelegate接口的以下实现:

    public class DefaultMessageDelegate implements MessageDelegate {
        // implementation elided for clarity...
      

    特别要注意的是,MessageDelegate接口(DefaultMessageDelegate类)的先前实现完全没有 JMS 依赖性。这确实是一个 POJO,我们可以通过以下配置将其变成 MDP:

    <!-- this is the Message Driven POJO (MDP) -->
    <bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <constructor-arg>
            <bean class="jmsexample.DefaultMessageDelegate"/>
        </constructor-arg>
    </bean>
    <!-- and this is the message listener container... -->
    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="messageListener"/>
    </bean>
      

    下一个示例显示另一个 MDP,它只能处理接收 JMS TextMessage消息。请注意,实际上是如何将消息处理方法称为receive(MessageListenerAdapter中的消息处理方法的名称默认为handleMessage),但是它是可配置的(如本节后面所述)。还请注意receive(..)方法是如何强类型 Importing 的,以便仅接收和响应 JMS TextMessage消息。以下 Lists 显示了TextMessageDelegate接口的定义:

    public interface TextMessageDelegate {
        void receive(TextMessage message);
      

    下面的 Lists 显示了实现TextMessageDelegate接口的类:

    public class DefaultTextMessageDelegate implements TextMessageDelegate {
        // implementation elided for clarity...
      

    话务员MessageListenerAdapter的配置如下:

    <bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <constructor-arg>
            <bean class="jmsexample.DefaultTextMessageDelegate"/>
        </constructor-arg>
        <property name="defaultListenerMethod" value="receive"/>
        <!-- we don't want automatic message context extraction -->
        <property name="messageConverter">
            <null/>
        </property>
    </bean>
      

    请注意,如果messageListener收到的类型不是TextMessage的 JMS Message,则会抛出IllegalStateException(随后将其吞咽)。 MessageListenerAdapter类的另一个功能是,如果处理程序方法返回非无效值,则自动发送回响应Message的功能。考虑以下接口和类:

    public interface ResponsiveTextMessageDelegate {
        // notice the return type...
        String receive(TextMessage message);
      
    public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
        // implementation elided for clarity...
      

    如果将DefaultResponsiveTextMessageDelegateMessageListenerAdapter结合使用,则从'receive(..)'方法的执行返回的任何非 null 值都将(在默认配置中)转换为TextMessage。然后将结果TextMessage发送到原始Message的 JMS Reply-To属性或MessageListenerAdapter上设置的默认Destination(如果已配置)的 JMS Reply-To属性中定义的Destination(如果存在)。如果未找到Destination,则会引发InvalidDestinationException(请注意,该异常不会被吞没,并且会在调用堆栈中传播)。

    3.3.5. 处理事务中的消息

    在事务中调用消息侦听器仅需要重新配置侦听器容器。

    您可以通过侦听器容器定义上的sessionTransacted标志激活本地资源事务。然后,每个消息侦听器调用都在活动的 JMS 事务中运行,并且在侦听器执行失败的情况下回退消息接收。 (通过SessionAwareMessageListener)发送响应消息是同一本地事务的一部分,但是任何其他资源操作(例如数据库访问)都是独立运行的。这通常需要在侦听器实现中进行重复消息检测,以解决数据库处理已提交但消息处理未能提交的情况。

    考虑以下 bean 定义:

    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="messageListener"/>
        <property name="sessionTransacted" value="true"/>
    </bean>
      

    要参与外部 Management 的事务,您需要配置一个事务 Management 器并使用支持外部 Management 的事务(通常为DefaultMessageListenerContainer)的侦听器容器。

    要为 XA 事务参与配置消息侦听器容器,您需要配置JtaTransactionManager(默认情况下,它委派给 Java EE 服务器的事务子系统)。请注意,底层的 JMS ConnectionFactory必须具有 XA 功能,并已向您的 JTA 事务协调器正确注册。 (检查 Java EE 服务器的 JNDI 资源配置.)这使消息接收和(例如)数据库访问成为同一事务的一部分(具有统一的提交语义,但以 XA 事务日志开销为代价)。

    以下 bean 定义创建一个事务 Management 器:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
      

    然后,我们需要将其添加到我们之前的容器配置中。容器负责其余的工作。以下示例显示了如何执行此操作:

    <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="messageListener"/>
        <property name="transactionManager" ref="transactionManager"/> (1)
    </bean>
       
  • (1) 我们的 TransactionManager。
  • 3.4. 支持 JCA 消息端点

    从 2.5 版开始,Spring 还提供了对基于 JCA 的MessageListener容器的支持。 JmsMessageEndpointManager尝试根据提供者的ResourceAdapter类名自动确定ActivationSpec类名。因此,通常可以提供 Spring 的通用JmsActivationSpecConfig,如以下示例所示:

    <bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
        <property name="resourceAdapter" ref="resourceAdapter"/>
        <property name="activationSpecConfig">
            <bean class="org.springframework.jms.listener.endpoint.JmsActivationSpecConfig">
                <property name="destinationName" value="myQueue"/>
            </bean>
        </property>
        <property name="messageListener" ref="myMessageListener"/>
    </bean>
      

    或者,您可以使用给定的ActivationSpec对象设置JmsMessageEndpointManagerActivationSpec对象也可以来自 JNDI 查找(使用<jee:jndi-lookup>)。以下示例显示了如何执行此操作:

    <bean class="org.springframework.jms.listener.endpoint.JmsMessageEndpointManager">
        <property name="resourceAdapter" ref="resourceAdapter"/>
        <property name="activationSpec">
            <bean class="org.apache.activemq.ra.ActiveMQActivationSpec">
                <property name="destination" value="myQueue"/>
                <property name="destinationType" value="javax.jms.Queue"/>
            </bean>
        </property>
        <property name="messageListener" ref="myMessageListener"/>
    </bean>
      

    使用 Spring 的ResourceAdapterFactoryBean,您可以在本地配置目标ResourceAdapter,如以下示例所示:

    <bean id="resourceAdapter" class="org.springframework.jca.support.ResourceAdapterFactoryBean">
        <property name="resourceAdapter">
            <bean class="org.apache.activemq.ra.ActiveMQResourceAdapter">
                <property name="serverUrl" value="tcp://localhost:61616"/>
            </bean>
        </property>
        <property name="workManager">
            <bean class="org.springframework.jca.work.SimpleTaskWorkManager"/>
        </property>
    </bean>
      

    指定的WorkManager也可以指向特定于环境的线程池-通常通过SimpleTaskWorkManager实例的asyncTaskExecutor属性来指向。如果您碰巧使用多个适配器,请考虑为所有ResourceAdapter实例定义一个共享线程池。

    在某些环境(例如 WebLogic 9 或更高版本)中,您可以(通过使用<jee:jndi-lookup>)从 JNDI 获取整个ResourceAdapter对象。然后,基于 Spring 的消息侦听器可以与服务器托管的ResourceAdapter进行交互,该服务器也使用服务器的内置WorkManager

    有关更多详细信息,请参见JmsMessageEndpointManagerJmsActivationSpecConfigResourceAdapterFactoryBean的 javadoc。

    Spring 还提供了与 JMS 无关的通用 JCA 消息端点 Management 器:org.springframework.jca.endpoint.GenericMessageEndpointManager。该组件允许使用任何消息侦听器类型(例如 CCI MessageListener)和任何特定于提供程序的ActivationSpec对象。请参阅 JCA 提供程序的文档以了解连接器的实际功能,并请参阅GenericMessageEndpointManager javadoc 以获取特定于 Spring 的配置详细信息。

    基于 JCA 的消息端点 Management 与 EJB 2.1 消息驱动 Bean 非常相似。它使用相同的基础资源提供者 Contract。与 EJB 2.1 MDB 一样,您也可以在 Spring 上下文中使用 JCA 提供程序支持的任何消息侦听器接口。尽管如此,Spring 仍为 JMS 提供了明确的“便利”支持,因为 JMS 是 JCA 端点 Management 协定中最常用的端点 API。

    3.5. Comments 驱动的侦听器端点

    异步接收消息的最简单方法是使用带 Comments 的侦听器端点基础结构。简而言之,它使您可以将托管 Bean 的方法公开为 JMS 侦听器端点。以下示例显示了如何使用它:

    @Component
    public class MyService {
        @JmsListener(destination = "myDestination")
        public void processOrder(String data) { ... }
      

    前面示例的想法是,只要javax.jms.Destination myDestination上有消息可用,就相应地调用processOrder方法(在这种情况下,使用 JMS 消息的内容,类似于MessageListenerAdapter提供的内容)。

    带 Comments 的终结点基础结构通过使用JmsListenerContainerFactory在幕后为每种带 Comments 的方法创建一个消息侦听器容器。此类容器未针对应用程序上下文进行注册,但可以通过使用JmsListenerEndpointRegistry bean 进行轻松定位以进行 Management。

    @JmsListener是 Java 8 上的可重复 Comments,因此您可以通过向其添加其他@JmsListener声明来将多个 JMS 目标与同一方法相关联。

    3.5.1. 启用侦听器端点 Comments

    要启用对@JmsListenerComments 的支持,可以将@EnableJms添加到@Configuration类之一,如以下示例所示:

    @Configuration
    @EnableJms
    public class AppConfig {
        @Bean
        public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
            DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
            factory.setConnectionFactory(connectionFactory());
            factory.setDestinationResolver(destinationResolver());
            factory.setSessionTransacted(true);
            factory.setConcurrency("3-10");
            return factory;
      

    默认情况下,基础结构将查找名为jmsListenerContainerFactory的 bean 作为工厂用来创建消息侦听器容器的源。在这种情况下(并忽略了 JMS 基础结构设置),您可以使用三个线程的核心轮询大小和十个线程的最大池大小来调用processOrder方法。

    您可以自定义用于每个 Comments 的侦听器容器工厂,也可以通过实现JmsListenerConfigurer接口来配置显式默认值。仅当至少一个端点在没有特定容器工厂的情况下注册时,才需要使用默认值。有关详细信息和示例,请参见实现JmsListenerConfigurer的类的 javadoc。

    如果您更喜欢XML configuration,则可以使用<jms:annotation-driven>元素,如以下示例所示:

    <jms:annotation-driven/>
    <bean id="jmsListenerContainerFactory"
            class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destinationResolver" ref="destinationResolver"/>
        <property name="sessionTransacted" value="true"/>
        <property name="concurrency" value="3-10"/>
    </bean>
      

    3.5.2. 程序化端点注册

    JmsListenerEndpoint提供 JMS 端点的模型,并负责为该模型配置容器。除了JmsListenerComments 检测到的端点外,该基础结构还允许您以编程方式配置端点。以下示例显示了如何执行此操作:

    @Configuration
    @EnableJms
    public class AppConfig implements JmsListenerConfigurer {
        @Override
        public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
            SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
            endpoint.setId("myJmsEndpoint");
            endpoint.setDestination("anotherQueue");
            endpoint.setMessageListener(message -> {
                // processing
            registrar.registerEndpoint(endpoint);
      

    在前面的示例中,我们使用了SimpleJmsListenerEndpoint,它提供了实际的MessageListener进行调用。但是,您也可以构建自己的端点变体来描述自定义调用机制。

    请注意,您可以完全跳过@JmsListener的使用,而只能通过JmsListenerConfigurer以编程方式注册您的端点。

    3.5.3. 带 Comments 的端点方法签名

    到目前为止,我们已经在端点中注入了一个简单的String,但实际上它可以具有非常灵活的方法签名。在以下示例中,我们将其重写为使用自定义 Headers 注入Order

    @Component
    public class MyService {
        @JmsListener(destination = "myDestination")
        public void processOrder(Order order, @Header("order_type") String orderType) {
      

    您可以在 JMS 侦听器端点中注入的主要元素如下:

  • 原始的javax.jms.Message或其任何子类(前提是它与传入的消息类型匹配)。

  • javax.jms.Session用于对本机 JMS API 的可选访问(例如,用于发送自定义回复)。

  • org.springframework.messaging.Message代表传入的 JMS 消息。请注意,此消息同时包含自定义 Headers 和标准 Headers(由JmsHeaders定义)。

  • @Header-带 Comments 的方法参数,用于提取特定的 Headers 值,包括标准的 JMSHeaders。

  • 一个带有@HeadersComments 的参数,还必须可以将其分配给java.util.Map才能访问所有 Headers。

  • 不是受支持的类型(MessageSession)之一的非 Comments 元素被视为有效负载。您可以通过用@PayloadComments 参数来使其明确。您还可以通过添加额外的@Valid来启用验证。

  • 注入 Spring 的Message抽象的能力特别有用,它可以受益于存储在特定于传输的消息中的所有信息,而无需依赖于特定于传输的 API。以下示例显示了如何执行此操作:

    @JmsListener(destination = "myDestination")
    public void processOrder(Message<Order> order) { ... }
      

    DefaultMessageHandlerMethodFactory提供了方法参数的处理,您可以进一步对其进行自定义以支持其他方法参数。您也可以在那里自定义转换和验证支持。

    例如,如果我们想在处理Order之前确保其有效,则可以使用@ValidComments 有效负载并配置必要的验证器,如以下示例所示:

    @Configuration
    @EnableJms
    public class AppConfig implements JmsListenerConfigurer {
        @Override
        public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
            registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
        @Bean
        public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
            DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
            factory.setValidator(myValidator());
            return factory;
      

    3.5.4. 反应 Management

    MessageListenerAdapter中的现有支持已使您的方法具有非void返回类型。在这种情况下,调用的结果将封装在javax.jms.Message中,该javax.jms.Message要么在原始消息的JMSReplyTo头中指定的目标中发送,要么在侦听器上配置的默认目标中发送。现在,您可以使用消息传递抽象的@SendToComments 设置该默认目标。

    假设我们的processOrder方法现在应该返回OrderStatus,我们可以将其编写为自动发送响应,如以下示例所示:

    @JmsListener(destination = "myDestination")
    @SendTo("status")
    public OrderStatus processOrder(Order order) {
        // order processing
        return status;
        

    如果您有几种带有@JmsListenerComments 的方法,则还可以将@SendToComments 放置在类级别以共享默认的答复目标。

    如果需要以与传输无关的方式设置其他 Headers,则可以使用类似于以下方法来返回Message

    @JmsListener(destination = "myDestination")
    @SendTo("status")
    public Message<OrderStatus> processOrder(Order order) {
        // order processing
        return MessageBuilder
                .withPayload(status)
                .setHeader("code", 1234)
                .build();
      

    如果需要在运行时计算响应目标,则可以将响应封装在JmsResponse实例中,该实例还提供要在运行时使用的目标。我们可以如下重写前一个示例:

    @JmsListener(destination = "myDestination")
    public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
        // order processing
        Message<OrderStatus> response = MessageBuilder
                .withPayload(status)
                .setHeader("code", 1234)
                .build();
        return JmsResponse.forQueue(response, "status");
      

    最后,如果您需要为响应指定一些 QoS 值,例如优先级或生存时间,则可以相应地配置JmsListenerContainerFactory,如以下示例所示:

    @Configuration
    @EnableJms
    public class AppConfig {
        @Bean
        public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
            DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
            factory.setConnectionFactory(connectionFactory());
            QosSettings replyQosSettings = new QosSettings();
            replyQosSettings.setPriority(2);
            replyQosSettings.setTimeToLive(10000);
            factory.setReplyQosSettings(replyQosSettings);
            return factory;
      

    3.6. JMS 命名空间支持

    Spring 提供了一个 XML 名称空间来简化 JMS 配置。要使用 JMS 命名空间元素,您需要引用 JMS 模式,如以下示例所示:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:jms="http://www.springframework.org/schema/jms" (1)
            xsi:schemaLocation="
                http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">
        <!-- bean definitions here -->
    </beans>
       
  • (1) 引用 JMS 模式。
  • 命名空间由三个顶级元素组成:<annotation-driven/><listener-container/><jca-listener-container/><annotation-driven/>启用Comments 驱动的侦听器端点的使用。 <listener-container/><jca-listener-container/>定义共享侦听器容器配置,并且可以包含<listener/>子元素。以下示例显示了两个侦听器的基本配置:

    <jms:listener-container>
        <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
        <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
    </jms:listener-container>
      

    前面的示例等效于创建两个不同的侦听器容器 bean 定义和两个不同的MessageListenerAdapter bean 定义,如Using MessageListenerAdapter所示。除了前面示例中显示的属性之外,listener元素还可以包含多个可选属性。下表描述了所有可用属性:

    表 3. JMS<listener>元素的属性

    method 要调用的处理程序方法的名称。如果ref属性指向MessageListener或 Spring SessionAwareMessageListener,则可以省略此属性。 response-destination 向其发送响应消息的默认响应目标的名称。如果请求消息中不包含JMSReplyTo字段,则应用此方法。此目标的类型由侦听器容器的response-destination-type属性确定。请注意,这仅适用于具有返回值的侦听器方法,为此,每个结果对象都将转换为响应消息。 subscription 持久订阅的名称(如果有)。 selector 此侦听器的可选消息 selectors。 concurrency 要启动此侦听器的并发会话或使用者的数量。该值可以是表示最大数的简单数字(例如5),也可以是指示下限和上限的范围(例如3-5)。请注意,指定的最小值仅是一个提示,在运行时可能会被忽略。默认值为容器提供的值。

    <listener-container/>元素还接受几个可选属性。这允许自定义各种策略(例如taskExecutordestinationResolver)以及基本的 JMS 设置和资源引用。通过使用这些属性,您可以定义高度自定义的侦听器容器,同时仍然受益于命名空间的便利性。

    您可以通过指定要通过factory-id属性公开的 Bean 的id来自动将此类设置公开为JmsListenerContainerFactory,如以下示例所示:

    <jms:listener-container connection-factory="myConnectionFactory"
            task-executor="myTaskExecutor"
            destination-resolver="myDestinationResolver"
            transaction-manager="myTransactionManager"
            concurrency="10">
        <jms:listener destination="queue.orders" ref="orderService" method="placeOrder"/>
        <jms:listener destination="queue.confirmations" ref="confirmationLogger" method="log"/>
    </jms:listener-container>
      

    下表描述了所有可用属性。有关各个属性的更多详细信息,请参见AbstractMessageListenerContainer的类级 javadoc 及其具体子类。 Javadoc 还讨论了事务选择和消息重新交付方案。

    表 4. JMS<listener-container>元素的属性

    container-type 此侦听器容器的类型。可用选项为defaultsimpledefault102simple102(默认选项为default)。 container-class 自定义侦听器容器实现类,作为完全限定的类名。根据container-type属性,默认值为 Spring 的标准DefaultMessageListenerContainerSimpleMessageListenerContainerfactory-id 用指定的id公开此元素定义为JmsListenerContainerFactory的设置,以便可以将其与其他端点重用。 connection-factory 对 JMS ConnectionFactory bean 的引用(默认 bean 名称为connectionFactory)。 task-executor 对 JMS 侦听器调用程序的 Spring TaskExecutor的引用。 destination-resolver 对解决 JMS Destination实例的DestinationResolver策略的引用。 message-converter 对将 JMS 消息转换为侦听器方法参数的MessageConverter策略的引用。默认值为SimpleMessageConvertererror-handlerErrorHandler策略的引用,该策略用于处理MessageListener执行期间可能发生的任何未捕获的异常。 destination-type 此侦听器的 JMS 目标类型:queuetopicdurableTopicsharedTopicsharedDurableTopic。这可能会启用容器的pubSubDomainsubscriptionDurablesubscriptionShared属性。默认值为queue(将禁用这三个属性)。 response-destination-type 响应的 JMS 目标类型:queuetopic。默认值为destination-type属性的值。 client-id 此侦听器容器的 JMSClient 端 ID。使用持久订阅时必须指定它。 cache JMS 资源的缓存级别:noneconnectionsessionconsumerauto。默认情况下(auto),缓存级别实际上是consumer,除非已指定外部事务 Management 器。在这种情况下,有效的默认值为none(假设 Java EE 风格的事务 Management,其中给定的 ConnectionFactory 是 XA 感知的池)。 acknowledge 本机 JMS 确认模式:autoclientdups-oktransacted。值transacted激活本地处理的Session。或者,您可以指定transaction-manager属性,如表中稍后所述。默认值为autotransaction-manager 对外部PlatformTransactionManager的引用(通常是基于 XA 的事务协调器,例如 Spring 的JtaTransactionManager)。如果未指定,则使用本机确认(请参见acknowledge属性)。 concurrency 每个侦听器启动的并发会话或使用者的数量。它可以是表示最大数的简单数字(例如5),也可以是指示下限和上限的范围(例如3-5)。请注意,指定的最小值只是一个提示,在运行时可能会被忽略。默认值为1。如果是主题侦听器或队列 Sequences 很重要,则应将并发限制为1。考虑将其提高到一般队列。 prefetch 加载到单个会话中的最大消息数。请注意,增加此数字可能会导致并发 Consumer 饥饿。 receive-timeout 用于接听电话的超时时间(以毫秒为单位)。默认值为1000(一秒)。 -1表示没有超时。 back-off 指定用于计算两次恢复尝试间隔的BackOff实例。如果BackOffExecution实现返回BackOffExecution#STOP,则侦听器容器不会进一步尝试恢复。设置此属性时,将忽略recovery-interval值。默认值为FixedBackOff,间隔为 5000 毫秒(即五秒)。 recovery-interval 指定两次恢复尝试之间的时间间隔(以毫秒为单位)。它提供了一种方便的方法来创建具有指定间隔的FixedBackOff。有关更多恢复选项,请考虑改为指定BackOff实例。缺省值为 5000 毫秒(即 5 秒)。 phase 此容器应在其中启动和停止的生命周期阶段。值越低,此容器启动的越早,而容器停止的越晚。默认值为Integer.MAX_VALUE,这意味着容器将尽可能晚地启动,并尽快停止。

    配置具有jms模式支持的基于 JCA 的侦听器容器非常相似,如以下示例所示:

    <jms:jca-listener-container resource-adapter="myResourceAdapter"
            destination-resolver="myDestinationResolver"
            transaction-manager="myTransactionManager"
            concurrency="10">
        <jms:listener destination="queue.orders" ref="myMessageListener"/>
    </jms:jca-listener-container>
      

    下表描述了 JCA 变体的可用配置选项:

    表 5. JMS<jca-listener-container/>元素的属性

    activation-spec-factoryJmsActivationSpecFactory的引用。默认设置是自动检测 JMS 提供程序及其ActivationSpec类(请参见DefaultJmsActivationSpecFactory)。 destination-resolver 对解决 JMS DestinationsDestinationResolver策略的引用。 message-converter 对将 JMS 消息转换为侦听器方法参数的MessageConverter策略的引用。默认值为SimpleMessageConverterdestination-type 此侦听器的 JMS 目标类型:queuetopicdurableTopicsharedTopic。或sharedDurableTopic。这可能会启用容器的pubSubDomainsubscriptionDurablesubscriptionShared属性。默认值为queue(将禁用这三个属性)。 response-destination-type 响应的 JMS 目标类型:queuetopic。默认值为destination-type属性的值。 client-id 此侦听器容器的 JMSClient 端 ID。使用持久订阅时需要指定它。 acknowledge 本机 JMS 确认模式:autoclientdups-oktransacted。值transacted激活本地处理的Session。或者,您可以指定后面描述的transaction-manager属性。默认值为autotransaction-manager 对 Spring JtaTransactionManagerjavax.transaction.TransactionManager的引用,用于为每个传入消息启动 XA 事务。如果未指定,则使用本机确认(请参见acknowledge属性)。 concurrency 每个侦听器启动的并发会话或使用者的数量。它可以是表示最大数的简单数字(例如5),也可以是指示下限和上限的范围(例如3-5)。请注意,指定的最小值只是一个提示,通常在运行时使用 JCA 侦听器容器时将被忽略。预设值为 1. prefetch 加载到单个会话中的最大消息数。请注意,增加此数字可能会导致并发 Consumer 饥饿。

    Spring 中的 JMX(JavaManagement 扩展)支持提供的功能使您可以轻松,透明地将 Spring 应用程序集成到 JMX 基础结构中。

    本章不是 JMX 的介绍。它没有试图解释为什么您可能要使用 JMX。如果您不熟悉 JMX,请参阅本章末尾的Further Resources

    具体来说,Spring 的 JMX 支持提供了四个核心功能:

  • 将任何 Spring bean 自动注册为 JMX MBean。

  • 一种用于控制 beanManagement 界面的灵活机制。

  • 通过远程 JSR-160 连接器以声明方式公开 MBean。

  • 本地和远程 MBean 资源的简单代理。

  • 这些功能旨在在不将应用程序组件耦合到 Spring 或 JMX 接口和类的情况下起作用。实际上,在大多数情况下,您的应用程序类无需了解 Spring 或 JMX 即可利用 Spring JMX 功能。

    4.1. 将您的 Bean 导出到 JMX

    Spring 的 JMX 框架的核心类是MBeanExporter。此类负责获取您的 Spring bean 并向 JMX MBeanServer注册它们。例如,考虑以下类:

    package org.springframework.jmx;
    public class JmxTestBean implements IJmxTestBean {
        private String name;
        private int age;
        private boolean isSuperman;
        public int getAge() {
            return age;
        public void setAge(int age) {
            this.age = age;
        public void setName(String name) {
            this.name = name;
        public String getName() {
            return name;
        public int add(int x, int y) {
            return x + y;
        public void dontExposeMe() {
            throw new RuntimeException();
      

    要将此 Bean 的属性和方法公开为 MBean 的属性和操作,可以在配置文件中配置MBeanExporter类的实例并传入 Bean,如以下示例所示:

    <beans>
        <!-- this bean must not be lazily initialized if the exporting is to happen -->
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
            <property name="beans">
                    <entry key="bean:name=testBean1" value-ref="testBean"/>
            </property>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    前面的配置片段中相关的 bean 定义是exporter bean。 beans属性准确地告诉MBeanExporter您必须将哪个 bean 导出到 JMX MBeanServer。在默认配置中,beans Map中每个条目的键用作相应条目值所引用的 Bean 的ObjectName。您可以按照控制您的 Bean 的 ObjectName 实例中所述更改此行为。

    使用此配置,testBean bean 在ObjectName bean:name=testBean1下作为 MBean 公开。默认情况下,bean 的所有public属性都作为属性公开,所有public方法(从Object类继承的方法除外)都作为操作公开。

    MBeanExporterLifecycle bean(请参阅启动和关机回调)。默认情况下,MBean 在应用程序生命周期中尽可能晚地导出。您可以通过设置autoStartup标志来配置导出发生的phase或禁用自动注册。

    4.1.1. 创建一个 MBeanServer

    preceding section中显示的配置假定该应用程序正在一个(并且只有一个)MBeanServer已运行的环境中运行。在这种情况下,Spring 尝试找到正在运行的MBeanServer并将您的 bean 注册到该服务器(如果有)。当您的应用程序在具有自己的MBeanServer的容器(例如 Tomcat 或 IBM WebSphere)中运行时,此行为很有用。

    但是,此方法在独立环境中或在不提供MBeanServer的容器中运行时无用。为了解决这个问题,您可以通过在配置中添加org.springframework.jmx.support.MBeanServerFactoryBean类的实例来声明性地创建MBeanServer实例。您还可以通过将MBeanExporter实例的server属性的值设置为MBeanServerFactoryBean返回的MBeanServer值来确保使用特定的MBeanServer,如以下示例所示:

    <beans>
        <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
        this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
        this means that it must not be marked as lazily initialized
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="bean:name=testBean1" value-ref="testBean"/>
            </property>
            <property name="server" ref="mbeanServer"/>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    在前面的示例中,MBeanServer的实例由MBeanServerFactoryBean创建,并通过server属性提供给MBeanExporter。当您提供自己的MBeanServer实例时,MBeanExporter不会尝试查找正在运行的MBeanServer并使用提供的MBeanServer实例。为了使其正常工作,您必须在 Classpath 上具有 JMX 实现。

    4.1.2. 重用现有的 MBeanServer

    如果未指定服务器,则MBeanExporter尝试自动检测正在运行的MBeanServer。在大多数仅使用一个MBeanServer实例的环境中,这是可行的。但是,当存在多个实例时,导出器可能选择了错误的服务器。在这种情况下,应使用MBeanServer agentId指示要使用的实例,如以下示例所示:

    <beans>
        <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
            <!-- indicate to first look for a server -->
            <property name="locateExistingServerIfPossible" value="true"/>
            <!-- search for the MBeanServer instance with the given agentId -->
            <property name="agentId" value="MBeanServer_instance_agentId>"/>
        </bean>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="server" ref="mbeanServer"/>
        </bean>
    </beans>
      

    对于平台或现有MBeanServer具有通过查找方法检索到的动态(或未知)agentId的情况,应使用factory-method,如以下示例所示:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="server">
                <!-- Custom MBeanServerLocator -->
                <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/>
            </property>
        </bean>
        <!-- other beans here -->
    </beans>
      

    4.1.3. 延迟初始化的 MBean

    如果为 Bean 配置了MBeanExporter,并且也为延迟初始化配置了该MBeanExporter,则MBeanExporter不会破坏该协定,并且避免实例化该 Bean。相反,它向MBeanServer注册了一个代理,并推迟从容器获取 Bean,直到对该代理进行第一次调用为止。

    4.1.4. 自动注册 MBean

    通过MBeanExporter导出并且已经是有效 MBean 的所有 bean 都将直接在MBeanServer上注册,而无需 Spring 的进一步干预。通过将autodetect属性设置为true,可以使MBeanExporter自动检测 MBean,如以下示例所示:

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="autodetect" value="true"/>
    </bean>
    <bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>
      

    在前面的示例中,名为spring:mbean=true的 bean 已经是有效的 JMX MBean,并由 Spring 自动注册。缺省情况下,自动检测到 JMX 注册的 bean 的 Bean 名称用作ObjectName。您可以覆盖此行为,如控制您的 Bean 的 ObjectName 实例中所述。

    4.1.5. 控制注册行为

    考虑以下情形:Spring MBeanExporter尝试通过使用ObjectName bean:name=testBean1MBeanServer注册MBean。如果MBean实例已经在同一ObjectName下注册,则默认行为是失败(并抛出InstanceAlreadyExistsException)。

    您可以精确控制将MBean注册到MBeanServer时发生的情况。当注册过程发现MBean已经在同一ObjectName下注册时,Spring 的 JMX 支持允许三种不同的注册行为来控制注册行为。下表总结了这些注册行为:

    表 6.注册行为

    FAIL_ON_EXISTING 这是默认的注册行为。如果MBean实例已经在同一ObjectName下注册,则不注册正在注册的MBean,并抛出InstanceAlreadyExistsException。现有的MBean不受影响。 IGNORE_EXISTING 如果MBean实例已经在同一ObjectName下注册,则正在注册的MBean不会被注册。现有的MBean不受影响,并且不会抛出Exception。这在多个应用程序要在共享MBeanServer中共享一个公共MBean的设置中很有用。 REPLACE_EXISTING 如果MBean实例已经在同一ObjectName下注册,那么先前已注册的现有MBean将被取消注册,而新的MBean会在其位置注册(新的MBean有效地替换先前的实例)。

    上表中的值定义为RegistrationPolicy类上的枚举。如果要更改默认注册行为,则需要将MBeanExporter定义上的registrationPolicy属性的值设置为这些值之一。

    下面的示例显示如何从默认注册行为更改为REPLACE_EXISTING行为:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="bean:name=testBean1" value-ref="testBean"/>
            </property>
            <property name="registrationPolicy" value="REPLACE_EXISTING"/>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    4.2. 控制 Bean 的 Management 接口

    preceding section的示例中,您几乎无法控制 bean 的 Management 界面。每个导出 bean 的所有public属性和方法分别作为 JMX 属性和操作公开。为了对已导出的 bean 的哪些属性和方法实际上作为 JMX 属性和操作公开而进行更细粒度的控制,Spring JMX 提供了一种全面且可扩展的机制来控制 bean 的 Management 接口。

    4.2.1. 使用 MBeanInfoAssembler 接口

    在幕后,MBeanExporter代表org.springframework.jmx.export.assembler.MBeanInfoAssembler接口的实现,该接口负责定义每个公开的 bean 的 Management 接口。默认实现org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler定义了一个 Management 接口,该接口公开了所有公共属性和方法(如您在前面几节的示例中所看到的)。 Spring 提供了MBeanInfoAssembler接口的两个附加实现,使您可以使用源级元数据或任何任意接口来控制生成的 Management 接口。

    4.2.2. 使用源级元数据:JavaComments

    通过使用MetadataMBeanInfoAssembler,您可以通过使用源级元数据来定义 bean 的 Management 接口。元数据的读取由org.springframework.jmx.export.metadata.JmxAttributeSource接口封装。 Spring JMX 提供了一个使用 JavaComments 的默认实现,即org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource。您必须为MetadataMBeanInfoAssembler配置JmxAttributeSource接口的实现实例,才能使其正常运行(没有默认值)。

    要标记要导出到 JMX 的 bean,应使用ManagedResourceComments 对 bean 类进行 Comments。您必须使用_注解标记要公开的每个方法,并使用ManagedAttribute注解标记希望公开的每个属性。标记属性时,可以省略 getter 或 setter 的 Comments,以分别创建只写或只读属性。

    带有ManagedResourceComments 的 Bean 必须是公共的,公开操作或属性的方法也必须是公共的。

    以下示例显示了我们在创建一个 MBeanServer中使用的JmxTestBean类的带 Comments 版本:

    package org.springframework.jmx;
    import org.springframework.jmx.export.annotation.ManagedResource;
    import org.springframework.jmx.export.annotation.ManagedOperation;
    import org.springframework.jmx.export.annotation.ManagedAttribute;
    @ManagedResource(
            objectName="bean:name=testBean4",
            description="My Managed Bean",
            log=true,
            logFile="jmx.log",
            currencyTimeLimit=15,
            persistPolicy="OnUpdate",
            persistPeriod=200,
            persistLocation="foo",
            persistName="bar")
    public class AnnotationTestBean implements IJmxTestBean {
        private String name;
        private int age;
        @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15)
        public int getAge() {
            return age;
        public void setAge(int age) {
            this.age = age;
        @ManagedAttribute(description="The Name Attribute",
                currencyTimeLimit=20,
                defaultValue="bar",
                persistPolicy="OnUpdate")
        public void setName(String name) {
            this.name = name;
        @ManagedAttribute(defaultValue="foo", persistPeriod=300)
        public String getName() {
            return name;
        @ManagedOperation(description="Add two numbers")
        @ManagedOperationParameters({
            @ManagedOperationParameter(name = "x", description = "The first number"),
            @ManagedOperationParameter(name = "y", description = "The second number")})
        public int add(int x, int y) {
            return x + y;
        public void dontExposeMe() {
            throw new RuntimeException();
      

    在前面的示例中,您可以看到JmxTestBean类标记有ManagedResourceComments,并且此ManagedResourceComments 配置了一组属性。这些属性可用于配置MBeanExporter生成的 MBean 的各个方面,稍后将在源级元数据类型中进行详细说明。

    agename属性都带有ManagedAttributeComments,但是,在age属性的情况下,仅标记了吸气剂。这导致这两个属性都作为属性包含在 Management 界面中,但是age属性是只读的。

    最后,add(int, int)方法带有ManagedOperation属性标记,而dontExposeMe()方法则没有。当您使用MetadataMBeanInfoAssembler时,这将导致 Management 界面仅包含一个操作(add(int, int))。

    以下配置显示了如何配置MBeanExporter以使用MetadataMBeanInfoAssembler

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="assembler" ref="assembler"/>
            <property name="namingStrategy" ref="namingStrategy"/>
            <property name="autodetect" value="true"/>
        </bean>
        <bean id="jmxAttributeSource"
                class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
        <!-- will create management interface using annotation metadata -->
        <bean id="assembler"
                class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
            <property name="attributeSource" ref="jmxAttributeSource"/>
        </bean>
        <!-- will pick up the ObjectName from the annotation -->
        <bean id="namingStrategy"
                class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
            <property name="attributeSource" ref="jmxAttributeSource"/>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.AnnotationTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    在前面的示例中,已为MetadataMBeanInfoAssembler bean 配置了AnnotationJmxAttributeSource类的实例,并通过汇编程序属性将其传递给MBeanExporter。这是为 Spring 公开的 MBean 利用元数据驱动的 Management 接口所需要的全部。

    4.2.3. 源级元数据类型

    下表描述了可在 Spring JMX 中使用的源级别元数据类型:

    表 7.源级元数据类型

    description 设置资源,属性或操作的友好描述。 ManagedResourceManagedAttributeManagedOperationManagedOperationParameter currencyTimeLimit 设置currencyTimeLimitDescriptors 字段的值。 ManagedResourceManagedAttribute defaultValue 设置defaultValueDescriptors 字段的值。 ManagedAttribute 设置logDescriptors 字段的值。 ManagedResource logFile 设置logFileDescriptors 字段的值。 ManagedResource persistPolicy 设置persistPolicyDescriptors 字段的值。 ManagedResource persistPeriod 设置persistPeriodDescriptors 字段的值。 ManagedResource persistLocation 设置persistLocationDescriptors 字段的值。 ManagedResource persistName 设置persistNameDescriptors 字段的值。 ManagedResource 设置操作参数的显示名称。 ManagedOperationParameter index 设置操作参数的索引。 ManagedOperationParameter

    4.2.4. 使用 AutodetectCapableMBeanInfoAssembler 接口

    为了进一步简化配置,Spring 包含了AutodetectCapableMBeanInfoAssembler接口,该接口扩展了MBeanInfoAssembler接口以添加对自动检测 MBean 资源的支持。如果用AutodetectCapableMBeanInfoAssembler的实例配置MBeanExporter,则可以在包含 Bean 的情况下“投票”,以暴露给 JMX。

    AutodetectCapableMBeanInfo接口的唯一实现是MetadataMBeanInfoAssembler,该投票表决以包括所有带有ManagedResource属性标记的 bean。在这种情况下,默认方法是将 Bean 名称用作ObjectName,这将导致与以下内容类似的配置:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <!-- notice how no 'beans' are explicitly configured here -->
            <property name="autodetect" value="true"/>
            <property name="assembler" ref="assembler"/>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
        <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler">
            <property name="attributeSource">
                <bean class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
            </property>
        </bean>
    </beans>
      

    请注意,在上述配置中,没有将任何 bean 传递给MBeanExporter。但是,JmxTestBean仍被注册,因为它已被标记为ManagedResource属性,并且MetadataMBeanInfoAssembler检测到该问题并对其进行投票以将其包括在内。这种方法的唯一问题是JmxTestBean的名称现在具有商业意义。您可以通过更改控制您的 Bean 的 ObjectName 实例中定义的ObjectName创建的默认行为来解决此问题。

    4.2.5. 使用 Java 接口定义 Management 接口

    除了MetadataMBeanInfoAssembler之外,Spring 还包括InterfaceBasedMBeanInfoAssembler,它使您可以基于一组接口中定义的一组方法来约束公开的方法和属性。

    尽管公开 MBean 的标准机制是使用接口和简单的命名方案,但是InterfaceBasedMBeanInfoAssembler扩展了此功能,它消除了对命名约定的需要,使您可以使用多个接口,并且不再需要 bean 来实现 MBean 接口。

    考虑以下接口,该接口用于为我们先前显示的JmxTestBean类定义 Management 接口:

    public interface IJmxTestBean {
        public int add(int x, int y);
        public long myOperation();
        public int getAge();
        public void setAge(int age);
        public void setName(String name);
        public String getName();
      

    该接口定义在 JMX MBean 上作为操作和属性公开的方法和属性。以下代码显示了如何配置 Spring JMX 以使用此接口作为 Management 接口的定义:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="bean:name=testBean5" value-ref="testBean"/>
            </property>
            <property name="assembler">
                <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
                    <property name="managedInterfaces">
                        <value>org.springframework.jmx.IJmxTestBean</value>
                    </property>
                </bean>
            </property>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    在前面的示例中,在为任何 bean 构造 Management 接口时,将InterfaceBasedMBeanInfoAssembler配置为使用IJmxTestBean接口。理解由InterfaceBasedMBeanInfoAssembler处理的 bean 不需要实现用于生成 JMXManagement 接口的接口,这一点很重要。

    在上述情况下,IJmxTestBean接口用于为所有 bean 构造所有 Management 接口。在许多情况下,这不是理想的行为,并且您可能希望对不同的 bean 使用不同的接口。在这种情况下,您可以通过interfaceMappings属性传递InterfaceBasedMBeanInfoAssembler Properties实例,其中每个条目的键是 Bean 名称,每个条目的值是一个用逗号分隔的接口名称列表,用于该 Bean。

    如果没有通过managedInterfacesinterfaceMappings属性指定 Management 接口,则InterfaceBasedMBeanInfoAssembler会在 Bean 上进行反映,并使用该 Bean 所实现的所有接口来创建 Management 接口。

    4.2.6. 使用 MethodNameBasedMBeanInfoAssembler

    MethodNameBasedMBeanInfoAssembler使您可以指定作为属性和操作公开给 JMX 的方法名称的列表。以下代码显示了示例配置:

    <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
        <property name="beans">
                <entry key="bean:name=testBean5" value-ref="testBean"/>
        </property>
        <property name="assembler">
            <bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler">
                <property name="managedMethods">
                    <value>add,myOperation,getName,setName,getAge</value>
                </property>
            </bean>
        </property>
    </bean>
      

    在前面的示例中,您可以看到addmyOperation方法公开为 JMX 操作,而getName()setName(String)getAge()公开为 JMX 属性的适当一半。在前面的代码中,方法 Map 适用于 JMX 公开的 bean。要控制每个 bean 的方法公开,可以使用MethodNameMBeanInfoAssemblermethodMappings属性将 bean 名称 Map 到方法名称列表。

    4.3. 控制您的 Bean 的 ObjectName 实例

    在幕后,MBeanExporter委派ObjectNamingStrategy的实现,以为其注册的每个 bean 获得ObjectName实例。默认情况下,默认实现KeyNamingStrategy使用beans Map的键作为ObjectName。此外,KeyNamingStrategy可以将beans Map的键 Map 到Properties文件(或多个文件)中的条目以解析ObjectName。除了KeyNamingStrategy之外,Spring 还提供了两个附加的ObjectNamingStrategy实现:IdentityNamingStrategy(基于 Bean 的 JVM 身份构建ObjectName)和MetadataNamingStrategy(使用源级元数据获取ObjectName)。

    4.3.1. 从属性读取 ObjectName 实例

    您可以配置自己的KeyNamingStrategy实例,并将其配置为从Properties实例读取ObjectName实例,而不必使用 Bean 键。 KeyNamingStrategy尝试使用与 bean 密钥相对应的密钥在Properties中定位条目。如果未找到任何条目,或者Properties实例是null,则使用 bean 密钥本身。

    以下代码显示了KeyNamingStrategy的示例配置:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="testBean" value-ref="testBean"/>
            </property>
            <property name="namingStrategy" ref="namingStrategy"/>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
        <bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy">
            <property name="mappings">
                <props>
                    <prop key="testBean">bean:name=testBean1</prop>
                </props>
            </property>
            <property name="mappingLocations">
                <value>names1.properties,names2.properties</value>
            </property>
        </bean>
    </beans>
      

    前面的示例将KeyNamingStrategy实例与Properties实例配置在一起,该实例是从 mapping 属性定义的Properties实例和位于 mappings 属性定义的路径中的属性文件合并而成的。在此配置中,testBean bean 被赋予bean:name=testBean1ObjectName,因为这是Properties实例中的条目,该条目具有与 bean 密钥相对应的密钥。

    如果在Properties实例中找不到任何条目,则将 bean 键名用作ObjectName

    4.3.2. 使用元数据命名策略

    MetadataNamingStrategy使用每个 bean 上ManagedResource属性的objectName属性来创建ObjectName。以下代码显示了MetadataNamingStrategy的配置:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="testBean" value-ref="testBean"/>
            </property>
            <property name="namingStrategy" ref="namingStrategy"/>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
        <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
            <property name="attributeSource" ref="attributeSource"/>
        </bean>
        <bean id="attributeSource"
                class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>
    </beans>
      

    如果没有为ManagedResource属性提供objectName,那么将使用以下格式创建ObjectName:* [完全合格的软件包名称]:type = [short-classname],name = [bean-name] *。例如,为以下 bean 生成的ObjectName将是com.example:type=MyClass,name=myBean

    <bean id="myBean" class="com.example.MyClass"/>
      

    4.3.3. 配置基于 Comments 的 MBean 导出

    如果您更喜欢使用基于 Comments 的方法定义 Management 界面,则可以使用MBeanExporter的便利子类:AnnotationMBeanExporter。在定义该子类的实例时,您不再需要namingStrategyassemblerattributeSource配置,因为它始终使用基于 JavaComments 的标准元数据(也始终启用自动检测)。实际上,@EnableMBeanExport @ConfigurationComments 支持更简单的语法,而不是定义MBeanExporter bean,如以下示例所示:

    @Configuration
    @EnableMBeanExport
    public class AppConfig {
      

    如果您更喜欢基于 XML 的配置,则<context:mbean-export/>元素具有相同的目的,并在以下列表中显示:

    <context:mbean-export/>
      

    如有必要,可以提供对特定 MBean server的引用,并且defaultDomain属性(AnnotationMBeanExporter的属性)接受生成的 MBean ObjectName域的备用值。如上一示例所示,它用于代替上一章节MetadataNamingStrategy中描述的标准软件包名称:

    @EnableMBeanExport(server="myMBeanServer", defaultDomain="myDomain")
    @Configuration
    ContextConfiguration {
      

    以下示例显示了与前面的基于 Comments 的示例等效的 XML:

    <context:mbean-export server="myMBeanServer" default-domain="myDomain"/>
       

    Warning

    请勿将基于接口的 AOP 代理与 bean 类中的 JMXComments 的自动检测结合使用。基于接口的代理“隐藏”目标类,它也隐藏了 JMXManagement 的资源 Comments。因此,在这种情况下,您应该使用目标类代理(通过在<aop:config/><tx:annotation-driven/>等上设置'proxy-target-class'标志)。否则,启动时可能会静默忽略您的 JMX bean。

    4.4. 使用 JSR-160 连接器

    对于远程访问,Spring JMX 模块在org.springframework.jmx.support包中提供了两个FactoryBean实现,用于创建服务器端和 Client 端连接器。

    4.4.1. 服务器端连接器

    要使 Spring JMX 创建,启动和公开 JSR-160 JMXConnectorServer,可以使用以下配置:

    <bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
      

    默认情况下,ConnectorServerFactoryBean创建绑定到service:jmx:jmxmp://localhost:9875JMXConnectorServer。因此,serverConnector bean 通过 localhost 上的 JMXMP 协议(端口 9875)向 Client 端公开本地MBeanServer。请注意,JSR 160 规范将 JMXMP 协议标记为可选。当前,主要的开源 JMX 实现 MX4J 和 JDK 随附的实现不支持 JMXMP。

    要指定另一个 URL 并向MBeanServer注册JMXConnectorServer本身,可以分别使用serviceUrlObjectName属性,如以下示例所示:

    <bean id="serverConnector"
            class="org.springframework.jmx.support.ConnectorServerFactoryBean">
        <property name="objectName" value="connector:name=rmi"/>
        <property name="serviceUrl"
                value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/>
    </bean>
      

    如果设置了ObjectName属性,Spring 会在ObjectName下自动将连接器注册到MBeanServer。以下示例显示了创建JMXConnector时可以传递给ConnectorServerFactoryBean的完整参数集:

    <bean id="serverConnector"
            class="org.springframework.jmx.support.ConnectorServerFactoryBean">
        <property name="objectName" value="connector:name=iiop"/>
        <property name="serviceUrl"
            value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/>
        <property name="threaded" value="true"/>
        <property name="daemon" value="true"/>
        <property name="environment">
                <entry key="someKey" value="someValue"/>
        </property>
    </bean>
      

    请注意,在使用基于 RMI 的连接器时,需要启动查找服务(tnameservrmiregistry)才能完成名称注册。如果您使用 Spring 通过 RMI 为您导出远程服务,则 Spring 已经构造了一个 RMI 注册表。如果没有,您可以使用以下配置片段轻松启动注册表:

    <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
        <property name="port" value="1099"/>
    </bean>
      

    4.4.2. Client 端连接器

    要为启用了远程 JSR-160 的MBeanServer创建MBeanServerConnection,可以使用MBeanServerConnectionFactoryBean,如以下示例所示:

    <bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
        <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
    </bean>
      

    4.4.3. 通过 Hessian 或 SOAP 的 JMX

    JSR-160 允许扩展 Client 端与服务器之间进行通信的方式。前面各节中显示的示例使用 JSR-160 规范(IIOP 和 JRMP)和(可选)JMXMP 所需的基于 RMI 的强制实现。通过使用其他提供程序或 JMX 实现(例如MX4J),可以通过简单的 HTTP 或 SSL 以及其他协议利用 SOAP 或 Hessian 等协议,如以下示例所示:

    <bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean">
        <property name="objectName" value="connector:name=burlap"/>
        <property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/>
    </bean>
      

    在前面的示例中,我们使用了 MX4J 3.0.0. 有关更多信息,请参见官方 MX4J 文档。

    4.5. 通过代理访问 MBean

    Spring JMX 使您可以创建代理,以将调用重新路由到在本地或远程MBeanServer中注册的 MBean。这些代理为您提供了一个标准的 Java 接口,您可以通过它与 MBean 进行交互。以下代码显示了如何为在本地MBeanServer中运行的 MBean 配置代理:

    <bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
        <property name="objectName" value="bean:name=testBean"/>
        <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
    </bean>
      

    在前面的示例中,您可以看到为在bean:name=testBeanObjectName下注册的 MBean 创建了代理。代理实现的接口集由proxyInterfaces属性控制,将这些接口上的方法和属性 Map 到 MBean 上的操作和属性的规则与InterfaceBasedMBeanInfoAssembler使用的规则相同。

    MBeanProxyFactoryBean可以创建可通过MBeanServerConnection访问的任何 MBean 的代理。默认情况下,已找到并使用了本地MBeanServer,但是您可以覆盖它并提供一个MBeanServerConnection指向远程MBeanServer,以适应指向远程 MBean 的代理:

    <bean id="clientConnector"
            class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
        <property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/>
    </bean>
    <bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
        <property name="objectName" value="bean:name=testBean"/>
        <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/>
        <property name="server" ref="clientConnector"/>
    </bean>
      

    在前面的示例中,我们创建一个MBeanServerConnection,该MBeanServerConnection指向使用MBeanServerConnectionFactoryBean的远程计算机。然后,此MBeanServerConnection通过server属性传递到MBeanProxyFactoryBean。创建的代理通过此MBeanServerConnection将所有调用转发到MBeanServer

    4.6. Notifications

    Spring 的 JMX 产品包括对 JMX 通知的全面支持。

    4.6.1. 注册侦听器以接收通知

    Spring 的 JMX 支持使您可以轻松地将任意数量的NotificationListeners注册到任意数量的 MBean(这包括 Spring 的MBeanExporter导出的 MBean 和通过其他机制注册的 MBean)。例如,考虑一种情况,即每次目标 MBean 的属性发生更改时,都希望(通过Notification)被告知。以下示例将通知写入控制台:

    package com.example;
    import javax.management.AttributeChangeNotification;
    import javax.management.Notification;
    import javax.management.NotificationFilter;
    import javax.management.NotificationListener;
    public class ConsoleLoggingNotificationListener
            implements NotificationListener, NotificationFilter {
        public void handleNotification(Notification notification, Object handback) {
            System.out.println(notification);
            System.out.println(handback);
        public boolean isNotificationEnabled(Notification notification) {
            return AttributeChangeNotification.class.isAssignableFrom(notification.getClass());
      

    以下示例将ConsoleLoggingNotificationListener(在前面的示例中定义)添加到notificationListenerMappings

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="bean:name=testBean1" value-ref="testBean"/>
            </property>
            <property name="notificationListenerMappings">
                    <entry key="bean:name=testBean1">
                        <bean class="com.example.ConsoleLoggingNotificationListener"/>
                    </entry>
            </property>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    使用先前的配置,每次从目标 MBean(bean:name=testBean1)BroadcastJMX Notification时,都会通知通过notificationListenerMappings属性注册为侦听器的ConsoleLoggingNotificationListener bean。然后ConsoleLoggingNotificationListener bean 可以响应Notification采取其认为适当的任何操作。

    您还可以使用纯 bean 名称作为导出的 bean 与侦听器之间的链接,如以下示例所示:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="bean:name=testBean1" value-ref="testBean"/>
            </property>
            <property name="notificationListenerMappings">
                    <entry key="testBean">
                        <bean class="com.example.ConsoleLoggingNotificationListener"/>
                    </entry>
            </property>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    如果要为封闭的MBeanExporter导出的所有 bean 注册一个NotificationListener实例,则可以使用特殊的通配符(*)作为notificationListenerMappings属性 Map 中条目的键,如以下示例所示:

    <property name="notificationListenerMappings">
            <entry key="*">
                <bean class="com.example.ConsoleLoggingNotificationListener"/>
            </entry>
    </property>
      

    如果需要进行相反操作(即,针对 MBean 注册多个不同的侦听器),则必须使用notificationListeners list 属性(而不是notificationListenerMappings属性)。这次,我们配置NotificationListenerBean实例,而不是为单个 MBean 配置NotificationListenerNotificationListenerBeanNotificationListener和要注册的ObjectName(或ObjectNames)封装在MBeanServer中。 NotificationListenerBean还封装了许多其他属性,例如NotificationFilter和可以在高级 JMX 通知场景中使用的任意 handback 对象。

    使用NotificationListenerBean实例时的配置与先前介绍的配置没有很大不同,如以下示例所示:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="bean:name=testBean1" value-ref="testBean"/>
            </property>
            <property name="notificationListeners">
                    <bean class="org.springframework.jmx.export.NotificationListenerBean">
                        <constructor-arg>
                            <bean class="com.example.ConsoleLoggingNotificationListener"/>
                        </constructor-arg>
                        <property name="mappedObjectNames">
                                <value>bean:name=testBean1</value>
                            </list>
                        </property>
                    </bean>
                </list>
            </property>
        </bean>
        <bean id="testBean" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
    </beans>
      

    前面的示例等效于第一个通知示例。那么,假设我们想在每次引发Notification时得到一个递归对象,并且还希望通过提供NotificationFilter来过滤掉无关的Notifications。以下示例实现了这些目标:

    <beans>
        <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
            <property name="beans">
                    <entry key="bean:name=testBean1" value-ref="testBean1"/>
                    <entry key="bean:name=testBean2" value-ref="testBean2"/>
            </property>
            <property name="notificationListeners">
                    <bean class="org.springframework.jmx.export.NotificationListenerBean">
                        <constructor-arg ref="customerNotificationListener"/>
                        <property name="mappedObjectNames">
                                <!-- handles notifications from two distinct MBeans -->
                                <value>bean:name=testBean1</value>
                                <value>bean:name=testBean2</value>
                            </list>
                        </property>
                        <property name="handback">
                            <bean class="java.lang.String">
                                <constructor-arg value="This could be anything..."/>
                            </bean>
                        </property>
                        <property name="notificationFilter" ref="customerNotificationListener"/>
                    </bean>
                </list>
            </property>
        </bean>
        <!-- implements both the NotificationListener and NotificationFilter interfaces -->
        <bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/>
        <bean id="testBean1" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="TEST"/>
            <property name="age" value="100"/>
        </bean>
        <bean id="testBean2" class="org.springframework.jmx.JmxTestBean">
            <property name="name" value="ANOTHER TEST"/>
            <property name="age" value="200"/>
        </bean>
    </beans>
      

    (有关移交对象是什么,实际上NotificationFilter是什么的完整讨论,请参阅 JMX 规范(1.2)中名为“ JMX 通知模型”的部分。)

    4.6.2. 发布通知

    Spring 不仅提供注册支持以接收Notifications,而且还提供发布Notifications的支持。

    这部分实际上仅与已通过MBeanExporter公开为 MBean 的 Spring 托管 Bean 有关。任何现有的用户定义的 MBean 都应使用标准的 JMX API 进行通知发布。

    Spring 的 JMX 通知发布支持中的关键接口是NotificationPublisher接口(在org.springframework.jmx.export.notification包中定义)。任何要通过MBeanExporter实例作为 MBean 导出的 bean 都可以实现相关的NotificationPublisherAware接口来访问NotificationPublisher实例。 NotificationPublisherAware接口通过一个简单的 setter 方法将NotificationPublisher的实例提供给实现 Bean,然后该 bean 可以使用它来发布Notifications

    NotificationPublisher接口的 javadoc 中所述,通过NotificationPublisher机制发布事件的托管 bean 不负责通知侦听器的状态 Management。 Spring 的 JMX 支持负责处理所有 JMX 基础结构问题。作为应用程序开发人员,您需要做的就是实现NotificationPublisherAware接口并使用提供的NotificationPublisher实例开始发布事件。注意,NotificationPublisher是在已将托管 bean 注册到MBeanServer之后设置的。

    使用NotificationPublisher实例非常简单。您创建一个 JMX Notification实例(或适当的Notification子类的实例),使用与要发布的事件相关的数据填充通知,并在NotificationPublisher实例上调用sendNotification(Notification)并传入Notification

    在以下示例中,每次调用add(int, int)操作时,JmxTestBean的导出实例都会发布NotificationEvent

    package org.springframework.jmx;
    import org.springframework.jmx.export.notification.NotificationPublisherAware;
    import org.springframework.jmx.export.notification.NotificationPublisher;
    import javax.management.Notification;
    public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {
        private String name;
        private int age;
        private boolean isSuperman;
        private NotificationPublisher publisher;
        // other getters and setters omitted for clarity
        public int add(int x, int y) {
            int answer = x + y;
            this.publisher.sendNotification(new Notification("add", this, 0));
            return answer;
        public void dontExposeMe() {
            throw new RuntimeException();
        public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
            this.publisher = notificationPublisher;
      

    NotificationPublisher界面和使一切正常运行的机制是 Spring 的 JMX 支持的更好的功能之一。但是,它确实带有将类耦合到 Spring 和 JMX 的代价。和往常一样,这里的建议要务实。如果您需要NotificationPublisher提供的功能,并且可以接受到 Spring 和 JMX 的耦合,则可以这样做。

    4.7. 更多资源

    本节包含有关 JMX 的更多资源的链接:

  • 甲骨文的JMX homepage

  • JMX specification(JSR-000003)。

  • JMX 远程 API 规范(JSR-000160)。

  • MX4J homepage。 (MX4J 是各种 JMX 规范的开源实现.)

  • 5. JCA CCI

    Java EE 提供了一个规范来标准化对企业信息系统(EIS)的访问:JCA(Java EE 连接器体系结构)。该规范分为两个不同的部分:

  • 连接器提供程序必须实现的 SPI(服务提供程序接口)。这些接口构成了可以部署在 Java EE 应用程序服务器上的资源适配器。在这种情况下,服务器将 Management 连接池,事务和安全性(托管模式)。应用服务器还负责 Management 配置,该配置保存在 Client 端应用程序外部。连接器也可以在没有应用程序服务器的情况下使用。在这种情况下,应用程序必须直接对其进行配置(非托管模式)。

  • 应用程序可用于与连接器进行交互并由此与 EIS 通信的 CCI(通用 Client 端接口)。还提供了用于本地事务划分的 API。

  • Spring CCI 支持的目的是使用 Spring Framework 的常规资源和事务 Management 工具,提供类以典型的 Spring 样式访问 CCI 连接器。

    连接器的 Client 端始终不使用 CCI。某些连接器公开了自己的 API,提供了 JCA 资源适配器以使用 Java EE 容器的系统协定(连接池,全局事务和安全性)。 Spring 没有为此类特定于连接器的 API 提供特殊支持。

    5.1. 配置 CCI

    本节介绍如何配置公共 Client 端接口(CCI)。它包括以下主题:

  • Connector Configuration

  • Spring 中的 ConnectionFactory 配置

  • 配置 CCI 连接

  • 使用单个 CCI 连接

  • 5.1.1. 连接器配置

    使用 JCA CCI 的基本资源是ConnectionFactory接口。您使用的连接器必须提供此接口的实现。

    要使用连接器,可以将其部署在应用程序服务器上,并从服务器的 JNDI 环境(托管模式)中获取ConnectionFactory。连接器必须打包为 RAR 文件(资源适配器 Files),并包含ra.xml文件来描述其部署特性。资源的实际名称是在部署时指定的。要在 Spring 中访问它,可以使用 Spring 的JndiObjectFactoryBean<jee:jndi-lookup>通过其 JNDI 名称获取工厂。

    使用连接器的另一种方法是将其嵌入到您的应用程序中(非托管模式),而不使用应用程序服务器来部署和配置它。 Spring 提供了通过称为(LocalConnectionFactoryBean)的FactoryBean实现将连接器配置为 Bean 的可能性。通过这种方式,您只需要在 Classpath 中使用连接器库(不需要 RAR 文件和ra.xmlDescriptors)。如有必要,必须从连接器的 RAR 文件中提取该库。

    一旦可以访问ConnectionFactory实例,就可以将其注入到组件中。这些组件可以根据普通的 CCI API 进行编码,也可以使用 Spring 的支持类进行 CCI 访问(例如CciTemplate)。

    在非托管模式下使用连接器时,您将无法使用全局事务,因为资源永远不会在当前线程的当前全局事务中被征用或除名。该资源不知道任何可能正在运行的全局 Java EE 事务。

    5.1.2. Spring 中的 ConnectionFactory 配置

    要连接到 EIS,需要从应用程序服务器(如果处于托管模式)或直接从 Spring(如果处于非托管模式)获取ConnectionFactory

    在托管模式下,您可以从 JNDI 访问ConnectionFactory。其属性在应用程序服务器中配置。以下示例显示了如何执行此操作:

    <jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>
      

    在非托管模式下,必须将要在 Spring 的配置中使用的ConnectionFactory配置为 JavaBean。 LocalConnectionFactoryBean类提供了这种设置样式,传入了连接器的ManagedConnectionFactory实现,公开了应用程序级 CCI ConnectionFactory。以下示例显示了如何执行此操作:

    <bean id="eciManagedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
        <property name="serverName" value="TXSERIES"/>
        <property name="connectionURL" value="tcp://localhost/"/>
        <property name="portNumber" value="2006"/>
    </bean>
    <bean id="eciConnectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
        <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
    </bean>
        

    您不能直接实例化特定的ConnectionFactory。您需要为连接器完成ManagedConnectionFactory接口的相应实现。该接口是 JCA SPI 规范的一部分。

    5.1.3. 配置 CCI 连接

    通过 JCA CCI,您可以使用连接器的ConnectionSpec实现来配置到 EIS 的连接。要配置其属性,您需要使用专用适配器ConnectionSpecConnectionFactoryAdapter包装目标连接工厂。您可以使用connectionSpec属性(作为内部 bean)配置专用的ConnectionSpec

    此属性不是必需的,因为 CCI ConnectionFactory接口定义了两种不同的方法来获取 CCI 连接。您通常可以在应用程序服务器(处于托管模式下)或相应的本地ManagedConnectionFactory实现上配置某些ConnectionSpec属性。以下 Lists 显示了ConnectionFactory接口定义的相关部分:

    public interface ConnectionFactory implements Serializable, Referenceable {
        Connection getConnection() throws ResourceException;
        Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException;
      

    Spring 提供了一个ConnectionSpecConnectionFactoryAdapter,您可以指定一个ConnectionSpec实例用于给定工厂上的所有操作。如果指定了适配器的connectionSpec属性,则适配器将getConnection变量与ConnectionSpec参数一起使用。否则,适配器将使用不带该参数的变量。以下示例显示了如何配置ConnectionSpecConnectionFactoryAdapter

    <bean id="managedConnectionFactory"
            class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
        <property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
        <property name="driverName" value="org.hsqldb.jdbcDriver"/>
    </bean>
    <bean id="targetConnectionFactory"
            class="org.springframework.jca.support.LocalConnectionFactoryBean">
        <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
    </bean>
    <bean id="connectionFactory"
            class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
        <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
        <property name="connectionSpec">
            <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
                <property name="user" value="sa"/>
                <property name="password" value=""/>
            </bean>
        </property>
    </bean>
      

    5.1.4. 使用单个 CCI 连接

    如果要使用单个 CCI 连接,Spring 会提供另一个ConnectionFactory适配器来 Management 此连接。 SingleConnectionFactory适配器类延迟打开单个连接,并在应用程序关闭时销毁该 bean 时将其关闭。此类公开了具有相应行为的特殊Connection代理,它们均共享相同的基础物理连接。以下示例显示如何使用SingleConnectionFactory适配器类:

    <bean id="eciManagedConnectionFactory"
            class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
        <property name="serverName" value="TEST"/>
        <property name="connectionURL" value="tcp://localhost/"/>
        <property name="portNumber" value="2006"/>
    </bean>
    <bean id="targetEciConnectionFactory"
            class="org.springframework.jca.support.LocalConnectionFactoryBean">
        <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
    </bean>
    <bean id="eciConnectionFactory"
            class="org.springframework.jca.cci.connection.SingleConnectionFactory">
        <property name="targetConnectionFactory" ref="targetEciConnectionFactory"/>
    </bean>
        

    无法使用ConnectionSpec直接配置此ConnectionFactory适配器。如果您需要特定ConnectionSpec的单个连接,则可以使用SingleConnectionFactory与之联系的中介ConnectionSpecConnectionFactoryAdapter

    5.2. 使用 Spring 的 CCI 访问支持

    本节描述如何使用 Spring 对 CCI 的支持来实现各种目的。它包括以下主题:

  • Record Conversion

  • Using CciTemplate

  • 使用 DAO 支持

  • 自动输出记录生成

  • CciTemplate 交互摘要

  • 直接使用 CCI 连接和交互

  • CciTemplate 用法示例

  • 5.2.1. 记录转换

    Spring 的 JCA CCI 支持的目的之一是为处理 CCI 记录提供便利的设施。您可以指定策略来创建记录并从 Logging 提取数据,以用于 Spring 的CciTemplate。如果您不想直接在应用程序中使用记录,本节中描述的接口将策略配置为使用 Importing 和输出记录。

    要创建 ImportingRecord,可以使用RecordCreator接口的专用实现。以下 Lists 显示了RecordCreator接口定义:

    public interface RecordCreator {
        Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException;
      

    createRecord(..)方法接收RecordFactory实例作为参数,该实例与所使用的ConnectionFactoryRecordFactory相对应。您可以使用此引用来创建IndexedRecordMappedRecord实例。下面的示例演示如何使用RecordCreator接口以及索引或 Map 记录:

    public class MyRecordCreator implements RecordCreator {
        public Record createRecord(RecordFactory recordFactory) throws ResourceException {
            IndexedRecord input = recordFactory.createIndexedRecord("input");
            input.add(new Integer(id));
            return input;
      

    您可以使用输出Record从 EIS 接收数据。因此,您可以将RecordExtractor接口的特定实现传递给 Spring 的CciTemplate,以从输出Record提取数据。以下 Lists 显示了RecordExtractor接口定义:

    public interface RecordExtractor {
        Object extractData(Record record) throws ResourceException, SQLException, DataAccessException;
      

    以下示例显示了如何使用RecordExtractor界面:

    public class MyRecordExtractor implements RecordExtractor {
        public Object extractData(Record record) throws ResourceException {
            CommAreaRecord commAreaRecord = (CommAreaRecord) record;
            String str = new String(commAreaRecord.toByteArray());
            String field1 = string.substring(0,6);
            String field2 = string.substring(6,1);
            return new OutputObject(Long.parseLong(field1), field2);
      

    5.2.2. 使用 CciTemplate

    CciTemplate是核心 CCI 支持包(org.springframework.jca.cci.core)的中心类。由于它处理资源的创建和释放,因此它简化了 CCI 的使用。这有助于避免常见错误,例如忘记始终关闭连接。它关心连接和交互对象的生命周期,让应用程序代码专注于从应用程序数据生成 Importing 记录并从输出 Logging 提取应用程序数据。

    JCA CCI 规范定义了两种不同的方法来调用 EIS 上的操作。 CCI Interaction接口提供了两个 execute 方法签名,如以下 Lists 所示:

    public interface javax.resource.cci.Interaction {
        boolean execute(InteractionSpec spec, Record input, Record output) throws ResourceException;
        Record execute(InteractionSpec spec, Record input) throws ResourceException;
      

    根据所调用的模板方法,CciTemplate知道在交互中要调用哪个execute方法。无论如何,必须正确初始化InteractionSpec实例。

    您可以通过两种方式使用CciTemplate.execute(..)

  • 具有直接的Record参数。在这种情况下,您需要传递 CCIImporting 记录,并且返回的对象是相应的 CCI 输出记录。

  • 对于应用程序对象,通过使用记录 Map。在这种情况下,您需要提供相应的RecordCreatorRecordExtractor实例。

  • 对于第一种方法,使用模板的以下方法(直接与Interaction接口上的方法相对应):

    public class CciTemplate implements CciOperations {
        public Record execute(InteractionSpec spec, Record inputRecord)
                throws DataAccessException { ... }
        public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord)
                throws DataAccessException { ... }
      

    使用第二种方法,我们需要指定记录创建和记录提取策略作为参数。使用的接口是上一节有关记录转换中描述的接口。下面的 Lists 显示了相应的CciTemplate方法:

    public class CciTemplate implements CciOperations {
        public Record execute(InteractionSpec spec,
                RecordCreator inputCreator) throws DataAccessException {
            // ...
        public Object execute(InteractionSpec spec, Record inputRecord,
                RecordExtractor outputExtractor) throws DataAccessException {
            // ...
        public Object execute(InteractionSpec spec, RecordCreator creator,
                RecordExtractor extractor) throws DataAccessException {
            // ...
      

    除非在模板上设置了