本文基于《Spring實戰(zhàn)(第4版)》所寫笙纤。
我們有多種可以使用的遠程調(diào)用技術(shù),包括:
- 遠程方法調(diào)用(Remote Method Invocation, RMI);
- Caucho的Hessian和Burlap;
- Spring基于HTTP的遠程服務(wù);
- 使用JAX-RPC和JAX-WS的Web Service酌伊。
Spring遠程調(diào)用概覽
遠程調(diào)用是客戶端應(yīng)用和服務(wù)端之間的會話购城。在客戶端,它所需要的一些共功能并不在該應(yīng)用的實現(xiàn)范圍之內(nèi)匣沼,所以應(yīng)用要向能提供這些功能的其他系統(tǒng)尋求幫助。而遠程應(yīng)用通過遠程服務(wù)暴露這些功能乃秀。
下表概述了每個一個Spring支持的RPC模型肛著,并簡要討論了它們所適用的不同場景。
RPC模型 | 適用場景 |
---|---|
遠程方法調(diào)用(RMI) | 不考慮網(wǎng)絡(luò)限制時(例如防火墻)跺讯,訪問/發(fā)布基于Java的服務(wù) |
Hessian或Burlap | 考慮網(wǎng)絡(luò)限制時枢贿,通過HTTP訪問/發(fā)布基于Java的服務(wù)。Hessian是二進制協(xié)議刀脏,而Burlap是基于XML的(Spring 5.0不支持Burlap了) |
HTTP invoker | 考慮網(wǎng)絡(luò)限制局荚,并希望使用基于XML或?qū)S械男蛄谢瘷C制實現(xiàn)Java序列化時,訪問/發(fā)布基于Spring的服務(wù) |
JAX-PRC和JAX-WS | 訪問/發(fā)布平臺獨立的愈污、基于SOAP的Web服務(wù) |
在所有的模型中耀态,服務(wù)都作為Spring所管理的bean配置到我們的應(yīng)用中。這是通過一個代理工廠bean實現(xiàn)的暂雹,這個bean能夠把遠程服務(wù)像本地對象一樣裝配到其他bean的屬性中去首装。下圖展示了它是如何工作的。
客戶端向代理發(fā)起作用,就像代理提供了這些服務(wù)一樣涧尿。代理代表客戶端與遠程服務(wù)進行通信系奉,由它負責處理連接的細節(jié)并向遠程服務(wù)發(fā)起調(diào)用。
更重要的是姑廉,如果調(diào)用遠程服務(wù)時發(fā)生java.rmi.RemoteException異常缺亮,代理會處理此異常并重新拋出非檢查型異常RemoteAccessException。遠程異常通常預(yù)示著系統(tǒng)發(fā)生了無法優(yōu)雅恢復(fù)的問題桥言,如網(wǎng)絡(luò)或配置問題萌踱。既然客戶端通常無法從遠程異常中恢復(fù),那么重新拋出RemoteAccessException異常就能讓客戶端決定是否處理此異常号阿。
在服務(wù)器端虫蝶,我們可以使用上表所列出的任意一種模型將Spring管理的bean發(fā)布為遠程服務(wù)。下圖展示了遠程導(dǎo)出器(remote exporter)如何將bean方法發(fā)布為遠程服務(wù)倦西。
無論我們開發(fā)的是使用遠程服務(wù)的代碼,還是實現(xiàn)這些服務(wù)的代碼赁严,或者兩者兼而有之扰柠,在Spring中粉铐,使用遠程服務(wù)純粹是一個配置問題。我們不需要編寫任何Java代碼就可以支持遠程調(diào)用卤档。我們的服務(wù)bean也不需要關(guān)心他們是否參與了一個RPC(當然蝙泼,任何傳遞給遠程調(diào)用的bean或從遠程調(diào)用返回的bean可能需要實現(xiàn)java.io.Serializable接口)。
使用RMI
RMI涉及到好幾個步驟劝枣,包括程序的和手工的汤踏。Spring簡化了RMI模型,它提供了一個代理工廠bean舔腾,能讓我們把RMI服務(wù)像本地JavaBean那樣裝配到我們的Spring應(yīng)用中溪胶。Spring還提供了一個遠程導(dǎo)出器,用來簡化把Spring管理的bean轉(zhuǎn)換為RMI服務(wù)的工作稳诚。
導(dǎo)出RMI服務(wù)
創(chuàng)建RMI服務(wù)哗脖,會涉及如下幾個步驟:
- 編寫一個服務(wù)實現(xiàn)類,類中的方法必須拋出java.rmi.RemoteException異常扳还;
- 創(chuàng)建一個繼承于java.rmi.Remote的服務(wù)接口才避;
- 運行RMI編譯器(rmic),創(chuàng)建客戶端stub類和服務(wù)端skeleton類氨距;
- 啟動一個RMI注冊表桑逝,以便持有這些服務(wù);
- 在RMI注冊表中注冊服務(wù)俏让。
在Spring中配置RMI服務(wù)
Spring只需簡單地編寫實現(xiàn)服務(wù)功能的POJO就可以了楞遏,Spring會處理剩余的其他事項。
我們將要創(chuàng)建RMI服務(wù)需要發(fā)布SpitterService接口中的方法舆驶,如下的程序清單展現(xiàn)了該接口定義
package spittr.web;
import spittr.model.Spitter;
import spittr.model.Spittle;
import java.util.List;
public interface SpitterService {
List<Spittle> getRecentSpittles(int count);
void saveSpittle(Spittle spittle);
void saveSpitter(Spitter spitter);
Spitter getSpitter(long id);
void startFollowing(Spitter follower, Spitter followee);
List<Spittle> getSpittlesForSpitter(Spitter spitter);
List<Spittle> getSpittlesForSpitter(String username);
Spitter getSpitter(String username);
Spittle getSpittleById(long id);
void deleteSpittle(long id);
List<Spitter> getAllSpitters();
}
如果使用傳統(tǒng)的RMI來發(fā)布服務(wù)橱健,SpitterService和SpitterServiceImpl中的所有方法都需要拋出java.rmi.RemoteException。但是如果使用Spring的RmiServiceExporter把該類轉(zhuǎn)變?yōu)镽MI服務(wù)沙廉,那現(xiàn)有的實現(xiàn)不需要做任何改變拘荡。
RmiServiceExporter可以把任意Spring管理的bean發(fā)布為RMI服務(wù)。如下圖所示撬陵,RmiServiceExporter把bean包裝在一個適配器類中珊皿,然后適配器類被綁定到RMI注冊表中,并且代理到服務(wù)類的請求—在本例中服務(wù)類也就是SpitterServiceImpl巨税。
使用RmiServiceExporter將SpitterServiceImpl發(fā)布為RMI服務(wù)的最簡單方式是在Spring中使用如下的@Bean方法進行配置:
@Bean
public RmiServiceExporter rmiServiceExporter(SpitterService spitterService){
RmiServiceExporter rmiExporter = new RmiServiceExporter();
rmiExporter.setService(spitterService);
rmiExporter.setServiceName("SpitterService");
rmiExporter.setServiceInterface(SpitterService.class);
// rmiExporter.setRegistryHost("rmi.spitter.com"); // 可以不用草添,直接用本機地址
// rmiExporter.setRegistryPort(1199); // 可以不用驶兜,默認1099
return rmiExporter;
}
這就是使用Spring把某個bean轉(zhuǎn)變?yōu)镽MI服務(wù)所需要做的全部工作。現(xiàn)在Spitter服務(wù)已經(jīng)導(dǎo)出為RMI服務(wù),我們可以為Spittr應(yīng)用創(chuàng)建其他的用戶界面或邀請第三方使用此RMI服務(wù)創(chuàng)建新的客戶端抄淑。如果使用Spring屠凶,客戶端開發(fā)者訪問Spitter的RMI服務(wù)會非常容易。
也可以使用XML配置
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="server" />
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="SpitterServer" />
<property name="service" ref="spitterServerImpl" />
<property name="serviceInterface"
value="server.SpitterServer" />
</bean>
</beans>
需要注意的是肆资,由于rmi服務(wù)一般來講使用jar包直接啟動矗愧,所以我們還在工程中建立一個主函數(shù)
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) throws InterruptedException {
// System.setProperty("java.rmi.server.hostname", "192.168.68.115");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
}
}
然后打包為jar,并在Linux中執(zhí)行郑原,命令請看在linux下發(fā)布jar包
如果使用jar啟服務(wù)時唉韭,提示沒有“沒有主清單屬性”,請看maven生成jar犯犁,提示沒有“沒有主清單屬性”
啟動服務(wù)后属愤,如果提示
[root@VM_0_17_centos ftpUser]# java -jar SpittrServer-1.0-SNAPSHOT.jar
四月 19, 2018 10:52:45 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@22d8cfe0: startup date [Thu Apr 19 10:52:45 CST 2018]; root of context hierarchy
四月 19, 2018 10:52:45 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [app.xml]
四月 19, 2018 10:52:46 上午 org.springframework.remoting.rmi.RmiServiceExporter getRegistry
信息: Looking for RMI registry at port '1099'
四月 19, 2018 10:52:46 上午 org.springframework.remoting.rmi.RmiServiceExporter getRegistry
信息: Could not detect RMI registry - creating new one
四月 19, 2018 10:52:46 上午 org.springframework.remoting.rmi.RmiServiceExporter prepare
信息: Binding service 'SpitterServer' to RMI registry: RegistryImpl[UnicastServerRef [liveRef: [endpoint:[10.45.***.***:1099](local),objID:[0:0:0, 0]]]]
則表示服務(wù)請用成功。如果報Connection Refused栖秕,或者endpoint:后面的IP為127.0.0.1都表明服務(wù)啟用不成功恨闪。
解決方法有兩種
- 在代碼中譬正,啟用服務(wù)前添加如下語句
System.setProperty("java.rmi.server.hostname", "192.168.68.115");
"192.168.68.115"表示當前IP也切,可以是局域網(wǎng)地址杜漠,也可以是外網(wǎng)地址
推薦本地調(diào)用時使用
- 如果是Linux系統(tǒng),先 vim /etc/hosts ,文件內(nèi)容如下:
127.0.0.1 localhost localhost.localdomain VM_0_17_centos
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
"VM_0_17_centos"是機器名
修改為
140.143.234.154 VM_0_17_centos localhost localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
下面轉(zhuǎn)換一下視角暑塑,來看看如何編寫Spitter RMI服務(wù)的客戶端吼句。
裝配RMI服務(wù)
傳統(tǒng)上,RMI客戶端必須使用RMI API的Naming類從RMI注冊表中查找服務(wù)事格。例如惕艳,下面的代碼片段演示了如何獲取Spitter的RMI服務(wù):
try{
String serviceUrl = "rmi:/spitter/SpitterService";
SpitterService spitterService = (SpitterService) Naming.lookup(serviceUrl);
...
}
catch (RemoteException e) { ... }
catch (NotBoundException e) { ... }
catch (MalformedURLException e) { ... }
雖然這段代碼可以獲取Spitter的RMI服務(wù)的引用,但是它存在兩個問題:
- 傳統(tǒng)的RMI查找可能會導(dǎo)致3種檢查型異常的任意一種(RemoteException驹愚、NotBoundException和MalformedURLException)远搪,這些異常必須被捕獲或重新拋出;
- 需要Spitter服務(wù)的任何代碼都必須自己負責獲取該服務(wù)逢捺。這屬于樣板代碼谁鳍,與客戶端的功能并沒有直接關(guān)系。
Spring的RmiProxyFactoryBean是一個工廠bean劫瞳,該bean可以為RMI服務(wù)創(chuàng)建代理倘潜。使用RmiProxyFactoryBean引用SpitterService的RMI服務(wù)是非常簡單的,只需要在客戶端的Spring配置中增加如下的@Bean方法:
@Bean
public RmiProxyFactoryBean spitterService() {
RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
rmiProxy.setServiceUrl("rmi://192.168.68.115:1099/SpitterService");
rmiProxy.setServiceInterface(SpitterService.class);
return rmiProxy;
}
也可以使用XML
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="client" />
<bean id="clientInvoke" class="client.ClientInvoke" />
<bean class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://192.168.68.115:1099/SpitterServer"></property>
<property name="serviceInterface" value="client.SpitterServer"></property>
</bean>
</beans>
服務(wù)的URL是通過RmiProxyFactoryBean的serviceUrl屬性來設(shè)置的志于,在這里涮因,服務(wù)名被設(shè)置為SpitterService,并且聲明服務(wù)是在本地機器上的伺绽;同時服務(wù)提供的接口由serviceInterface屬性來指定养泡。下圖展示了客戶端和RMI代理和交互嗜湃。
現(xiàn)在已經(jīng)把RMI服務(wù)聲明為Spring管理的bean输硝,我們就可以把它作為依賴裝配進另一個bean中,就像任意非遠程的bean的那樣程梦。例如点把,假設(shè)客戶端需要使用Spitter服務(wù)為指定的用戶獲取Spittle列表,我們可以使用@Autowired注解把服務(wù)代理裝配進客戶端中:
@Autowired
SpitterService spitterService;
我們還可以像本地bean一樣調(diào)用它的方法:
public List<Spittle> getSpittle(String userName) {
Spitter spitter = spitterService.getSpitter(userName);
return spitterService.getSpittlesForSpitter(spitter);
}
此外屿附,代理捕獲了這個服務(wù)所有可能拋出的RemoteException異常郎逃,并把它包裝為運行期異常重新拋出,這樣我們就可以放心地忽略這些異常挺份。
如果用于調(diào)試可創(chuàng)建一個主函數(shù)調(diào)用
import client.ClientInvoke;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
ClientInvoke clientInvoke = (ClientInvoke) context.getBean("clientInvoke");
clientInvoke.test();
context.close();
}
}
提醒一下褒翰,本例中調(diào)用了兩次服務(wù),都會受網(wǎng)絡(luò)延遲的影響匀泊,進而可能會影響到客戶端的性能优训。
RMI是一種實現(xiàn)遠程服務(wù)交互的好辦法,但是它存在某些限制各聘。首先揣非,RMI很難穿越防火墻,這是因為RMI使用任意端口來交互—這是防火墻通常所不允許的躲因。
另外一件需要考慮的事情是RMI是基于Java的早敬。這意味這客戶端和服務(wù)端必須都是用Java開發(fā)的。因為RMI使用了Java的序列化機制大脉,所以通過網(wǎng)絡(luò)傳輸?shù)膶ο箢愋捅仨氁WC在調(diào)用兩端的Java運行時中是完全相同的版本搞监。
使用Hessian發(fā)布遠程服務(wù)
Hessian和Burlap是Caucho Technology提供的兩種基于HTTP的輕量級遠程服務(wù)解決方案。
- Hessian镰矿,基于二進制消息進行交互琐驴。可在Java衡怀、PHP棍矛、Python、C++和C#抛杨。由于二進制交互够委,帶寬上更具優(yōu)勢。
- Burlap怖现,基于XML的遠程調(diào)用技術(shù)茁帽。它的消息結(jié)構(gòu)盡可能的簡單玉罐,不需要額外的外部定義語句(例如WSDL或IDL),Spring 5.0不支持Burlap了潘拨。
使用Hessian導(dǎo)出bean的功能
像之前一樣吊输,把SpitterServiceImpl類的功能發(fā)布為遠程服務(wù)—這次是一個Hessian服務(wù)。我們只需要編寫一個繼承com.caucho.hessian.server.HessianServlet的類铁追,并確保所有的服務(wù)方法是public的(在Hessian里季蚂,所有public方法被視為服務(wù)方法)。
和Spring一起使用時琅束,可利用Spring的AOP來為Hessian服務(wù)提供系統(tǒng)級服務(wù)扭屁,例如聲明式事務(wù)。
導(dǎo)出Hessian服務(wù)
為了把Spitter服務(wù)bean發(fā)布為Hessian服務(wù)涩禀,我們需要配置另一個導(dǎo)出bean料滥,只不過這次是HessianServiceExporter。
它把POJO的public方法發(fā)布成Hessian服務(wù)的方法艾船。不過正如下圖所示葵腹,其實現(xiàn)過程與RmiServiceExporter將POJO發(fā)布為RMI服務(wù)是不同的。
HessianServiceExporter是一個Spring MVC控制器,它接收Hessian請求雁社,并把這些請求轉(zhuǎn)換成對被POJO的調(diào)用從而將POJO導(dǎo)出為一個Hessian服務(wù)浴井。在如下Spring的聲明中,HessianServiceExporter會把spitterService bean導(dǎo)出為Hessian服務(wù):
@Bean
public HessianServiceExporter hessianExportedSpitterService(SpitterService service) {
HessianServiceExporter exporter = new HessianServiceExporter();
exporter.setService(service);
exporter.setServiceInterface(SpitterService.class);
return exporter;
}
與RmiServiceExporter不同的是霉撵,我們不需要設(shè)置serviceName屬性磺浙。在RMI中,serviceName屬性用來在RMI注冊表中注冊一個服務(wù)徒坡。而Hessian沒有注冊表撕氧,因此也就沒有必要為Hessian服務(wù)進行命名。
配置Hessian控制器
由于Hessian是基于HTTP的喇完,所以HessianServiceExporter實現(xiàn)為一個Spring MVC控制器伦泥。這意味著為了使用導(dǎo)出的Hessian服務(wù),我們需要執(zhí)行兩個額外的配置步驟:
- 在web.xml中配置Spring的DispatcherServlet锦溪,并把我們的應(yīng)用部署為Web應(yīng)用不脯;
- 在Spring的配置文件中配置一個URL處理器,把Hessian服務(wù)的URL分發(fā)給對應(yīng)的Hessian服務(wù)bean刻诊。
首先防楷,我們需要一個DispatcherServlet。這個我們已經(jīng)在Spittr應(yīng)用的web.xml文件中配置了则涯。但是為了處理Hessian服務(wù)复局,DispatcherServlet還需要配置一個Servlet映射來攔截后綴為“*.service”的URL:
<servlet-mapping>
<servlet-name>spitter</servlet-name>
<url-patten>*.service</url-patten>
</servlet-mapping>
如果在 Java中通過實現(xiàn)WebApplicationInitializer來配置DispatcherServlet的話冲簿,那么需要將URL模式作為映射添加到ServletRegistration.Dynamic中,在將DispatcherServlet添加到容器中的時候亿昏,我們能夠得到ServletRegistration.Dynamic對象:
ServletRegistration.Dynamic dispatcher = container.addServlet("appServlet", new DispatcherServlet(dispatcherServletContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
dispatcher.addMapping("*.service");
或者峦剔,如果你通過擴展AbstractDispatcherServletInitializer或AbstractAnnotationConfigDispatcherServletInitializer的方式來配置DispatcherServlet,那么在重載getServletMappings() 的時候角钩,需要包含該映射:
@Override
protected String[] getServletMappings() {
return new String[] {"/" , "*.service"};
}
這樣配置后吝沫,任何以“.service”結(jié)束的URL請求都將由DispatcherServlet處理,它會把請求傳遞給匹配這個URL的控制器彤断。因此“/spitter.service”的請求最終將被hessianSpitterService bean所處理(它實際上僅僅是一個SpitterServiceImpl的代理)野舶。
我們還需要配置一個URL映射來確保DispatcherServlet把請求轉(zhuǎn)給hessianSpitterService。如下的SimpleUrlHandlerMapping bean可以做到這一點:
@Bean
public HandlerMapping hessianMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Properties mappings = new Properties();
mappings.setProperty("/spitter.service",
"hessianExportedSpitterService");
mapping.setMappings(mappings);
return mapping;
}
訪問Hessian服務(wù)
與RMI的客戶端的代碼類似宰衙,客戶端調(diào)用基于Hessian的Spitter服務(wù)可以用如下的配置聲明:
@Bean
public HessianProxyFactoryBean spitterService() {
HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
proxy.setServiceUrl("http://localhost:8080/Spitter/spitter.service");
proxy.setServiceInterface(SpitterService.class);
return proxy;
}
如果想用xml配置,如下:
<bean class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/Spitter/spitter.service"></property>
<property name="serviceInterface" value="client.SpitterServer"></property>
</bean>
serviceInterface屬性指定了這個服務(wù)實現(xiàn)的接口睹欲。并且供炼,serviceUrl標識了這個服務(wù)的URL。既然Hessian是基于HTTP的窘疮,當然在這里要設(shè)置一個HTTP URL(URL是由我們先前定義的URL映射所決定的)袋哼。下圖展示了客戶端以及由HessianProxyFactoryBean所生成的代理之間是如何交互的。
Hessian和Burlap
優(yōu)點:基于HTTP的涛贯,解決了防火墻滲透問題。服務(wù)端和客戶端基本上支持常用語言蔚出。傳輸速度快弟翘。
缺點:Hessian和Burlap采用了私有的序列化機制
RMI
優(yōu)點:RMI使用的是Java本身的序列化機制
缺點:由于不是HTTP協(xié)議,會有防火墻滲透問題骄酗。而且服務(wù)端和客戶端都必須用Java語言稀余。傳輸速度慢。
讓我們看以下Spring的HTTP invoker, 它基于HTTP提供了RPC(像Hessian/Burlap一樣)趋翻,同時又使用了Java的對象序列化機制(像RMI一樣)睛琳。
使用Spring的HttpInvoker
Spring的HttpInvoker 是一個新的遠程調(diào)用模型,作為Spring框架的一部分踏烙,能夠執(zhí)行基于HTTP的遠程調(diào)用(讓防火墻不為難)师骗,并使用Java的序列化機制。
將bean導(dǎo)出為HTTP服務(wù)
為了把Spitter服務(wù)導(dǎo)出為一個基于HTTP invoker的服務(wù)讨惩,我們需要像下面的配置一樣聲明一個HttpInvokerServiceExporter bean:
@Bean
public HttpInvokerServiceExporter httpExportedSpitterService (SpitterService service) {
HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
exporter.setService(service);
exporter.setServiceInterface(SpitterService.class);
return exporter;
}
如下圖所示,HttpInvokerServiceExporter的工作方式與HessianServiceExporter很相似辟癌,它也是一個Spring的MVC控制器,它通過DispatcherServlet接收來自于客戶端的請求步脓,并將這些請求轉(zhuǎn)換成對實現(xiàn)服務(wù)的POJO的方法調(diào)用愿待。
因為HttpInvokerServiceExporter是一個Spring MVC控制器仍侥,我們需要建立一個URL處理器要出,映射HTTP URL到對應(yīng)的服務(wù)上,就像Hessian導(dǎo)出器所做的一樣:
@Bean
public HandlerMapping httpInvokerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Properties mappings = new Properties();
mappings.setProperty("/spitter.service",
"httpExportedSpitterService");
mapping.setMappings(mappings);
return mapping;
}
同樣农渊,我們需要確保匹配了DispatcherServlet患蹂,這樣才能處理對“*.service”擴展的請求。
通過HTTP訪問服務(wù)
如下圖所示砸紊,HttpInvokerProxyFactoryBean填充了相同的位置传于。
為了把基于HTTP invoker的遠程服務(wù)裝配進我們的客戶端Spring應(yīng)用上下文中沼溜,我們必須將HttpInvokerProxyFactoryBean配置為一個bean來代理它,如下所示:
@Bean
public HttpInvokerProxyFactoryBean spitterService(){
HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
proxy.setServiceUrl("http://localhost:8080/Spitter/spitter.service");
proxy.setServiceInterface(SpitterService.class);
return proxy;
}
serviceInterface屬性用來標識Spitter服務(wù)所實現(xiàn)的接口游添,而serviceUrl屬性用來標識遠程服務(wù)的位置系草。
要記住HTTP invoker有一個重大的限制:客戶端和服務(wù)端必須都是Spring應(yīng)用,并且都要基于java唆涝。另外找都,因為使用Java的序列化機制,客戶端與服務(wù)端必須使用相同版本的類(與RMI類似)廊酣。
發(fā)布和使用Web服務(wù)
SOA(面向服務(wù)架構(gòu))的核心理念是能耻,應(yīng)用程序可以并且應(yīng)該被設(shè)計成依賴于一組公共的核心服務(wù),而不是為每個應(yīng)用都重新實現(xiàn)相同的功能亡驰。
Spring為使用Java API for XML Web Service(JAX-WS)來發(fā)布和使用SOAP Web服務(wù)提供了大力支持晓猛。
創(chuàng)建基于Spring的JAX-WS端點
Spring提供了JAX-WS服務(wù)導(dǎo)出器,SimpleJaxWsServiceExporter隐解。但它并不一定是所有場景下的最好選擇鞍帝。SimpleJaxWsServiceExporter要求JAX-WS運行時支持將端點發(fā)布到指定地址上。Sun JDK 1.6自帶的JAX-WS可以符合要求煞茫,但是其他的JAX-WS實現(xiàn)帕涌,包括JAX-WS的參考實現(xiàn),可能并不能滿足此需求续徽。
如果我們將要部署的JAX-WS運行時不支持將其發(fā)布到指定地址上蚓曼,那我們就要以更為傳統(tǒng)的方式來編寫JAX-WS端點。這意味著端點的生命周期由JAX-WS運行時來進行管理钦扭,而不是Spring纫版。但這并不意味著它們不能裝配Spring上下文的bean。
在Spring中自動裝配JAX-WS端點
JAX-WS編程模型使用注解將類和類的方法聲明為Web服務(wù)的操作客情。使用@WebService注解所標注的類被認為Web服務(wù)的端點其弊,而使用@WebMethod注解所標注的方法被認為是操作癞己。
裝配JAX-WD端點的秘密在于繼承SpringBeanAutowiringSupport。通過繼承SpringBeanAutowiringSupport梭伐,我們可以使用@Autowired注解標注端點的屬性痹雅,依賴就會自動注入了。(此方法未驗證)
導(dǎo)出獨立的JAX-WS
SpringSimpleJaxWsServiceExporter的工作方式很類似于其他的服務(wù)導(dǎo)出器糊识。它把Spring管理的bean發(fā)布為JAX-WS運行時中的服務(wù)端點绩社。與其他服務(wù)導(dǎo)出器不同的是,SpringSimpleJaxWsServiceExporter不需要為它指定一個被導(dǎo)出bean的引用赂苗,它會將使用JAX-WS注解所標注的所有bean發(fā)布為JAX-WS服務(wù)愉耙。
SpringSimpleJaxWsServiceExporter可以使用如下的@Bean方法來配置:
@Bean
public SimpleJaxWsServiceExporter jaxWsServiceExporter(){
SimpleJaxWsServiceExporter exporter = new SimpleJaxWsServiceExporter();
exporter.setBaseAddress("http://localhost:8092/services/");
return exporter;
}
當啟動的時候,它會搜索Spring應(yīng)用上下文來查找所有使用@WebService注解的bean拌滋。當找到符合的bean時朴沿,SimpleJaxWsServiceExporter使用“http://localhost:8092/services/”地址將bean發(fā)布為JAX-WS端點的基本地址(也可不設(shè)置,默認為“http://localhost:8080”)败砂。SpitterServiceEndpoint就是其中一個被查找到的bean
package spittr.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import spittr.model.Spittle;
import javax.jws.WebMethod;
import javax.jws.WebService;
@Service // 必須標注悯仙,否則掃描不到
@WebService(serviceName = "spitterService")
public class SpitterServiceEndpoint { //啟動自動配置
@Autowired
SpitterService spitterService; // 自動裝配SpitterService
@WebMethod
public void addSpittle(Spittle spittle){
spitterService.addSpittle(spittle); // 委托給spitterService
}
@WebMethod
public String deleteSpittle(long spittleId) {
return spitterService.deleteSpittle(spittleId); // 委托給spitterService
}
}
我們注意到SpitterServiceEndpoint完全就是一個Spring bean,因此它不需要繼承任何特殊的支持類就可以實現(xiàn)自動裝配。
需要注意的是吠卷,它只能用在支持將端點發(fā)布到指定地址的JAX-WS運行時中。這包含了sun 1.6 JDK自帶的JAX-WS運行時沦零。
在客戶端代理JAX-WS服務(wù)
使用JaxWsProxyFactoryBean祭隔,我們可以在Spring中裝配Spitter Web服務(wù),與任意一個其他的bean一樣路操。JaxWsProxyFactoryBean是Spring工廠bean疾渴,它能生成一個知道如何與SOAP Web服務(wù)交互的代理。所創(chuàng)建的代理實現(xiàn)了服務(wù)接口屯仗,如下圖搞坝。
我們可以像下面這樣配置JaxWsPortProxyFactoryBean來引用Spitter服務(wù):
@Bean
public JaxWsPortProxyFactoryBean spitterService() throws MalformedURLException {
JaxWsPortProxyFactoryBean proxy = new JaxWsPortProxyFactoryBean();
proxy.setWsdlDocumentUrl(new URL("http://localhost:8092/services/spitterService?wsdl"));
proxy.setServiceName("spitterService");
proxy.setPortName("SpitterServiceEndpointPort");
proxy.setServiceInterface(SpitterService.class);
proxy.setNamespaceUri("http://web.spittr/");
return proxy;
}
wdslDocumentUrl屬性標識了遠程Web服務(wù)定義文件的位置桩撮。JaxWsPortProxyFactoryBean將使用這個位置上可用的WSDL來為服務(wù)創(chuàng)建代理。由JaxWsPortProxyFactoryBean所生成的代理實現(xiàn)了serviceInterface屬性所指定的SpitterService接口峰弹。
剩下的三個屬性的值通车炅浚可以通過查看服務(wù)的WSDL來確定。如下所示:
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://www.w3.org/ns/ws-policy"
xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://web.spittr/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://web.spittr/" name="spitterService">
....
<service name="spitterService">
<port name="SpitterServiceEndpointPort"
binding="tns:SpitterServiceEndpointPortBinding">
<soap:address location="http://localhost:8092/services/spitterService"/>
</port>
</service>
</definitions>