Java實現遠端服務生產與消費(RPC)的4種方法-RMI,WebService,HttpClient,RestTemplate
本文將通過具體的遠端服務釋出與消費案例展示4種RPC遠端呼叫方法.
一. 通過rmi實現遠端服務的生產與消費
- Java自身提供了
java.rmi
包, 方便開發者進行遠端服務的部署與消費, 下面將通過具體案例進行講解.
遠端服務提供者實現.
建立rmi-provider專案(Maven)
- 建立
UserService
介面.
//將要釋出的服務的介面 public interface UserService extends Remote { public String helloRmi(String name) throws RemoteException; }
- 建立
UserServiceImpl
實現類
- 注意,
UserServiceImpl
除了實現UserService
介面外, 還要繼承UnicastRemoteObject
類, 你可以理解為它是一個釋出出去供他人呼叫的類, 當UserServiceImpl
實現了這個類後,UserServiceImpl
就能被髮布出去供別人呼叫.
//將要釋出的服務的實現類 public class UserServiceImpl extends UnicastRemoteObject implements UserService { public UserServiceImpl() throws RemoteException { super(); } public String helloRmi(String name) throws RemoteException { return "hello " + name; } }
- 釋出遠端服務
public static void main(String[] args) { try { //完成遠端服務的釋出 LocateRegistry.createRegistry(8888);//將遠端服務釋出在本地的8888埠 String name = "rmi://localhost:8888/rmi";//釋出的遠端服務被訪問的url UserService userService = new UserServiceImpl();//建立一個提供具體服務的遠端物件 Naming.bind(name, userService);//給遠端服務繫結一個url System.out.println("--- 已釋出rmi遠端服務 ---"); } catch (Exception e) { e.printStackTrace(); } }
遠端服務消費者實現
建立rmi-consumer專案
- 把
rmi-provider
專案種的UserService
介面與UserServiceImpl
實現類複製到本rmi-consumer
專案中.(這一步可以進行優化解耦, 我們可以多建立一個rmi-resource
專案, 讓rmi-provider
和rmi-consumer
共同依賴rmi-resource
專案, 然後把資原始檔比如遠端服務所用到的UserService
等放入rmi-resource
專案中) - 遠端服務消費者對遠端服務發起呼叫.
public static void main(String[] args) { try { //釋出遠端服務的訪問url String name = "rmi://localhost:8888/rmi"; //通過釋出遠端服務的url, 獲取遠端服務的代理物件 UserService userService = (UserService) Naming.lookup(name); System.out.println("獲得的遠端服務的代理物件:" + userService.getClass().getName()); String result = userService.helloRmi("rmi");//拿到遠端方法呼叫的結果 System.out.println("result: " + result); }catch (Exception e) { e.printStackTrace(); } } //最後輸出 獲得的遠端服務的代理物件:com.sun.proxy.$Proxy0 result: hello rmi
- 通過最後的輸出我們看到獲得的遠端服務物件是動態代理產生的.
二. 通過WebService實現遠端服務的生產與消費
- WebService協議是RPC的一種具體實現, 服務提供方和消費方通過
http + xml
進行通訊.
遠端服務提供者實現.
- 首先建立遠端服務介面
UserService
及其實現類UserServiceImpl
.
- 注意, 使用
WebService
時需要對遠端服務加上註解@WebService
@WebService public interface UserService { public String sayHello(String name); } @WebService public class UserServiceImpl implements UserService { @Override public String sayHello(String name) { return "hello " + name + "~"; } }
- 釋出遠端服務, 過程和
rmi
差不多, 需要提供遠端服務的訪問地址和具體的遠端服務實現類, 使用Endpoint
類的publish()
方法進行釋出, 這都是JDK封裝好的.
public class WsProviderApp { public static void main(String[] args) { //釋出的WebService的被訪問地址 String address = "http://localhost:9999/ws"; //建立遠端服務物件 UserService userService = new UserServiceImpl(); //釋出服務 Endpoint.publish(address, userService); System.out.println("遠端服務已經發布..."); } }
檢視遠端服務文件wdsl
- 和
rmi
不同的是, WebService釋出後, 呼叫者可以通過檢視它的文件對遠端服務發起呼叫. - 檢視的方法是在瀏覽器中輸入遠端服務的訪問地址加上
?wdsl
, 比如本案例中是http://localhost:9999/ws?wsdl
- 注意, 在客戶端呼叫遠端方法時需要用工具對wdsl文件進行解析, 並獲得呼叫遠端方法的工具類. 具體操作見下一段.
遠端服務消費者實現.
- 首先根據文件獲得呼叫遠端服務的工具類, JDK已經為我們封裝好了獲取的工具, 它在
bin
目錄下, 名字是wsimport
- 開啟命令列, 在命令列中輸入解析命令
wsimport -keep -d C:\githubRepositories\shopping\ws-consumer\src\main\java -p com.shenghao.client http://localhost:9999/ws?wsdl 解釋: 1. wsimport 是命令的名字 2. -keep 用於保留生成的類, 如果沒有該指令會只生成class檔案 3. -d 後面接專案中存放這些工具類的包, 填絕對路徑 4. -p 填wdsl文件的地址
- 可以看到命令執行完後, 指定的包中出現一堆相關的類, 最直接呼叫到的類是
UserServiceImplService
. 下面演示對遠端方法進行呼叫.
public static void main(String[] args) { //建立服務類物件 UserServiceImplService service = new UserServiceImplService(); //獲得遠端服務的代理物件 UserServiceImpl userService = service.getUserServiceImplPort(); System.out.println(userService.getClass().getName()); //對遠端服務物件的方法進行呼叫 String result = userService.sayHello("炭燒生蠔"); System.out.println(result); } //結果輸出 com.sun.proxy.$Proxy32 hello 炭燒生蠔~
三. 通過HttpClient實現遠端服務的生產與消費
- 這裡我們換一個案例進行演示. 假設現在有一套使用者系統和一套訂單系統, 要實現使用者系統訪問訂單系統以獲得某個使用者的訂單資訊.
遠端服務提供者實現
- 提供遠端服務的過程和響應web請求很相似, 只不過響應的不是
<html>
標籤, 而是json
字串. 微信小程式前後端通訊也是這個原理.
- 建立名為
order-sys
的Maven專案, 指定打包為war
包.
- 建立訂單類
public class Order { private String id; private Double total; private String date; //get / set ... }
- 對外提供服務, 釋出時打包釋出到
Tomcat
上
@Controller public class OrderController { /** * 接收http請求, 響應訂單集合, 非同步響應 * 將list集合序列化為json串響應 * @param uid * @return */ @RequestMapping("/loadOrderList2") @ResponseBody public List<Order> loadOrderList2(String uid){ System.out.println("uid: " + uid); //模擬訂單資料 Order o1 = new Order(); o1.setId("111"); o1.setTotal(333.33); o1.setDate("2019-4-29"); Order o2 = new Order(); o2.setId("222"); o2.setTotal(444.44); o2.setDate("2019-5-29"); Order o3 = new Order(); o3.setId("333"); o3.setTotal(555.55); o3.setDate("2019-6-29"); List<Order> list = new ArrayList<>(); list.add(o1); list.add(o2); list.add(o3); return list; } }
遠端服務消費者實現
- 在服務消費端使用
HttpClient
傳送請求, 可以理解為模擬瀏覽器傳送post/get請求.HttpClient
為我們封裝了拼接一個請求的細節, 使得傳送一個請求變得容易.
public static void main(String[] args) throws IOException { //傳送遠端的http請求的地址 String url = "http://localhost:7070/order/loadOrderList2"; //建立HttpClient物件 CloseableHttpClient client = HttpClients.createDefault(); //建立HttpPost物件, 傳送post請求 HttpPost method = new HttpPost(url); //封裝傳送到服務提供者的引數 NameValuePair id = new BasicNameValuePair("uid", "10001"); List<NameValuePair> params = new ArrayList<>(); params.add(id); //封裝請求體資料 method.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); //傳送具體的http請求 HttpResponse response = client.execute(method); //獲得服務提供者響應的具體資料 HttpEntity entity = response.getEntity(); //獲得http的響應體 InputStream is = entity.getContent(); int len = 0; char[] buf = new char[1024]; //使用字元流讀 InputStreamReader reader = new InputStreamReader(is); StringBuffer sb = new StringBuffer(); while((len = reader.read(buf)) != -1){ sb.append(String.valueOf(buf, 0, len)); } System.out.println(sb); //將響應回來的json字串解析為Order集合 List<Order> list = JSON.parseArray(sb.toString(), Order.class); for(Order o : list){ System.out.println(o.getId() + "\t" + o.getTotal() + "\t" + o.getDate()); } }
四. 通過spring提供的RestTemplate實現遠端服務的生產與消費
- 通過一個紅包系統和訂單系統進行演示, 紅包系統訪問訂單系統, 獲得某個使用者的訂單資訊, 派發紅包.
- 訂單系統繼續沿用
HttpClient
中的訂單系統, 通過訪問loadOrderList2
方法能返回一個訂單集合Json字串.
遠端服務消費者實現.
@Controller public class RedController { //注入由spring提供的RestTemplate物件 @Autowired private RestTemplate restTemplate; /** * 傳送遠端的http請求, 消費http服務 * 獲得訂單物件的集合 */ @RequestMapping("/loadOrderList3") @ResponseBody public List<ResponseEntity<Order[]>> loadOrderList3(String uid){ //傳送遠端http請求的url String url = "http://localhost:7070/order/loadOrderList2"; //傳送到遠端服務的引數 MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); params.add("uid", uid); //通過RestTemplate物件傳送post請求 ResponseEntity<Order[]> entitys = restTemplate.postForEntity(url, params, Order[].class); //檢視響應的狀態碼 System.out.println(entitys.getStatusCodeValue()); //檢視響應頭 HttpHeaders headMap = entitys.getHeaders(); for(Map.Entry<String, List<String>> m : headMap.entrySet()){ System.out.println(m.getKey() + ": " + m.getValue()); } return Arrays.asList(entitys); } }