Spring實戰(zhàn)(十五)-使用遠程服務(wù)

本文基于《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的屬性中去首装。下圖展示了它是如何工作的。

在Spring中杭跪,遠程服務(wù)被代理仙逻,所以它們能夠像其他Spring 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ù)倦西。

使用遠程導(dǎo)出器將Spring管理的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ù)哗脖,會涉及如下幾個步驟:

  1. 編寫一個服務(wù)實現(xiàn)類,類中的方法必須拋出java.rmi.RemoteException異常扳还;
  2. 創(chuàng)建一個繼承于java.rmi.Remote的服務(wù)接口才避;
  3. 運行RMI編譯器(rmic),創(chuàng)建客戶端stub類和服務(wù)端skeleton類氨距;
  4. 啟動一個RMI注冊表桑逝,以便持有這些服務(wù);
  5. 在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把POJO包裝到服務(wù)適配器中蟋定,并將服務(wù)適配器綁定到RMI注冊表中,從而將POJO轉(zhuǎn)換為RMI服務(wù)

使用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ù)啟用不成功恨闪。

解決方法有兩種

  1. 在代碼中譬正,啟用服務(wù)前添加如下語句
System.setProperty("java.rmi.server.hostname", "192.168.68.115");

"192.168.68.115"表示當前IP也切,可以是局域網(wǎng)地址杜漠,也可以是外網(wǎng)地址

推薦本地調(diào)用時使用

  1. 如果是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代理和交互嗜湃。

RmiProxyFactoryBean生成一個代理對象,該對象代表客戶端來負責與遠程的RMI服務(wù)進行通信瓤荔【辉椋客戶端通過服務(wù)的接口與代理進行交互,就如同遠程服務(wù)就是一個本地的POJO

現(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ù)

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所生成的代理之間是如何交互的。

HessianProxyFactoryBean和BurlapProxyFactoryBean生成的代理對象負責通過HTTP(Hessian為二進制闸衫、Burlap為XML)與遠程對象通信

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工作方式與Hessian和Burlap很相似浩螺,通過Spring MVC的DispatcherServlet接收請求,并將這些請求轉(zhuǎn)換成對Spring bean的方法調(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填充了相同的位置传于。

HttpInvokerProxyFactoryBean是一個代理工廠bean,用于生成一個代理醉顽,該代理使用Spring特有的基于HTTP協(xié)議進行遠程通信

為了把基于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ù)接口屯仗,如下圖搞坝。

JaxWsProxyFactoryBean生成可以與遠程Web服務(wù)交互的代理。這些代理可以被裝配到其他bean中魁袜,就像它們是本地POJO一樣

我們可以像下面這樣配置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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鞠呈,一起剝皮案震驚了整個濱河市融师,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚁吝,老刑警劉巖旱爆,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舀射,死亡現(xiàn)場離奇詭異,居然都是意外死亡怀伦,警方通過查閱死者的電腦和手機脆烟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來空镜,“玉大人浩淘,你說我怎么就攤上這事∥庠埽” “怎么了张抄?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洼怔。 經(jīng)常有香客問我署惯,道長,這世上最難降的妖魔是什么镣隶? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任极谊,我火速辦了婚禮,結(jié)果婚禮上安岂,老公的妹妹穿的比我還像新娘轻猖。我一直安慰自己,他們只是感情好域那,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布咙边。 她就那樣靜靜地躺著,像睡著了一般次员。 火紅的嫁衣襯著肌膚如雪败许。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天淑蔚,我揣著相機與錄音市殷,去河邊找鬼。 笑死刹衫,一個胖子當著我的面吹牛醋寝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绪妹,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼甥桂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了邮旷?” 一聲冷哼從身側(cè)響起黄选,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后办陷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體貌夕,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年民镜,在試婚紗的時候發(fā)現(xiàn)自己被綠了啡专。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡制圈,死狀恐怖们童,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鲸鹦,我是刑警寧澤慧库,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站馋嗜,受9級特大地震影響齐板,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葛菇,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一甘磨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眯停,春花似錦济舆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至九府,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間覆致,已是汗流浹背侄旬。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留煌妈,地道東北人儡羔。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像璧诵,于是被迫代替她去往敵國和親汰蜘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理之宿,服務(wù)發(fā)現(xiàn)族操,斷路器,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評論 6 342
  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器,...
    simoscode閱讀 6,702評論 2 22
  • 1.1 Spring IoC容器和bean簡介 本章介紹了Spring Framework實現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,578評論 0 8
  • 有個叫成人的地方色难,這里的居民都很奇怪泼舱,自18歲之后,無論去哪里枷莉,身后總會跟著一根針娇昙。 每個人的針都不太一樣,有的銳...
    我就是不愛說話的S閱讀 438評論 3 5