java RPC 初步了解
首先要了解一個(gè)概念:wsdl 協(xié)議 web service description language
使用wsdl 要定義一個(gè)接口,一個(gè)服務(wù);目前常用的就是xml 描述,類(lèi)似java中 jax-ws?
WSDL 元素? 基于XML語(yǔ)法描述了與服務(wù)進(jìn)行交互的基本元素:
Type(消息類(lèi)型):數(shù)據(jù)類(lèi)型定義的容器,它使用某種類(lèi)型系統(tǒng)(如 XSD)。
Message(消息):通信數(shù)據(jù)的抽象類(lèi)型化定義,它由一個(gè)或者多個(gè) part 組成。
Part:消息參數(shù)
Operation(操作):對(duì)服務(wù)所支持的操作進(jìn)行抽象描述,WSDL定義了四種操作: 1.單向(one-way):端點(diǎn)接受信息;2.請(qǐng)求-響應(yīng)(request-response):端點(diǎn)接受消息,然后發(fā)送相關(guān)消息;3.要求-響應(yīng)(solicit-response):端點(diǎn)發(fā)送消息,然后接受相關(guān)消息;4.通知(notification ):端點(diǎn)發(fā)送消息。
Port Type(端口類(lèi)型):特定端口類(lèi)型的具體協(xié)議和數(shù)據(jù)格式規(guī)范。
Binding:特定端口類(lèi)型的具體協(xié)議和數(shù)據(jù)格式規(guī)范。
Port:定義為綁定和網(wǎng)絡(luò)地址組合的單個(gè)端點(diǎn)。
Service:相關(guān)端口的集合,包括其關(guān)聯(lián)的接口、操作、消息等。
-------------------------------------------------------------------------------------------------------
JAX-WS(Java API for XML Web Services)規(guī)范是一組XML web services的JAVA API,JAX-WS允許開(kāi)發(fā)者可以選擇RPC-oriented或者message-oriented 來(lái)實(shí)現(xiàn)自己的web services。
SOAP 協(xié)議:簡(jiǎn)單對(duì)象訪問(wèn)協(xié)議? simple object access protocol.
一個(gè) SOAP 實(shí)例
在下面的例子中,一個(gè) GetStockPrice 請(qǐng)求被發(fā)送到了服務(wù)器。此請(qǐng)求有一個(gè) StockName 參數(shù),而在響應(yīng)中則會(huì)返回一個(gè) Price 參數(shù)。此功能的命名空間被定義在此地址中: "http://www.example.org/stock"
SOAP 請(qǐng)求:
POST /InStock HTTP/1.1Host: www.example.org
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Body xmlns:m="http://www.example.org/stock">
? <m:GetStockPrice>
??? <m:StockName>IBM</m:StockName>
? </m:GetStockPrice>
</soap:Body>
</soap:Envelope>
SOAP 響應(yīng):
HTTP/1.1 200 OKContent-Type: application/soap+xml; charset=utf-8
Content-Length: nnn
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Body xmlns:m="http://www.example.org/stock">
? <m:GetStockPriceResponse>
??? <m:Price>34.5</m:Price>
? </m:GetStockPriceResponse>
</soap:Body>
</soap:Envelope>
使用JAX-WS(JWS)發(fā)布WebService,實(shí)現(xiàn)輕量級(jí)WebService框架--demo
我們使用JAX-WS開(kāi)發(fā)WebService只需要很簡(jiǎn)單的幾個(gè)步驟:寫(xiě)接口和實(shí)現(xiàn)=>發(fā)布=>生成客戶(hù)端(測(cè)試或使用)。
而在開(kāi)發(fā)階段我們也不需要導(dǎo)入外部jar包,因?yàn)檫@些api都是現(xiàn)成的。首先是接口的編寫(xiě)(接口中只需要把類(lèi)注明為@WebService,把 要暴露給客戶(hù)端的方法注明為@WebMethod即可,其余如@WebResult、@WebParam等都不是必要的,而客戶(hù)端和服務(wù)端的通信用RPC 和Message-Oriented兩種,區(qū)別和配置以后再說(shuō)):
package service;import java.util.Date;import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.jws.soap.SOAPBinding;/*** 作為測(cè)試的WebService接口* * @author Johness* */ @WebService @SOAPBinding(style = SOAPBinding.Style.RPC) public interface SayHiService {/*** 執(zhí)行測(cè)試的WebService方法*/@WebMethodvoid SayHiDefault();/*** 執(zhí)行測(cè)試的WebService方法(有參)* * @param name*/@WebMethodvoid SayHi(@WebParam(name = "name") String name);/*** 執(zhí)行測(cè)試的WebService方法(用于時(shí)間校驗(yàn))* * @param clentTime 客戶(hù)端時(shí)間* @return 0表示時(shí)間校驗(yàn)失敗 1表示校驗(yàn)成功*/@WebMethod@WebResult(name = "valid")int CheckTime(@WebParam(name = "clientTime") Date clientTime); } package service.imp;import java.text.SimpleDateFormat; import java.util.Date;import javax.jws.WebService; import javax.jws.soap.SOAPBinding;import service.SayHiService;/*** 作為測(cè)試的WebService實(shí)現(xiàn)類(lèi)* * @author Johness* */ @WebService(endpointInterface = "service.SayHiService") @SOAPBinding(style = SOAPBinding.Style.RPC) public class SayHiServiceImp implements SayHiService {@Overridepublic void SayHiDefault() {System.out.println("Hi, Johness!");}@Overridepublic void SayHi(String name) {System.out.println("Hi, " + name + "!");}@Overridepublic int CheckTime(Date clientTime) {// 精確到秒String dateServer = new java.sql.Date(System.currentTimeMillis()).toString()+ " "+ new java.sql.Time(System.currentTimeMillis());String dateClient = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(clientTime);return dateServer.equals(dateClient) ? 1 : 0;}}然后是發(fā)布(一般有兩種方式):
方式一(此方式只能作為調(diào)試,有以下bug:
jdk1.6u17?以下編譯器不支持以Endpoint.publish方式發(fā)布document方式的soap,必須在service接口和實(shí)現(xiàn)類(lèi)添加“@SOAPBinding(style = SOAPBinding.Style.RPC)”注解;訪問(wèn)受限,似乎只能本機(jī)訪問(wèn)(應(yīng)該會(huì)綁定到publish的URL上,如下使用localhost的話(huà)就只能本機(jī)訪問(wèn))……):
package mian;import javax.xml.ws.Endpoint;import service.imp.SayHiServiceImp;public class Main {/*** 發(fā)布WebService* 簡(jiǎn)單*/public static void main(String[] args) {Endpoint.publish("http://localhost:8080/testjws/service/sayHi", new SayHiServiceImp());}}方式二(基于web服務(wù)器Servlet方式):
以Tomcat為例,首先編寫(xiě)sun-jaxws.xml文件并放到WEB-INF下:
<?xml version="1.0" encoding="UTF-8"?> <endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"version="2.0"><endpoint name="SayHiService"implementation="service.imp.SayHiServiceImpl"url-pattern="/service/sayHi" /> </endpoints>然后改動(dòng)web.xml,添加listener和servlet(url-pattern要相同):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"><listener> <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener </listener-class></listener><servlet><servlet-name>SayHiService</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet </servlet-class></servlet> <servlet-mapping> <servlet-name>SayHiService</servlet-name> <url-pattern>/service/sayHi</url-pattern> </servlet-mapping><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file></welcome-file-list> </web-app>最后部署到Tomcat里,值得一提的是您可能需要添加以下jar包(因?yàn)門(mén)omcat沒(méi)有):
啟動(dòng)Tomcat。
服務(wù)端工作就完成了,注意兩個(gè)事情。
注意:項(xiàng)目需要使用UTF-8編碼(至少sun-jaxws.xml必須是UTF-8格式的);
對(duì)于MyEclipse的內(nèi)置Tomcat,可能會(huì)出現(xiàn)不需要手動(dòng)添加上述jar包,但獨(dú)立部署時(shí)應(yīng)該添加,因?yàn)樗鼈兪褂玫腸lass-path不一樣;
多個(gè)不同路徑的接口也要使用同一個(gè)WSServlet;
最好加上@SOAPBinding(style = SOAPBinding.Style.RPC)注解。
部署好了之后打開(kāi)瀏覽器輸入網(wǎng)址:http://localhost:8080/testjws/service/sayHi?wsdl。可以看到東西就證明發(fā)布成功了。
附上項(xiàng)目樹(shù)狀圖:
最后是客戶(hù)端使用,由于WebService是平臺(tái)和語(yǔ)言無(wú)關(guān)的基于xml的,所以我們完全可以使用不同語(yǔ)言來(lái)編寫(xiě)或生成客戶(hù)端。
一般有三種方式來(lái)使用(對(duì)于Java語(yǔ)言而言):
一,使用jdk自帶工具wsimport生成客戶(hù)端:
jdk自帶的wsimport工具生成,上圖我是把客戶(hù)端文件生成到了桌面src文件中(-d),并保留了源文件(-keep),指定了包名(-p)。
然后我們就可以使用生成的文件來(lái)調(diào)用服務(wù)器暴露的方法了:
值得一提的是你生成使用的jdk和你客戶(hù)端的jre需要配套!
從上面的目錄結(jié)構(gòu)我們可以發(fā)現(xiàn):服務(wù)端的每個(gè)webmethod都被單獨(dú)解析成為了一個(gè)類(lèi)(如果使用了實(shí)體,實(shí)體也會(huì)被解析到客戶(hù)端,并且是源碼,所以建議使用實(shí)體時(shí)慎重)。
(上面的圖是舊圖,只是為了表示一下jaxws是為每個(gè)webmethod生成類(lèi)的情況)
而我們的service則被生成了一個(gè)代理類(lèi)來(lái)調(diào)用服務(wù),接下來(lái)我們看看使用情況:
package test;import java.util.Date; import java.util.GregorianCalendar;import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar;import testjws.client.SayHiService; import testjws.client.SayHiServiceImpService;public class Main {public static void main(String[] args) throws DatatypeConfigurationException {// 獲取serviceSayHiService service = new SayHiServiceImpService().getSayHiServiceImpPort();// sayhiservice.sayHiDefault();service.sayHi("Ahe");// checktime// 這里主要說(shuō)一下時(shí)間日期的xml傳遞,方法還略顯復(fù)雜GregorianCalendar calender = new GregorianCalendar();calender.setTime(new Date(System.currentTimeMillis()));XMLGregorianCalendar xmldate = DatatypeFactory.newInstance().newXMLGregorianCalendar(calender);System.out.println(service.checkTime(xmldate));}}看看服務(wù)器的輸出,我們是否調(diào)用成功:
成功了!
對(duì)于校驗(yàn)時(shí)間的方法客戶(hù)端也收到反饋了:
二,使用諸如MyEclipse(Eclipse for Jave EE也可以)創(chuàng)建一個(gè)Web Service Client的項(xiàng)目
然后填入wsdl地址即可,后續(xù)步驟我就不貼出了。
-----------------------------------------------------------------------------------------------------------
以java rmi? 方式實(shí)現(xiàn)rpc
RMI遠(yuǎn)程調(diào)用步驟:
1,客戶(hù)對(duì)象調(diào)用客戶(hù)端輔助對(duì)象上的方法
2,客戶(hù)端輔助對(duì)象打包調(diào)用信息(變量,方法名),通過(guò)網(wǎng)絡(luò)發(fā)送給服務(wù)端輔助對(duì)象
3,服務(wù)端輔助對(duì)象將客戶(hù)端輔助對(duì)象發(fā)送來(lái)的信息解包,找出真正被調(diào)用的方法以及該方法所在對(duì)象
4,調(diào)用真正服務(wù)對(duì)象上的真正方法,并將結(jié)果返回給服務(wù)端輔助對(duì)象
5,服務(wù)端輔助對(duì)象將結(jié)果打包,發(fā)送給客戶(hù)端輔助對(duì)象
6,客戶(hù)端輔助對(duì)象將返回值解包,返回給客戶(hù)對(duì)象
7,客戶(hù)對(duì)象獲得返回值
對(duì)于客戶(hù)對(duì)象來(lái)說(shuō),步驟2-6是完全透明的
1、創(chuàng)建遠(yuǎn)程方法接口,該接口必須繼承自Remote接口
Remote 接口是一個(gè)標(biāo)識(shí)接口,用于標(biāo)識(shí)所包含的方法可以從非本地虛擬機(jī)上調(diào)用的接口,Remote接口本身不包含任何方法
package server; import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { public String sayHello(String name) throws RemoteException; }由于遠(yuǎn)程方法調(diào)用的本質(zhì)依然是網(wǎng)絡(luò)通信,只不過(guò)隱藏了底層實(shí)現(xiàn),網(wǎng)絡(luò)通信是經(jīng)常會(huì)出現(xiàn)異常的,所以接口的所有方法都必須拋出RemoteException以說(shuō)明該方法是有風(fēng)險(xiǎn)的
2、創(chuàng)建遠(yuǎn)程方法接口實(shí)現(xiàn)類(lèi):
UnicastRemoteObject類(lèi)的構(gòu)造函數(shù)拋出了RemoteException,故其繼承類(lèi)不能使用默認(rèn)構(gòu)造函數(shù),繼承類(lèi)的構(gòu)造函數(shù)必須也拋出RemoteException
由于方法參數(shù)與返回值最終都將在網(wǎng)絡(luò)上傳輸,故必須是可序列化的
package server; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements Hello { private static final long serialVersionUID = -271947229644133464L; public HelloImpl() throws RemoteException{ super(); } public String sayHello(String name) throws RemoteException { return "Hello,"+name; } } 3、利用java自帶rmic工具生成sutb存根類(lèi)(jdk1.5.0_15/bin/rmic)jdk1.2以后的RMI可以通過(guò)反射API可以直接將請(qǐng)求發(fā)送給真實(shí)類(lèi),所以不需要skeleton類(lèi)了
sutb存根為遠(yuǎn)程方法類(lèi)在本地的代理,是在服務(wù)端代碼的基礎(chǔ)上生成的,需要HelloImpl.class文件,由于HelloImpl繼承了Hello接口,故Hello.class文件也是不可少的
Test
- - server
- - - - Hello.class
- - - - HelloImpl.class
方式一:
[name@name Test]$ cd /home/name/Test/ [name@name Test]$ rmic server.HelloImpl方式二:
[name@name Test]$ rmic -classpath /home/name/Test server.HelloImpl?運(yùn)行成功后將會(huì)生成HelloImpl_Stub.class文件
4、啟動(dòng)RMI注冊(cè)服務(wù)(jdk1.5.0_15/bin/rmiregistry)
方式一:后臺(tái)啟動(dòng)rmiregistry服務(wù)
[name@name jdk]$ jdk1.5.0_15/bin/rmiregistry 12312 & [1] 22720 [name@name jdk]$ ps -ef|grep rmiregistry name 22720 13763 0 16:43 pts/3 00:00:00 jdk1.5.0_15/bin/rmiregistry 12312 name 22737 13763 0 16:43 pts/3 00:00:00 grep rmiregistry如果不帶具體端口號(hào),則默認(rèn)為1099
方式二:人工創(chuàng)建rmiregistry服務(wù),需要在代碼中添加:
LocateRegistry.createRegistry(12312); 5、編寫(xiě)服務(wù)端代碼package server; import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class HelloServer { public static void main(String[] args) { try{ Hello h = new HelloImpl(); //創(chuàng)建并導(dǎo)出接受指定port請(qǐng)求的本地主機(jī)上的Registry實(shí)例。 //LocateRegistry.createRegistry(12312); /** Naming 類(lèi)提供在對(duì)象注冊(cè)表中存儲(chǔ)和獲得遠(yuǎn)程對(duì)遠(yuǎn)程對(duì)象引用的方法 * Naming 類(lèi)的每個(gè)方法都可將某個(gè)名稱(chēng)作為其一個(gè)參數(shù), * 該名稱(chēng)是使用以下形式的 URL 格式(沒(méi)有 scheme 組件)的 java.lang.String: * //host:port/name * host:注冊(cè)表所在的主機(jī)(遠(yuǎn)程或本地),省略則默認(rèn)為本地主機(jī) * port:是注冊(cè)表接受調(diào)用的端口號(hào),省略則默認(rèn)為1099,RMI注冊(cè)表registry使用的著名端口 * name:是未經(jīng)注冊(cè)表解釋的簡(jiǎn)單字符串 */ //Naming.bind("//host:port/name", h); Naming.bind("rmi://192.168.58.164:12312/Hello", h); System.out.println("HelloServer啟動(dòng)成功"); }catch(Exception e){ e.printStackTrace(); } } }
先創(chuàng)建注冊(cè)表,然后才能在注冊(cè)表中存儲(chǔ)遠(yuǎn)程對(duì)象信息
6、運(yùn)行服務(wù)端(58.164):
Test
- - server
- - - - Hello.class
- - - - HelloImpl.class
- - - - HelloServer.class
[name@name ~]$ java server.HelloServer HelloServer啟動(dòng)成功當(dāng)然/home/name/Test一定要在系統(tǒng)CLASSPATH中,否則會(huì)報(bào)找不到相應(yīng)的.class文件
7、編寫(xiě)客戶(hù)端代碼
package client; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import server.Hello; public class HelloClient { public static void main(String[] args) { try { Hello h = (Hello)Naming.lookup("rmi://192.168.58.164:12312/Hello"); System.out.println(h.sayHello("zx")); } catch (MalformedURLException e) { System.out.println("url格式異常"); } catch (RemoteException e) { System.out.println("創(chuàng)建對(duì)象異常"); e.printStackTrace(); } catch (NotBoundException e) { System.out.println("對(duì)象未綁定"); } }8、運(yùn)行客戶(hù)端(58.163):
Test
- - client
- - - - HelloClient.class
- - server
- - - - Hello.class
- - - - HelloImpl_Stub.class//服務(wù)端生成的存根文件
[name@name client]$ java client.HelloClient Hello,zx同服務(wù)器端,/home/name/Test一定要在系統(tǒng)CLASSPATH中
PS:
1、客戶(hù)端所在服務(wù)和服務(wù)端所在的服務(wù)器網(wǎng)絡(luò)一定要通(一開(kāi)始浪費(fèi)了很多時(shí)間,最后才發(fā)現(xiàn)是網(wǎng)絡(luò)不通)
2、所有代碼在jdk1.5.0_15,Linux服務(wù)器上調(diào)試通過(guò)
3、如果java命令運(yùn)行提示找不到類(lèi)文件,則為CLASSPATH配置問(wèn)題
[name@name ~]$ vi .bash_profile JAVA_HOME=/home/name/jdk/jdk1.5.0_15 export JAVA_HOME PATH=$JAVA_HOME/bin:$PATH export PATH CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:/home/name/Test export CLASSPATHJAVA_HOME為jdk的根目錄
PATH為java工具類(lèi)路徑(java,javac,rmic等)
CLASSPATH為java?.class文件的存放路徑,使用java命令運(yùn)行.class文件時(shí)即會(huì)在該參數(shù)配置的路徑下尋找相應(yīng)文件
java RMI的缺點(diǎn):
1、從代碼中也可以看到,代碼依賴(lài)于ip與端口
2、RMI依賴(lài)于Java遠(yuǎn)程消息交換協(xié)議JRMP(Java Remote Messaging Protocol),該協(xié)議為java定制,要求服務(wù)端與客戶(hù)端都為java編寫(xiě)
總結(jié)
以上是生活随笔為你收集整理的java RPC 初步了解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 领导干部违规插手干预工程项目问题治理方案
- 下一篇: Maven的Archetype简介