Spring使用各种技术为远程处理提供支持。远程处理支持简化了远程支持服务的开发,通过Java接口和对象作为输入和输出实现。目前,Spring支持以下远程处理技术:
- 远程方法调用(RMI):通过使用RmiProxyFactoryBean和RmiServiceExporter,Spring支持传统的RMI(使用java.rmi.Remote接口和java.rmi.RemoteException)以及通过RMI调用器(使用任何Java接口)进行透明远程处理。
- Spring HTTP Invoker:Spring提供了一种特殊的远程处理策略,允许通过HTTP进行Java序列化,支持任何Java接口(就像RMI调用器那样)。相应的支持类是HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter。
- Hessian:通过使用Spring的HessianProxyFactoryBean和HessianServiceExporter,你可以通过Caucho提供的基于HTTP的轻量级二进制协议透明地公开你的服务。
- Java Web Services:Spring通过JAX-WS为Web服务提供远程处理支持。
- JMS:spring-jms模块中的JmsInvokerServiceExporter和JmsInvokerProxyFactoryBean类支持通过JMS作为底层协议进行远程处理。
- AMQP:通过AMQP作为底层协议的远程处理由单独的 Spring 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向远程客户机公开服务开始,并讨论使用RMI的缺点。然后以一个使用Hessian作为协议的示例继续。
一、RMI(远程方法调用)
通过使用Spring对RMI的支持,你可以通过RMI基础设施透明地公开你的服务。设置完成后,你基本上拥有一个类似于远程EJB的配置,除了没有对安全上下文传播或远程事务传播的标准支持。当你使用RMI调用器时,Spring确实为这种额外的调用上下文提供了钩子,因此你可以(例如)插入安全框架或自定义安全凭证。
使用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>
servicePort属性已被省略(默认为0)。这意味着使用匿名端口与服务通信。
在客户端连接服务
我们的客户机是一个简单的对象,它使用AccountService来管理帐户,如下例所示:
public class SimpleObject { private AccountService accountService; public void setAccountService(AccountService accountService) { this.accountService = accountService; } // additional methods using the accountService }
为了在客户机上连接服务,我们创建一个单独的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>
这就是我们需要做的,以支持客户端上的远程帐户服务。Spring透明地创建一个调用器,并通过RmiServiceExporter远程启用帐户服务。在客户端,我们使用RmiProxyFactoryBean将其链接起来。
二、使用Hessian通过HTTP远程调用服务
Hessian提供了一个基于HTTP的二进制远程处理协议。它是由Caucho开发的,你可以访问https://www.caucho.com/ 。
Hessian
Hessian通过HTTP进行通信,并使用一个定制的servlet进行通信。通过使用Spring的DispatcherServlet规则,我们可以连接这样一个servlet来公开你的服务。首先,我们必须在应用程序中创建一个新的servlet,如下面的web.xml文件:
<servlet> <servlet-name>remoting</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--springmvc.xml 是自己创建的SpringMVC全局配置文件,用contextConfigLocation作为参数名来加载 如果不配置 contextConfigLocation,那么默认加载的是/WEB-INF/servlet名称-servlet.xml,在这里也就是 springmvc-servlet.xml 参数多个值使用逗号隔开,如:a.xml,b.xml <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:config/springmvc.xml</param-value> </init-param> --> <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容器配置。下一节将使用应用程序上下文。
或者,考虑使用Spring更简单的HttpRequestHandlerServlet。这样可以将远程导出器定义嵌入根应用程序上下文(默认情况下,在WEB-INF/applicationContext.xml),单个servlet定义指向特定的导出器bean。在本例中,每个servlet名称都需要与其目标导出器的bean名称相匹配。
使用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>
现在我们已经准备好在客户端连接服务了。没有指定显式的处理程序映射(将请求url映射到服务上),所以我们使用BeanNameUrlHandlerMapping。因此,服务在包含DispatcherServlet实例的映射(如前所定义)中的bean名称所指示的URL处导出:http://主机:8080/远程处理/AccountService。
或者,你可以在根应用程序上下文中(例如,在WEB-INF中)创建HessianServiceExporter/applicationContext.xml),如下例所示:
<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名称需要与目标导出器的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>
在客户端连接服务
通过使用HessianProxyFactoryBean,我们可以在客户端连接服务。同样的原则适用于RMI。我们创建一个单独的bean工厂或应用程序上下文,并在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>
对通过Hessian公开的服务应用进行HTTP基本身份验证
Hessian的一个优点是我们可以很容易地应用HTTP基本身份验证,因为这两个协议都是基于HTTP的。你的常规HTTP服务器安全机制可以通过使用web.xml文件,例如,安全功能。通常,你不需要在这里对每个使用用户进行安全凭据验证。相反,你可以使用在HessianProxyFactoryBean级别定义的共享凭据(类似于JDBC数据源),如下例所示:
<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 映射,并设置了一个拦截器,以便仅允许管理员和操作员调用此应用程序上下文中提到的bean。
三、Spring HTTP调用程序
与Hessian相反,Spring HTTP invokers都是轻量级协议,它们使用自己的slim序列化机制,并使用标准Java序列化机制通过HTTP公开服务。如果参数和返回类型是无法使用Hessian使用的序列化机制进行序列化的复杂类型,则这有很大的优势
在幕后,Spring使用JDK或apachehttpcomponents提供的标准工具来执行HTTP调用。
请注意由于不安全的Java反序列化导致的漏洞:在反序列化步骤中,被操纵的输入流可能导致服务器上执行不需要的代码。因此,不要向不受信任的客户端公开HTTP调用程序端点。相反,只在你自己的服务之间公开它们。一般来说,我们强烈建议使用其他任何消息格式(如JSON)。
如果你担心Java序列化导致的安全漏洞,请考虑核心JVM级别的通用序列化过滤器机制,该机制最初是为JDK 9开发的,但同时后移植到JDK 8、7和6。
https://blogs.oracle.com/java-platform-group/entry/incoming_filter_serialization_data_a 和 https://openjdk.java.net/jeps/290
公开服务对象
为服务对象设置HTTP invoker 基础设施与使用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>
或者,你可以在根应用程序上下文(例如,在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>
此外,你可以在中为此导出器定义相应的servlet web.xml文件,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>
在客户端连接服务
同样,从客户端连接服务与使用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>
如前所述,你可以选择要使用的HTTP客户端。默认情况下,HttpInvokerProxy使用JDK的HTTP功能,但是你也可以通过设置httpInvokerRequestExecutor属性来使用apache HttpComponents客户机。下面的示例演示如何执行此操作:
<property name="httpInvokerRequestExecutor"> <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/> </property>
四、Java WebService
五、JMS
六、AMQP
七、选择技术时的注意事项
八、REST服务
Spring框架为调用REST服务提供了两种选择:
- RestTemplate:原始的spring rest客户机,具有同步的模板方法API。
- WebClient:一种无阻塞、响应式的替代方案,支持同步和异步以及流式场景。
RestTemplate
RestTemplate是一个执行HTTP请求的同步客户端。它是原始的spring rest客户端,在底层HTTP客户端库上公开了一个简单的模板方法API。
从5.0开始,Restemplate处于维护模式,以后只接受少量的更改和bug请求。请考虑使用WebClient,它提供了一个更现代的API,并支持同步、异步和流式处理方案。
WebClient
WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。