目錄
本文將通過具體的遠程服務發布與消費案例展示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文檔的地址


5. 可以看到命令執行完后, 指定的包中出現一堆相關的類, 最直接調用到的類是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
包.
點擊這里查看pom.xml文件, 常規操作
<properties> <!-- spring 依賴 --> <spring.version>4.3.18.RELEASE</spring.version> <jstl.version>1.2</jstl.version> <servlet-api.version>2.5</servlet-api.version> <jsp-api.version>2.0</jsp-api.version> <jackson.version>2.9.0</jackson.version> </properties> <dependencies> <!-- jsp相關依賴 --> <!-- servlet依賴 --> <!-- jstl依賴 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>${jstl.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>${servlet-api.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>${jsp-api.version}</version> <scope>provided</scope> </dependency> <!-- springmvc依賴--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> </dependencies> <build> <finalName>order</finalName> <plugins> <!-- 配置Tomcat插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <path>/order</path> <port>7070</port> </configuration> </plugin> </plugins> </build></code></p>
2. 創建訂單類
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);
}
}