序:本文主要是总结和归纳spring的远程服务相关知识,可作为入门学习笔记.写博客目的也是为了进行知识梳理,便于以后查看.本文主要参考资料 spring 实战第三版
本文主要讨论内容如下:
- 远程调度概
- spring整合远程方法调用(RMI)服务
- Hessian和Burlap服务与spring的整合
- spring的HTTP invokerspring
- 结合JAX-WS开发Web服务
- Lingo异步通信
- 基于spring消息的远程调用
- 小结
1.远程调度概况
远程调度主要是客户端与服务端之间的会话.客户端和服务端的会话开始于客户端应用的一个远程过程调用(RPC),即客户端调用服务器端的方法或函数但又感觉不到是在远程调用,即程序员认为调用的方法在本地.远程调度主要是为了提供多个应用或服务之间的信息交流,这种模式适用于SOA(面向服务编程).这里的客户端不是平常B/S模式下的浏览器,而是另一种应用或服务,与服务端所指的应用或服务不在同一个应用或电脑上.
spring支持多种RPC模型,并且对每一种模型都提供了风格一致的支持,意味着只要学习一种模型的spring整合方法变知道其他模型配置方法。这些模型可以分为同步通信和异步通信两大类。
同步通信:客户端调用服务端方法时必须等待服务完成才能执行客户端代码,必须保证服务端存在。
异步通信:客户端调用服务端方法时,不需要等待服务的完成便可执行自己的内部方法。
同步通信模型包括:
- 远程方法调用(RMI):不考虑网络限制时(如不考虑防火墙),访问/发布基于java的服务
- Hessian和Blurlap: 考虑网络限制,通过HTTP访问/发布基于java的服务
- Sping自带的HTTP invoker:考虑网络限制,并希望使用基于XML或专有的java序列化机制,访问/发布基于spring的服务.(必须要有spring的支持,即服务和spring绑定过紧).
- JAX-WS的Web服务:访问/发布平台中立的,基于SOAP的Web服务.
异步通信模型:
- Lingo
- 基于spring的消息RPC.
1.1 同步通信和异步通信的区别
同步特点:
- 同步通信意味着客户端要等待服务端的服务调用完成才能执行客户端的代码.对客户端性能带来负面影响.
- 客户端和服务端耦合性高.通过客服端通过服务接口与服务端联系,如果服务端接口发生变化那么客户端必须改变;如果服务端不可用,那么客户端也无法运行;客户端必须制定服务端的地址.
异步特点:
- 无需等待:客户端不需要等待服务器的服务完成,提高了客户端性能.
- 基于JMS的异步通信能够实现客户端与服务端的解耦,不需要依赖于特点服务接口.
1.2 spring支持的远程服务工作原理
在所有的模型中,服务都作为Spring所管理的Bean配置在应用中。这里采用了代理模式的设计模式风格,spring为每个模型都提供了一个代理工厂Bean负责远程调用,使得能够像调用本地方法一样调用远程服务。具体模式如下:
客户端向代理发起调用,就像代理提供了这些服务。代理代表客户端与远程服务进行通信,并由它负责链接细节及向远处服务发起远程调用。如果远程调用时发生java.rmi.RemoteException异常时,代理会处理此异常并重新抛出非检查型异常RemoteAccessException。
检查型异常和非检查型异常的区别:
检查型异常在Java中所有不是RuntimeException派生的Exception都是检查型异常。当函数中存在抛出检查型异常的操作时该函数的函数声明中必须包含throws语句。
调用改函数的函数也必须对该异常进行处理,如不进行处理则必须在调用函数上声明throws语句。
非检查型异常:在Java中所有RuntimeException的派生类都是非检查型异常,与检查型异常相对抛出非检查型异常可以不在函数声明中添加throws语句,调用函数上也不需要强制处理。
即可以不使用try...catch进行处理,但是如果有异常产生,则异常将由JVM进行处理,也会导致程序中断。
服务端需要将服务注册,发布一个远程服务,才能保证客户端的代理能够访问。服务端的结构特点如下:
2.同步通信模型
2.1spring整合RMI服务
2.1.1 概念:
spring提供了简单地发布RMI的方式,不用编写那些需要抛出RemoteException异常的特定RMI类,只需要编写实现服务功能的POJO.
POJO(Plain Ordinary Java Object),是用来表示普通的Java对象,实质上可以理解为简单的实体类,但不是JavaBean。POJO不含有业务逻辑,也不实现任何特殊的Java框架的接口如,EJB,JDBC等等。
完全POJO的系统称为轻量级系统,如Spring.
2.1.2 服务端:发布RMI服务
首先定义一个我们需要发布的服务接口和实现:
服务接口:
public interface UserService{ public void saveUser(User user); public User getUser(int userId); public boolean deleteUser(int userId); }
服务实现(因为在spring的各个模型中都会提供一个远程输出器,所以实现类不需要在每个方法中抛出java.rmi.RemoteException异常。这也使得实现了保持纯POJO特点。):
public UserServiceImpl implements UserService{ public void saveUser(User user){ System.out.println("saveUser"); } public User getUser(int userId){ System.out.println("getUser"); } public boolean deleteUser(int userId){ System.out.println("getUser"); } }
这个服务接口和服务实现我们将用在下面所有的模型中。接下来就是RMI 发布该服务,提供客户端调用(这里需要在客户端放入一份相同的UserService 接口代码)。发布远程服务只需要一个远程输出器负责将实现类的Bean包装在一个适配器中,然后适配器被绑定到RMI注册表中,并将请求代理给服务类。每个模型都是相同的结构,都是通过一个远程输出器发布服务,参看图:
具体代码:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter" p:service-ref="userService" p:serviceName="userService" p:serviceInterface="rpc.service.UserService" p:registryHost="rmi.user.com" p:registryPort="1199" /> <bean id="userService" class="rpc.service.impl.UserServiceImpl">
service-ref 指定要发布的具体Bean,这里是UserServiceImpl
serviceName:命名该RMI服务
serviceInterface:指定该服务所实现的接口
registryHost: RMI注册表绑定的主机,默认是本地主机地址
registryPost: RMI注册表绑定的端口,默认是1099
2.1.3 客户端
传统的RMI存在以下问题:
- RMI查找可能会导致3种检查型异常(RemoteException,NotBoundException,MalformedURLException),所以必须处理异常
- 存在大量的样版式代码
所以Spring为RMI和其他远程调用模块都提供了一个工厂Bean,负责为服务创建代理。spring为RMI提供RmiProxyFactoryBean工厂,简化了客户端操作。
public class UserServiceTest{' @Autowired UserService userService; public void saveUser(User user){ userService.saveUser(user); } }
<bean id="userService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean" p:serviceUrl="rmi://localhost/userService" p:serviceInterface="rmi.service.UserService" />
serviceUrl:指定了服务URL. serviceInterface指定服务实现的接口。 通过RmiProxyFactory不仅可以减少样板式代码,还为我们处理异常。同时在调用时根本不知道所用的userService是来自本地还是服务。客户端和代理的交互图:
其他模型架构一摸一样。因为其他模型在sping里使用的模型一样,下面就直接列出不同模型的代码。
2.2 Hessian和Burlap服务与spring的整合
2.2.1 Hessian服务端
是基于HTTP协议,所以spring为其提供的远程输出类HessianServiceExporter是基于Spring MVC。所以需要在web.xml中配置:
<servlet-mapping> <servlet-name>user</servlet-name> <url-pattern>*.service</url-pattern> </servlet-mapping>
spring配置文件
<!-- url 映射配置 -->
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mapping"> <value>/userService.service=userService</value> </property> </bean>
<!--服务配置 --> <bean id="userService" class="org.springframework.remoting.caucho.HessianServiceExporter" p:service-ref="userService" p:serviceInterface="rpc.service.UserService"/> <bean id="userService" class="rpc.service.imp.UserServiceImpl"/>
所有以userService.service 结尾的URL请求都会由userService的bean处理。 与RMI不同的是这里不需要设置serviceName,因为Hessian没有注册表。
2.2.2 Hessian客户端
配置和RMI基本相同,不同的是serviceUrl标识服务的URL。 对应的代理是HessianProxyFactoryBean.
<bean id="userService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean" p:serviceUrl="http://localhost:8080/user/user.service" p:serviceInterface="rpc.service.UserService"/>
2.2.3 Burlap
Burlap是基于xml协议,它和Hessian一样,都是基于HTTP协议.Burlap是基于XML的,自然也可以支持很多不同的语言。它需要配置web.xml和spring文件,大部分代码都一样,所以这里省略了web.xml和 url的映射配置。
<bean id="userService" class="org.springframework.remoting.caucho.BurlapServiceExporter" p:service-ref="userService" p:serviceInterface="rpc.service.UserService"/> <bean id="userService" class="rpc.service.imp.UserServiceImpl"/>
<bean id="userService" class="org.springframework.remoting.caucho.BurlapProxyFactoryBean" p:serviceUrl="http://localhost:8080/user/user.service" p:serviceInterface="rmi.service.UserService" />
2.3 spring的HTTP invoker
spring 的http invoker是使用http协议(让防火墙可以接受),并使用了java序列化机制。所以这里在服务端也采用了和hessian,burlap一样的配置,需要配置web.xml和spring配置文件的url映射。和上面代码一样。http invoker有一个严重的限制:它是基于spring框架提供的远程调用解决方案,所以客户端和服务端必须都是spring应用。
<bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter" p:service-ref="userService" p:serviceInterface="rpc.service.UserService"/> <bean id="userService" class="rpc.service.imp.UserServiceImpl"/>
<bean id="userService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean" p:serviceUrl="http://localhost:8080/user/usr.service" p:serviceInterface="rmi.service.UserService" />
2.4 spring 结合JAX-WS开发Web服务
JAX-WS编程模型使用注解将类和方法声明为Web服务的操作.使用@WebService注解所标注的类被认为Web服务的端点,使用@WebMethod注解所标注的方法被认为是操作.如:
@WebService(serviceName="userService") public UserServiceImpl implements UserService{ @WebMethod public void saveUser(User user){ System.out.println("saveUser"); } @WebMethod public User getUser(int userId){ System.out.println("getUser"); } @WebMethod public boolean deleteUser(int userId){ System.out.println("getUser"); } }
需要在配置文件中配置JAX-WS的导出器,便可以将上面的服务注册发布:
<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter" p:baseAddress="http://localhost:8888/user/" />
默认服务地址为http://localhost:8080/项目名字
3.异步模型
上面介绍的都是同步通信的远程服务调用.但它存在一些问题如服务端和客户端的耦合,客户端等待等问题.下面介绍的是基于JMS(java message service)的远程调用框架.基于JMS的RPC框架有以下优点:
- 无需等待,客户端无需等待服务端的服务完成(一般是对无返回值方法的调用采用异步调用)
- 面向消息和解耦:不依赖于服务器和客户端的服务接口,而是使用JMS发送消息.
- 位置独立.客户端不需要知道服务端的地址,便可以发送远程方法调用.当服务端采用点对点模型时,可以增加服务端数量成立服务集群,这样多个服务可以从同一个队列中接收和处理消息,提高服务端的负载.如果采用发布-订阅模型的话,可以使用不同服务器对相同的服务请求做出不同的实现.
- 如果服务端崩溃不影响客户端的执行.
3.1 基于spring消息的RPC
服务bean的导出时基于JMS服务.不是完全意义上的异步通信,没有解决客户端等待问题,主要是解决客户端和服务端的耦合问题.Spring 提供的方案有一个缺点就是只能使用点对点消息。
3.1.1 服务端
public interface HelloWord {
public void sayHello(String name);
}
@Component
public class HelloWorldImpl implements HelloWorld {
@Autowired
private Producer producer;
public void sayHello(String name) {
Mail mail = new Mail();
mail.setContent("Hello," + name);
mail.setTo(name);
producer.send(mail);
}
}
JmsInvokerServiceExporter是Spring 提供的基于JMS服务导出的工厂类.
<bean id="jmsServiceExporter" class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
<property name="service" ref="helloService"/>
<property name="serviceInterface" value="rpc.service.HelloWorld"/>
</bean>
<bean id="helloService" class="rpc.service.impl.HelloWorldImpl"/>
将导出器配置为JMS监听器,这样它才能够知道如何连接消息代理.
<amq:connectionFactory id="jmsFactory" /> <jms:listener-container destination-type="queue" connection-factory="jmsFactory" concurrency="3" container-type="simple"> <jms:listener destination="sparta" ref="jmsServiceExporter" /> </jms:listener-container>
3.1.2 客户端
<amq:connectionFactory id="connectionFactory" /> <bean id="clientService" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean" p:serviceInterface="rpc.service.HelloWorld" p:connectionFactory-ref="connectionFactory" p:queueName="sparta"/>
@Test public void testSend() throws JMSException, InterruptedException { myHelloService.sayHello("Test"); }
3.2 Lingo
是基于spring的远程调用解决方案.它充分利用了JMS来实现在真正的异步服务调用,即客户端发起请求时,服务端不需要处理可用状态,客户端也无需等待服务端的执行完成服务.它的主要思想是,对于无返回的方法调用采用异步通信,有返回数据的方法采用JMS调用.
服务端java代码与上面的一样,客户端java代码也是.这里主要区别是服务端和客户端的配置代码.
<bean id="jmsServiceExporter" class="org.logicblaze.lingo.jms.JmsServiceExporter" p:connectionFactory-ref="connectionFactory" p:destination-ref="sparta" p:service-ref="helloService" p:serviceInterface="rpc.service.HelloWorld" /> <bean id="helloService" class="rpc.service.impl.HelloWorldImpl"/> <amq:queue id="sparta" physicalName="sparta"/>
客户端:
<bean id="helloService" class="org.logicblaze.lingo.jms.JmsProxyFactoryBean" p:connectionFactory-ref="connectionFactory" p:destination-ref="sparta" P:serviceInterface="rpc.service.HelloWorldService" > <property name="metadataStrategy"> <bean id="metadataStrategy" class="org.logicblaze.lingo.SimpleMetadataStrategy"> <constructor-arg value="true" /> </bean> </property> </bean>
metadataStrategy的构造器参数值设置为true,标识这个所有的void方法都视为单向方法,因此可以被异步调用,无需等待服务端的服务完成便可执行客户端代码.
6.小结
本文主要是对spring中的几个远程调度模型做一个知识梳理.spring所支持的RPC框架可以分为两类,同步调用和异步调用.同步调用如:RMI,Hessian,Burlap,Http Invoker,JAX-WS. RMI采用java序列化,但很难穿过防火墙.Hessian,Burlap都是基于http协议,能够很好的穿过防火墙.但使用了私有的对象序列化机制,Hessian采用二进制传送数据,而Burlap采用xml,所以Burlap能支持很多语言如python,java等.Http Invoker 是sping基于HTTP和java序列化协议的远程调用框架,只能用于java程序的通行.Web service(JAX-WS)是连接异构系统或异构语言的首选协议,它使用SOAP形式通讯,可以用于任何语言,目前的许多开发工具对其的支持也很好.
同步通信有一定的局限性.所以出现了异步通信的RPC框架,如lingo和基于sping JMS的RPC框架.