Spring中,用JMS搞RPC時會用到:
spring在實現RPC的幾種方式上都提供了風格一致的支持。
在這裡我打算把幾種RPC模型記錄下來並作比較。
先從最基本的RMI開始。
RMI相關的API早在JDK1.1時就有了,我在這裡簡單描述一下RMI的原生實現(代碼可以從別的地方參考)。
如果是spring實現RMI,方法會簡單很多。
我們只需要用到兩個類:
我簡單定義一下接口和實現類:
package pac.testcase.ws;
public interface MyService {
public boolean inviteMeIn();
public String welcome();
}
package pac.testcase.ws.impl;
import pac.testcase.ws.MyService;
public class MyServiceImpl implements MyService{
public boolean inviteMeIn() {
return true;
}
public String welcome() {
return "Everybody is welcome!!";
}
}
簡簡單單,不需要繼承其他任何東西,非常pojo。
下面是spring相關配置:
<bean id="myService" class="pac.testcase.ws.impl.MyServiceImpl" />
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"
p:service-ref="myService"
p:serviceName="welcomeService"
p:serviceInterface="pac.testcase.ws.MyService"
/>
將我們的pojo導出為RMI服務,在這裡我采用默認配置。
地址在默認情況時如下:
/**
* Set the host of the registry for the exported RMI service,
* i.e. {@code rmi://HOST:port/name}
* <p>Default is localhost.
*/
public void setRegistryHost(String registryHost) {
this.registryHost = registryHost;
}
/**
* Set the port of the registry for the exported RMI service,
* i.e. {@code rmi://host:PORT/name}
* <p>Default is {@code Registry.REGISTRY_PORT} (1099).
* @see java.rmi.registry.Registry#REGISTRY_PORT
*/
public void setRegistryPort(int registryPort) {
this.registryPort = registryPort;
}
客戶端方面使用RmiProxyFactoryBean,被代理的服務就像一個簡單的bean一樣:
<bean id="clientSideService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
p:serviceUrl="rmi://localhost:1099/welcomeService"
p:serviceInterface="pac.test.RemoteService"
/>
配置中的pac.test.RemoteService就是那個簡單的bean,根據客戶端的需要,在這裡重新定義一下。
package pac.test;
public interface RemoteService {
public String welcome();
}
這樣就可以在服務端調用了,不用做什麼Naming.lookup(serviceUrl)之類的操作,遠程調用變得透明。
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
RemoteService service = (RemoteService)context.getBean("clientSideService");
System.out.println(service.welcome());
RMI雖然簡單高效,但使用RMI會存在一些問題,比如java序列化的版本問題或者防火牆問題(RMI不是基於HTTP的)。
Hessian / Burlap
Hessian和Burlap,現在進Caucho的網站都幾乎見不到這方面的內容了。
我也不知道有沒有人還會用這兩個東東,雖然去年出了一個版本,但上一個版本是在2010年。
剛才在群裡問了一下有沒有人用,結果還真有人用Hessian,他們是C#和Java做通信。
Burlap性能更令人頭疼,不知道還有沒有人提及。
雖然不知道使用情況如何,但也在這裡簡單記錄一下,拓展一下思維。
Hessian和Burlap都是由Caucho提供的,Hessian是Resin的一部分。
這兩個東西就像同一件事物的兩個部件,比如像這樣的槍+鏈鋸?
Hessian是binary transport protocol,但與RMI不同的是他不是java序列化對象,所以他可以和其他語言的程序通信,比如C++、C#、Python、Ruby什麼的。
Burlap是基於XML的,自然也可以支持很多不同的語言。
當然,同樣地傳輸內容下,XML的傳輸量會大一些。
如果要說有什麼好處的話也只有可讀性了。
實在懶得添加依賴再提供原生實現,但他並不復雜。
Creating a Hessian service using Java has four steps:
在這裡我主要記錄一下如何在spring中導出與調用Hessian service。
正如上面所說,我需要把服務配置到servlet engine中;
服務端和客戶端都需要添加一個dependency:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.33</version>
</dependency>
正好我這邊有個使用springMVC的應用,我就在這個基礎上導出Hessian service。
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
簡單寫一個接口與實現:
package pac.king.common.rpc;
public interface MyHessianService {
public String justHadEnoughParties();
}
package pac.king.common.rpc.impl;
import pac.king.common.rpc.MyHessianService;
public class MyHessianServiceImpl implements MyHessianService {
public String justHadEnoughParties() {
return "Please save me..";
}
}
我在spring-mvc.xml中曾經做了如下配置,並在*Controller中使用了RequestMapping注解去給URL做映射。
但這並不妨礙我導出Hessian service再為其映射一個URL:
<context:component-scan base-package="pac.king.controller"
use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/service=myHessianService
</value>
</property>
</bean>
導出Hessian service:
<bean id="myHessianServiceImpl" class="pac.king.common.rpc.impl.MyHessianServiceImpl" />
<bean id="myHessianService" class="org.springframework.remoting.caucho.HessianServiceExporter"
p:service-ref="myHessianServiceImpl"
p:serviceInterface="pac.king.common.rpc.MyHessianService"
/>
現在可以調用了,我需要在客戶端聲明一個接口(pac.test.HessianService),再用代理去調用:
<bean id="myHessianClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"
p:serviceUrl="http://localhost:8080/runtrain/service"
p:serviceInterface="pac.test.HessianService"
/>
調用:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
HessianService service = (HessianService)context.getBean("myHessianClient");
System.out.println(service.justHadEnoughParties());
console輸出:
對於Burlap,幾乎與Hessian的配置沒什麼區別;
只需要把HessianServiceExporter改為BurlapServiceExporter,
並將HessianProxyFactoryBean改為BurlapProxyFactoryBean即可。
RMI使用Java的序列化,而Hessian/Burlap則為了不同語言之間通信而使用私有的序列化。
如果我需要基於HTTP,但我並不需要多語言支持,我只想用Java...
我應該說這是基於Http的RMI嗎?
雖然看起來兩全其美,但也存在讓人"遺憾"的地方,
(事實上不怎麼遺憾的說,我曾經做過沒有Spring的項目,連持久層框架都是自己實現,做得越久越痛苦...)
他沒有所謂"原生"的實現,他是Spring的一部分,只能在Spring應用中使用。
Spring為這些RPC通信模型提供的相關類在命名上都有一致,都是:
自然地,HttpInvoker將用到
基於HttpInvoker的服務也像Hessian那樣由DispatcherServlet進行分發。
鑒於很多相同的地方,我打算繼續使用在上一篇中用Hessian通信的接口和實現類。
我幾乎不用做任何工作,URL映射也不需要修改,我只需要將服務端的配置修改一下:
<bean id="myHessianServiceImpl" class="pac.king.common.rpc.impl.MyHessianServiceImpl" />
<!-- <bean id="myHessianService" class="org.springframework.remoting.caucho.HessianServiceExporter"
p:service-ref="myHessianServiceImpl"
p:serviceInterface="pac.king.common.rpc.MyHessianService"
/> -->
<bean id="myHessianService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"
p:service-ref="myHessianServiceImpl"
p:serviceInterface="pac.king.common.rpc.MyHessianService"
/>
相應地,客戶端也只需要修改一下class:
<!-- <bean id="myHessianClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"
p:serviceUrl="http://localhost:8080/runtrain/service"
p:serviceInterface="pac.test.HessianService"
/> -->
<bean id="myHessianClient" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"
p:serviceUrl="http://localhost:8080/runtrain/service"
p:serviceInterface="pac.test.HessianService"
/>
這樣就保證了效率又解決了防火牆的問題,
後來我聽有人說,他們用Hessian做跨語言通信時,基於Http這個特征並不能解決防火牆的問題。
不知道他們具體情況如何,似乎沒說到一塊兒...
看了Hessian之後突然感覺Web service這種東西好笨重啊(雖然也有一些方法可以克服部分問題)。
既然有Hessian,那為什麼還要用Web service這種東西呢?
我突然開始懷疑起他存在的意義。
搜了一下,結果都是比較RPC通信模型的效率,沒有人說他們為什麼還要用(都應該有存在的意義吧)...
如果僅僅是效率的話都用Hessian不就得了?
帶著這個問題我逛了逛stackoverflow,然後我得到了下面幾種答案。
最後我還是沒有得到讓我滿意的答案,倒是復習了Web service...
很多類似場景下人們都將Web service視為"standard"option。 既然如此...那就看看Web service吧。
看來使用web service是不可避免的。
我曾對這個有些抵觸,因為他給我印象總是麻煩+慢(後來雖然方便了許多,但還是很慢)。
然後再去搜索"advantages of web service"什麼的試著再讓自己接受他。
簡單記錄一下如何用Spring導出Endpoint。
假設我想在有一個Spring應用,我需要把一個Pojo或者一部分方法導出為Web Service。
但這會有一個問題——Endpoint的生命周期是由JAX-WS runtime來管理(The lifecycle of such an endpoint instance will be managed by the JAX-WS runtime),
Spring context中的Bean無法autowire到Endpoint中,而我要導出的那些東東都用到了Spring管理的Bean。
對此,我們有兩個解決方法:
我上面括號中的那段話是引用的SpringBeanAutowiringSupport的javaDoc。
使用該類的典型案例就是bean注入到JAX-WS endpoint類中(人家注釋上寫的),任何一個生命周期不是由Spring來管理的場景都可以用到他。
而我們只需要繼承這個類,也就是說創建一個實例時會調用父類的無參構造方法,我們來看看他的構造方法:
/**
* This constructor performs injection on this instance,
* based on the current web application context.
* <p>Intended for use as a base class.
* @see #processInjectionBasedOnCurrentContext
*/
public SpringBeanAutowiringSupport() {
processInjectionBasedOnCurrentContext(this);
}
/**
* Process {@code @Autowired} injection for the given target object,
* based on the current web application context.
* <p>Intended for use as a delegate.
* @param target the target object to process
* @see org.springframework.web.context.ContextLoader#getCurrentWebApplicationContext()
*/
public static void processInjectionBasedOnCurrentContext(Object target) {
Assert.notNull(target, "Target object must not be null");
WebApplicationContext cc = ContextLoader.getCurrentWebApplicationContext();
if (cc != null) {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(cc.getAutowireCapableBeanFactory());
bpp.processInjection(target);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Current WebApplicationContext is not available for processing of " +
ClassUtils.getShortName(target.getClass()) + ": " +
"Make sure this class gets constructed in a Spring web application. Proceeding without injection.");
}
}
}
那就試試看:
@Service
@WebService(serviceName="testMyService")
public class MyServiceEndpoint extends SpringBeanAutowiringSupport{
@Autowired
MyService myService;
@WebMethod
public String sayHiFarAway(String name){
return myService.sayHiTo(name);
}
}
接著發布一下:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:applicationContext*.xml");
Endpoint.publish("http://localhost:8080/myservices", (MyServiceEndpoint)context.getBean(MyServiceEndpoint.class));
調用:
javax.xml.ws.Service service = javax.xml.ws.Service.create(url, new QName("http://endpoint.king.pac/","testMyService"));
QName q = new QName("http://endpoint.king.pac/","MyServiceEndpointPort");
MyClientService client = service.getPort(q,MyClientService.class);
System.out.println(client.sayHiFarAway("King"));
寫一個EndPoint還要繼承和業務無關的類,讓人不爽...而且發布和調用都麻煩。
那試試SimpleJaxWsServiceExporter,只需要簡單的配置就可以導出一個EndPoint。
但是他也有需要注意的地方,引用一下該類的javaDoc:
Note that this exporter will only work if the JAX-WS runtime actually supports publishing with an address argument, i.e. if the JAX-WS runtime ships an internal HTTP server. This is the case with the JAX-WS runtime that's inclued in Sun's JDK 1.6 but not with the standalone JAX-WS 2.1 RI.
SimpleJaxWsServiceExporter會自動detect所有被WebService注解的類,因此只需要在配置中聲明即可;此時Endpoint地址直接使用默認的localhost:8080:
<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter" />
接著改善一下客戶端的調用,使用JaxWsPortProxyFactoryBean:
<bean id="clientSide" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"
p:wsdlDocumentUrl="http://localhost:8080/testMyService?wsdl"
p:serviceName="testMyService"
p:portName="MyServiceEndpointPort"
p:serviceInterface="pac.king.endpoint.MyClientService"
p:namespaceUri="http://endpoint.king.pac/"
/>
這樣就可以像使用普通bean一樣使用service了:
MyClientService client = (MyClientService)context.getBean("clientSide");
System.out.println(client.sayHiFarAway("King"));
用起來方便了不少,但仍然無法改變一個事實:
Web service requests are larger than requests encoded with a binary protocol.
還有就是http/https的問題。
如果使用不當,我可能需要多做些工作去處理HTTP做不來的事情,而這時候RMI又非常適合。
Spring中如何配置Hibernate事務 http://www.linuxidc.com/Linux/2013-12/93681.htm
Struts2整合Spring方法及原理 http://www.linuxidc.com/Linux/2013-12/93692.htm
基於 Spring 設計並實現 RESTful Web Services http://www.linuxidc.com/Linux/2013-10/91974.htm
Spring-3.2.4 + Quartz-2.2.0集成實例 http://www.linuxidc.com/Linux/2013-10/91524.htm
使用 Spring 進行單元測試 http://www.linuxidc.com/Linux/2013-09/89913.htm
運用Spring注解實現Netty服務器端UDP應用程序 http://www.linuxidc.com/Linux/2013-09/89780.htm
Spring 3.x 企業應用開發實戰 PDF完整高清掃描版+源代碼 http://www.linuxidc.com/Linux/2013-10/91357.htm
Spring 的詳細介紹:請點這裡
Spring 的下載地址:請點這裡