使用 Spring 28. Remoting 和 web services

28.1 简介

Spring features integration classes 用于使用各种技术进行远程支持。远程支持简化了 remote-enabled 服务的开发,由您的常规(Spring)POJO 实现。目前,Spring 支持以下远程技术:

  • Remote 方法调用(RMI)。通过使用RmiProxyFactoryBeanRmiServiceExporter Spring 支持传统的 RMI(带有java.rmi.Remote接口和java.rmi.RemoteException)和通过 RMI 调用者(带有任何 Java 接口)的透明远程处理。

  • Spring 的 HTTP 调用程序。 Spring 提供了一种特殊的远程处理策略,允许通过 HTTP 进行 Java 序列化,支持任何 Java 接口(就像 RMI 调用程序一样)。相应的支持 classes 是HttpInvokerProxyFactoryBeanHttpInvokerServiceExporter

  • 黑森州。通过使用 Spring 的HessianProxyFactoryBeanHessianServiceExporter,您可以使用 Caucho 提供的轻量级二进制 HTTP-based 协议透明地公开您的服务。

  • 粗麻布。 Burlap 是 Caucho 的替代 Hessian 的替代品。 Spring 提供支持 classes,如BurlapProxyFactoryBeanBurlapServiceExporter

  • JAX-WS。 Spring 通过 JAX-WS(JAX-RPC 的后继者,如 Java EE 5 和 Java 6 中引入)为 web services 提供远程支持。

  • JMS。通过JmsInvokerServiceExporterJmsInvokerProxyFactoryBean classes 支持使用 JMS 作为底层协议进行远程处理。

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

在讨论 Spring 的远程处理功能时,我们将使用以下域 model 和相应的服务:

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 开始将服务公开给 remote client,并谈谈使用 RMI 的缺点。然后我们将继续使用 Hessian 作为协议来显示一个 example。

28.2 使用 RMI 公开服务

使用 Spring 对 RMI 的支持,您可以透明地通过 RMI 基础结构公开您的服务。完成此设置后,除了没有标准支持 security context 传播或 remote transaction 传播之外,您基本上都具有类似于 remote EJB 的 configuration。在使用 RMI 调用程序时,Spring 确实为这样的附加调用 context 提供了钩子,因此您可以在此处插入安全框架或自定义安全凭证。

28.2.1 使用 RmiServiceExporter 导出服务

使用RmiServiceExporter,我们可以将 AccountService object 的接口公开为 RMI object。在传统的 RMI 服务的情况下,可以使用RmiProxyFactoryBean或通过普通 RMI 访问接口。 RmiServiceExporter明确支持通过 RMI 调用者公开任何 non-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 注册表的 port。通常,您的 application 服务器也维护一个 RMI 注册表,明智的做法是不干扰那个。此外,service name 用于绑定服务。所以现在,该服务将绑定在'rmi://HOST:1199/AccountService'。我们稍后将使用该 URL 链接到 client 端的服务。

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

28.2.2 在 client 的服务中进行链接

我们的 client 是一个简单的 object,使用AccountService来管理帐户:

public class SimpleObject {

    private AccountService accountService;

    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

    // additional methods using the accountService

}

要链接 client 上的服务,我们将创建一个单独的 Spring 容器,其中包含简单的 object 和连接 configuration 位的服务:

<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 上支持 remote 帐户服务所需要做的全部工作。 Spring 将透明地创建一个调用者并通过RmiServiceExporter远程启用帐户服务。在 client,我们使用RmiProxyFactoryBean链接它。

28.3 使用 Hessian 或 Burlap 通过 HTTP 远程调用服务

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

28.3.1 为 Hessian 和 co。连接 DispatcherServlet。

Hessian 通过 HTTP 进行通信,并使用自定义 servlet 进行通信。使用 Spring 的DispatcherServlet原则,从 Spring Web MVC 用法中可以看出,您可以轻松连接这样一个暴露您的服务的 servlet。首先,我们必须在 application 中创建一个新的 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'的 Spring 容器配置资源(在 servlet 的 name 之后)。 application context 将在下一节中使用。

或者,考虑使用 Spring 更简单的HttpRequestHandlerServlet。这允许您在根 application context 中嵌入 remote 导出器定义(默认情况下在'WEB-INF/applicationContext.xml'中),单个 servlet 定义指向特定的导出器 beans。在这种情况下,每个 servlet name 需要 match 其目标导出器的 bean name。

28.3.2 使用 HessianServiceExporter 公开 beans

在新创建的名为remoting-servlet.xml的 application context 中,我们将创建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 的服务。没有指定显式的处理程序映射,将请求 URL 映射到服务,因此将使用BeanNameUrlHandlerMapping:因此,服务将在包含DispatcherServlet的映射(如上定义)中通过其 bean name 指示的 URL 导出:'http://HOST:8080/remoting/AccountService'

或者,在根 application context(e.g. in '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,结果相同:导出器将映射到请求路径/remoting/AccountService。请注意,servlet name 需要 match 目标导出器的 bean name。

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

28.3.3 链接 client 上的服务

使用HessianProxyFactoryBean我们可以链接到 client 的服务。同样的原则适用于 RMI example。我们将创建一个单独的 bean 工厂或 application context 并提及以下 beans,其中SimpleObject使用AccountService来管理帐户:

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

28.3.4 使用粗麻布

我们不会在这里详细讨论 Burlap,即 Hessian 的 XML-based 等价物,因为它的配置和设置方式与上面解释的 Hessian 变体完全相同。只需将Hessian替换为Burlap,就可以了。

28.3.5 将 HTTP 基本身份验证应用于通过 Hessian 或 Burlap 公开的服务

Hessian 和 Burlap 的一个优点是我们可以轻松应用 HTTP 基本身份验证,因为这两个协议都是 HTTP-based。对于 example,可以通过使用web.xml security features 轻松应用正常的 HTTP 服务器安全机制。通常,您不在此处使用 per-user 安全凭证,而是在Hessian/BurlapProxyFactoryBean level(类似于 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>

这是一个 example,我们明确提到BeanNameUrlHandlerMapping并设置一个拦截器,只允许管理员和 operators 调用 application context 中提到的 beans。

当然,这个示例并未显示灵活的安全基础结构。有关安全性的更多选项,请查看http://projects.spring.io/spring-security/处的 Spring Security 项目。

28.4 使用 HTTP 调用程序公开服务

与使用自己的苗条序列化机制的轻量级协议 Burlap 和 Hessian 相反,Spring HTTP 调用者使用标准的 Java 序列化机制通过 HTTP 公开服务。如果您的 arguments 和 return 类型是使用 Hessian 和 Burlap 使用的序列化机制无法序列化的复杂类型,那么这具有巨大的优势(在选择远程处理技术时,请参阅下一节以了解更多注意事项)。

在引擎盖下,Spring 使用 JDK 提供的标准工具或 Apache HttpComponents来执行 HTTP calls。如果您需要更高级和 easier-to-use 功能,请使用后者。有关更多信息,请参阅hc.apache.org/httpcomponents-client-ga/

请注意由于不安全的 Java 反序列化导致的漏洞:在反序列化 step 期间,操作的输入流可能导致服务器上不需要的 code 执行。因此,不要将 HTTP 调用者 endpoints 暴露给不受信任的客户端,而只是在您自己的服务之间。通常,我们强烈建议使用任何其他消息格式(e.g. JSON)。

如果您担心由于 Java 序列化导致的安全漏洞,请考虑核心 JVM level 上的 general-purpose 序列化过滤机制,最初为 JDK 9 开发,但同时向后移植到 JDK 8,7 和 6:https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a http://openjdk.java.net/jeps/290

28.4.1 公开服务 object

为服务 object 设置 HTTP 调用程序基础结构与使用 Hessian 或 Burlap 的方式非常相似。就像 Hessian 支持提供HessianServiceExporter一样,Spring 的 HttpInvoker 支持提供了org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter

要在 Spring Web MVC DispatcherServlet中公开AccountService(如上所述),需要在调度程序的 application context 中使用以下 configuration:

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

这样的出口商定义将通过DispatcherServlet的标准制图设施公开,如 Hessian 部分所述。

或者,在根 application context(e.g. in '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 name 与目标导出器的 bean name 匹配:

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

如果您在 servlet 容器之外运行并使用 Oracle 的 Java 6,那么您可以使用 built-in HTTP 服务器 implementation。您可以将SimpleHttpServerFactoryBeanSimpleHttpInvokerServiceExporter一起配置,如此 example 中所示:

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

<bean id="httpServer"
        class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
    <property name="contexts">
        <util:map>
            <entry key="/remoting/AccountService" value-ref="accountExporter"/>
        </util:map>
    </property>
    <property name="port" value="8080"/>
</bean>

28.4.2 在 client 的服务中进行链接

同样,来自 client 的服务链接非常类似于使用 Hessian 或 Burlap 时的方式。使用代理,Spring 将能够将 calls 转换为指向导出服务的 URL 的 HTTP POST 请求。

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

如前所述,您可以选择要使用的 HTTP client。默认情况下,HttpInvokerProxy使用 JDK 的 HTTP 功能,但您也可以通过设置httpInvokerRequestExecutor property 来使用 Apache HttpComponents client:

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

28.5 Web services

Spring 提供对标准 Java web services API 的完全支持:

  • 使用 JAX-WS 公开 web services

  • 使用 JAX-WS 访问 web services

除了 Spring Core 中 JAX-WS 的库存支持外,Spring 产品组合还_fe

28.5.1 使用 JAX-WS 公开 servlet-based web services

Spring 为 JAX-WS servlet 端点 implementations 提供了方便的 base class - SpringBeanAutowiringSupport。为了公开我们的AccountService,我们在这里扩展 Spring 的SpringBeanAutowiringSupport class 并实现我们的业务逻辑,通常将调用委托给业务层。我们只需使用 Spring 的@Autowired annotation 来表达对 Spring-managed beans 的依赖。

/**
 * 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 context 相同的 web application 中运行,以允许访问 Spring 的工具。默认情况下,在 Java EE 5 环境中使用 JAX-WS servlet 端点部署的标准 contract 就是这种情况。有关详细信息,请参阅 Java EE 5 web service 教程。

28.5.2 使用 JAX-WS 导出独立的 web services

Oracle JDK 附带的 built-in JAX-WS 提供程序支持使用 JDK 中包含的 built-in HTTP 服务器暴露 web services。 Spring 的SimpleJaxWsServiceExporter检测 Spring application context 中的所有@WebService带注释的 beans,通过默认的 JAX-WS 服务器(JDK HTTP 服务器)导出它们。

在这种情况下,端点实例被定义和管理为 Spring beans 本身;它们将在 JAX-WS 引擎中注册,但它们的生命周期将取决于 Spring application context。这意味着可以将诸如显式依赖注入之类的 Spring 功能应用于端点实例。当然,annotation-driven 注入@Autowired也可以。

<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但不必,因为端点在这里是完全 Spring-managed bean。这意味着端点 implementation 可能如下所示,没有声明任何超类 - 并且 Spring 的@Autowired configuration annotation 仍然被尊重:

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

}

28.5.3 使用 JAX-WS RI 的 Spring 支持导出 web services

Oracle 的 JAX-WS RI 是作为 GlassFish 项目的一部分开发的,它将 Spring 支持作为其 JAX-WS Commons _project 的一部分。这允许将 JAX-WS endpoints 定义为 Spring-managed beans,类似于上一节中讨论的独立模式 - 但在 Servlet 环境中这是 time。请注意,这在 Java EE 5 环境中不可移植;它主要用于 non-EE 环境,例如 Tomcat,将 JAX-WS RI 作为 web application 的一部分嵌入。

导出 servlet-based endpoints 的标准样式的不同之处在于端点实例本身的生命周期将由 Spring 在此处管理,并且web.xml中将只定义一个 JAX-WS servlet。使用标准的 Java EE 5 样式(如上所示),每个服务端点都有一个 servlet 定义,每个端点通常委托给 Spring beans(通过使用@Autowired,如上所示)。

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

28.5.4 使用 JAX-WS 访问 web services

Spring 提供了两个工厂 beans 来创建 JAX-WS web service 代理,即LocalJaxWsServiceFactoryBeanJaxWsPortProxyFactoryBean。前者只能_return JAX-WS service class 供我们使用。后者是 full-fledged version,可以 return 实现我们的业务服务接口的代理。在这个 example 中,我们使用后者为AccountService端点创建一个代理(再次):

<bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
    <property name="serviceInterface" value="example.AccountService"/>
    <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>

serviceInterface是 clients 将使用的业务接口。 wsdlDocumentUrl是 WSDL 文件的 URL。 Spring 需要这个启动 time 来创建 JAX-WS 服务。 namespaceUri对应于.wsdl 文件中的 targetNamespace。 serviceName对应于.wsdl 文件中的服务 name。 portName对应于.wsdl 文件中的 port name。

现在访问 web service 非常简单,因为我们有一个 bean 工厂,它将它作为AccountService接口公开。我们可以在 Spring 中连接它:

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

从 client code 我们可以访问 web service,就像它是一个普通的 class 一样:

public class AccountClientImpl {

    private AccountService service;

    public void setService(AccountService service) {
        this.service = service;
    }

    public void foo() {
        service.insertAccount(...);
    }
}

上面的内容略有简化,因为 JAX-WS 需要使用@WebService@SOAPBinding etc annotations 注释端点接口和 implementation classes。这意味着您不能(轻松地)使用普通 Java 接口和 implementation classes 作为 JAX-WS endpoint artifacts;你需要先对它们进行相应的注释。有关这些要求的详细信息,请查看 JAX-WS 文档。

28.6 JMS

还可以使用 JMS 作为底层通信协议透明地公开服务。 Spring Framework 中的 JMS 远程支持非常基本 - 它在same thread和 non-transactional Session上发送和接收,因此吞吐量将非常依赖于 implementation。请注意,这些 single-threaded 和 non-transactional 约束仅适用于 Spring 的 JMS 远程支持。有关 Spring 对 JMS-based 消息的丰富支持的信息,请参阅第 30 章,JMS(Java 消息服务)

服务器和 client 端都使用以下接口。

package com.foo;

public interface CheckingAccountService {

    public void cancelAccount(Long accountId);

}

在 server-side 上使用以下简单 implementation 的上述接口。

package com.foo;

public class SimpleCheckingAccountService implements CheckingAccountService {

    public void cancelAccount(Long accountId) {
        System.out.println("Cancelling account [" + accountId + "]");
    }

}

此 configuration 文件包含 client 和服务器上共享的 JMS-infrastructure beans。

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

28.6.1 Server-side configuration

在服务器上,您只需要使用JmsInvokerServiceExporter公开服务 object。

<?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"});
    }

}

28.6.2 Client-side configuration

client 只需要创建一个 client-side 代理,它将实现商定的接口(CheckingAccountService)。在以下 bean 定义的后面创建的结果 object 可以注入到其他 client 端 objects 中,代理将负责通过 JMS 将调用转发到 server-side object。

<?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));
    }

}

28.7 AMQP

有关更多信息,请参阅Spring AMQP Reference Document'Spring Remoting with AMQP'部分

remote 接口未实现 28.8 Auto-detection

remote 接口没有实现 auto-detection 实现接口的主要原因是避免为 remote 调用者打开太多门。目标 object 可能会实现内部回调接口,如InitializingBeanDisposableBean,这些接口不希望向调用者公开。

提供具有目标实现的所有接口的代理通常在本地情况下无关紧要。但是在导出 remote 服务时,您应该公开一个特定的服务接口,其中包含用于 remote 用法的特定操作。除了内部回调接口,目标可能会实现多个业务接口,其中只有一个用于 remote 暴露。出于这些原因,我们需要指定这样的服务接口。

这在配置方便性和内部方法意外暴露的风险之间是 trade-off。始终指定服务接口不是太费力,并且使您在特定方法的受控暴露方面处于安全的位置。

28.9 选择技术时的注意事项

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

使用 RMI 时,除非您正在隧道传输 RMI 流量,否则无法通过 HTTP 协议访问 objects。 RMI 是一个相当 heavy-weight 协议,因为它支持 full-object 序列化,这在使用需要通过线路进行序列化的复杂数据 model 时非常重要。但是,RMI-JRMP 与 Java clients 绑定:它是一个 Java-to-Java 远程解决方案。

如果你需要 HTTP-based 远程处理但是依赖于 Java 序列化,Spring 的 HTTP 调用程序是一个很好的选择。它与 RMI 调用者共享基本的基础结构,只使用 HTTP 作为传输。请注意,HTTP 调用程序不仅限于 Java-to-Java 远程处理,还包括 client 和服务器端的 Spring。 (后者也适用于 Spring 的 RMI 调用者 non-RMI interfaces.)

Hessian and/or Burlap 在异构环境中运行时可能会提供重要的 value,因为它们明确允许 non-Java clients。但是,non-Java 支持仍然有限。已知问题包括 Hibernate objects 与 lazily-initialized 集合的序列化。如果您有这样的数据 model,请考虑使用 RMI 或 HTTP 调用程序而不是 Hessian。

JMS 可用于提供服务集群并允许 JMS broker 负责负载平衡,发现和 auto-failover。默认情况下:使用 JMS 远程处理时使用 Java 序列化,但 JMS 提供程序可以使用不同的线程格式化机制,例如 XStream,以允许服务器在其他技术中实现。

最后但同样重要的是,EJB 比 RMI 具有优势,因为它支持标准 role-based 身份验证和授权以及 remote transaction 传播。有可能让 RMI 调用者或 HTTP 调用者也支持安全 context 传播,虽然核心 Spring 没有提供:这里只插入适当的钩子来插入 third-party 或自定义解决方案。

28.10 在 client 上访问 RESTful 服务

RestTemplate是 client-side 访问 RESTful 服务的核心 class。它在概念上类似于 Spring 中的其他模板 classes,例如JdbcTemplateJmsTemplate以及其他 Spring 投资组合项目中的其他模板 classes。 RestTemplate's behavior is customized by providing callback methods and configuring the HttpMessageConverter used to marshal objects into the HTTP request body and to unmarshal any response back into an object. As it is common to use XML as a message format, Spring provides a MarshallingHttpMessageConverter that uses the Object-to-XML framework that is part of the org.springframework.oxm`包。这为您提供了多种 XML 到 Object 映射技术的选择。

本节介绍如何使用RestTemplate及其关联的HttpMessageConverters

28.10.1 RestTemplate

在 Java 中调用 RESTful 服务通常使用辅助 class 来完成,例如 Apache HttpComponents HttpClient。对于 common REST 操作,此方法太低 level,如下所示。

String uri = "http://example.com/hotels/1/bookings";

PostMethod post = new PostMethod(uri);
String request = // create booking request content
post.setRequestEntity(new StringRequestEntity(request));

httpClient.executeMethod(post);

if (HttpStatus.SC_CREATED == post.getStatusCode()) {
    Header location = post.getRequestHeader("Location");
    if (location != null) {
        System.out.println("Created new booking at :" + location.getValue());
    }
}

RestTemplate 提供了更高的 level 方法,这些方法对应于六个主要的 HTTP 方法中的每一个,这些方法使得调用许多 RESTful 服务成为 one-liner 并强制执行 REST 最佳实践。

RestTemplate 有一个异步 counter-part:见第 28.10.3 节,“异步 RestTemplate”

表格 1_.RestTemplate 方法概述

HTTP 方法RestTemplate 方法
删除删除
得到getForObject getForEntity
headForHeaders(String url,String ... uriVariables)
OPTIONSoptionsForAllow(String url,String ... uriVariables)
POSTpostForLocation(String url,Object request,String ... uriVariables) postForObject(String url, Object request, Class<T> responseType, String… uriVariables)
put(String url,Object request,String ... uriVariables)
PATCH 和其他人交换 执行

RestTemplate方法的名称遵循命名约定,第一部分指示正在调用的 HTTP 方法,第二部分指示返回的内容。对于 example,方法getForObject()将执行 GET,将 HTTP 响应转换为您选择的 object 类型,并 returnobject。方法postForLocation()将执行 POST,将给定的 object 转换为 HTTP 请求,并 return 响应 HTTP Location 标头,其中可以找到新创建的 object。如果 exception 处理 HTTP 请求,将抛出RestClientException类型的 exception;通过将另一个ResponseErrorHandler implementation 插入RestTemplate可以更改此行为。

exchangeexecute方法是上面列出的更具体方法的通用版本,可以支持其他组合和方法,e.g. HTTP PATCH。但是,请注意底层 HTTP library 还必须支持所需的组合。 JDK HttpURLConnection不支持PATCH方法,但 Apache HttpComponents HttpClient version 4.2 或更高版本。它们还允许RestTemplate使用ParameterizedTypeReference读取对泛型类型(e.g. List<Account>)的 HTTP 响应,这是一个新的 class,可以捕获和传递泛型类型信息。

传递给这些方法并从这些方法返回的 Objects 通过HttpMessageConverter implementations 转换为 HTTP 消息和从 HTTP 消息转换。默认情况下会注册主 MIME 类型的转换器,但您也可以通过messageConverters() bean property 覆盖默认值并注册自定义转换器。默认转换器是ByteArrayHttpMessageConverterStringHttpMessageConverterResourceHttpMessageConverterSourceHttpMessageConverter以及AllEncompassingFormHttpMessageConverter和几个 provider-specific 转换器:e.g. 当 Jackson 存在于 classpath 时MappingJackson2HttpMessageConverter

每种方法都以两种形式获取 URI 模板 arguments,可以是String variable-length 参数,也可以是Map<String,String>。例如,

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

使用 variable-length arguments 和

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

使用Map<String,String>

要创建RestTemplate的实例,您只需调用默认的 no-arg 构造函数即可。这将使用java.net包中的标准 Java classes 作为底层 implementation 来创建 HTTP 请求。可以通过指定ClientHttpRequestFactory的 implementation 来覆盖它。 Spring 提供 implementation HttpComponentsClientHttpRequestFactory,它使用 Apache HttpComponents HttpClient来创建请求。 HttpComponentsClientHttpRequestFactory使用org.apache.http.client.HttpClient的实例进行配置,该实例又可以配置凭据信息或连接池功能。

请注意,HTTP 访问的java.net implementation 可能会在访问表示错误的响应的状态时引发 exception(e.g. 401)。如果这是一个问题,请切换到HttpComponentsClientHttpRequestFactory

使用 Apache HttpComponents HttpClient的前一个 example 直接重写为使用RestTemplate如下所示

uri = "http://example.com/hotels/{id}/bookings";

RestTemplate template = new RestTemplate();

Booking booking = // create booking object

URI location = template.postForLocation(uri, booking, "1");

要使用 Apache HttpComponents 而不是本机java.net功能,请按如下方式构造RestTemplate

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

Apache HttpClient 支持 gzip 编码。要使用它,像这样构造一个HttpComponentsClientHttpRequestFactory

HttpClient httpClient = HttpClientBuilder.create().build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);

通用回调接口是RequestCallback,并在调用 execute 方法时调用。

public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
        ResponseExtractor<T> responseExtractor, String... uriVariables)

// also has an overload with uriVariables as a Map<String, String>.

RequestCallback接口定义为

public interface RequestCallback {
 void doWithRequest(ClientHttpRequest request) throws IOException;
}

并允许您操作请求 headers 并写入请求正文。使用 execute 方法时,您不必担心任何资源 management,模板将始终关闭请求并处理任何错误。有关使用 execute 方法及其他方法 arguments 的含义的更多信息,请参阅 API 文档。

使用 URI

对于每个主要的 HTTP 方法,RestTemplate提供了以 String URI 或java.net.URI作为第一个参数的变体。

String URI 变体接受模板 arguments 作为 String variable-length 参数或Map<String,String>。他们还假设 URL String 未编码,需要进行编码。例如以下内容:

restTemplate.getForObject("http://example.com/hotel list", String.class);

将在http://example.com/hotel%20list上执行 GET。这意味着如果输入的 URL String 已经编码,它将被编码两次 - i.e。 http://example.com/hotel%20list将成为http://example.com/hotel%2520list。如果这不是预期的效果,请使用java.net.URI方法变体,如果您想多次重复使用单个(完全展开的)URI,则假定 URL 已经编码通常也很有用。

UriComponentsBuilder class 可用于 build 和编码URI,包括对 URI 模板的支持。对于 example,您可以从 URL String 开始:

UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

URI uri = uriComponents.toUri();

或者单独指定每个 URI component:

UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

URI uri = uriComponents.toUri();

处理请求和响应 headers

除了上面描述的方法之外,RestTemplate还有exchange()方法,可以用于基于HttpEntity class 的任意 HTTP 方法执行。

也许最重要的是,exchange()方法可用于添加请求 headers 和读取响应 headers。例如:

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);

HttpEntity<String> response = template.exchange(
        "http://example.com/hotels/{hotel}",
        HttpMethod.GET, requestEntity, String.class, "42");

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

在上面的例子中,我们首先准备一个包含MyRequestHeader标头的请求实体。然后我们检索响应,并读取MyResponseHeader和 body。

Jackson JSON Views 支持

可以指定Jackson JSON 查看仅序列化 object properties 的子集。例如:

MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(value);
String s = template.postForObject("http://example.com/user", entity, String.class);

28.10.2 HTTP 消息转换

传递给方法getForObject()postForLocation()put()并从方法返回的 Objects 被HttpMessageConverters转换为 HTTP 请求和 HTTP 响应。 HttpMessageConverter界面如下所示,让您更好地了解其功能

public interface HttpMessageConverter<T> {

    // Indicate whether the given class and media type can be read by this converter.
    boolean canRead(Class<?> clazz, MediaType mediaType);

    // Indicate whether the given class and media type can be written by this converter.
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // Return the list of MediaType objects supported by this converter.
    List<MediaType> getSupportedMediaTypes();

    // Read an object of the given type from the given input message, and returns it.
    T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;

    // Write an given object to the given output message.
    void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;

}

framework 中提供了主媒体(mime)类型的具体 implementation,默认情况下注册 client-side 上的RestTemplate和 server-side 上的RequestMethodHandlerAdapter

HttpMessageConverter的__mplempleations 将在以下部分中介绍。对于所有转换器,使用默认媒体类型,但可以通过设置supportedMediaTypes bean property 来覆盖它

StringHttpMessageConverter

一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入 Strings。默认情况下,此转换器支持所有文本媒体类型(text/*),并使用Content-Type text/plain进行写入。

FormHttpMessageConverter

一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入表单数据。默认情况下,此转换器读取和写入媒体类型application/x-www-form-urlencoded。表格数据从MultiValueMap<String, String>读取并写入.3_。

ByteArrayHttpMessageConverter

一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入字节数组。默认情况下,此转换器支持所有媒体类型(*/*),并使用Content-Type application/octet-stream进行写入。这可以通过设置supportedMediaTypes property 和覆盖getContentType(byte[])来覆盖。

MarshallingHttpMessageConverter

一个HttpMessageConverter implementation,可以使用来自org.springframework.oxm包的 Spring 的MarshallerUnmarshaller抽象来读写 XML。该转换器需要MarshallerUnmarshaller才能使用。这些可以通过构造函数或 bean properties 注入。默认情况下,此转换器支持(text/xml)和(application/xml)。

MappingJackson2HttpMessageConverter

一个HttpMessageConverter implementation,可以使用 Jackson 的ObjectMapper来读写 JSON。通过使用 Jackson 提供的注释,可以根据需要自定义 JSON 映射。当需要进一步控制时,可以通过ObjectMapper property 注入自定义ObjectMapper,以用于需要为特定类型提供自定义 JSON serializers/deserializers 的情况。默认情况下,此转换器支持(application/json)。

MappingJackson2XmlHttpMessageConverter

一个HttpMessageConverter implementation,可以使用Jackson XML扩展名的XmlMapper来读写 XML。可以根据需要通过使用 JAXB 或 Jackson 提供的注释来自定义 XML 映射。当需要进一步控制时,可以通过ObjectMapper property 注入自定义XmlMapper,以用于需要为特定类型提供自定义 XML serializers/deserializers 的情况。默认情况下,此转换器支持(application/xml)。

SourceHttpMessageConverter

一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入javax.xml.transform.Source。仅支持DOMSourceSAXSourceStreamSource。默认情况下,此转换器支持(text/xml)和(application/xml)。

BufferedImageHttpMessageConverter

一个HttpMessageConverter implementation,可以从 HTTP 请求和响应中读取和写入java.awt.image.BufferedImage。此转换器读取和写入 Java I/O API 支持的媒体类型。

28.10.3 Async RestTemplate

Web applications 当天经常需要查询外部 REST 服务。当针对这些需求扩展 applications 时,HTTP 和同步 calls 的本质可能会带来挑战:可能会阻塞多个线程,等待 remote HTTP 响应。

AsyncRestTemplate第 28.10.1 节,“RestTemplate”的 API 非常相似;见Table 28.1,“RestTemplate 方法概述”。这些 API 之间的主要区别在于AsyncRestTemplate返回ListenableFuture包装器而不是具体结果。

之前的RestTemplate example 转换为:

// async call
Future<ResponseEntity<String>> futureEntity = template.getForEntity(
    "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

// get the concrete result - synchronous call
ResponseEntity<String> entity = futureEntity.get();

ListenableFuture接受完成回调:

ListenableFuture<ResponseEntity<String>> futureEntity = template.getForEntity(
    "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

// register a callback
futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
    @Override
    public void onSuccess(ResponseEntity<String> entity) {
        //...
    }

    @Override
    public void onFailure(Throwable t) {
        //...
    }
});

默认的AsyncRestTemplate构造函数注册SimpleAsyncTaskExecutor以执行 HTTP 请求。在处理大量 short-lived 请求时,像ThreadPoolTaskExecutor 类这样的 thread-pooling TaskExecutor implementation 可能是一个不错的选择。

有关详细信息,请参阅ListenableFuture javadocsAsyncRestTemplate javadocs

Updated at: 7 months ago
VII. IntegrationTable of content29. Enterprise JavaBeans(EJB)integration