【如何實作一個簡單的RPC架構】系列文章:
【遠端調用架構】如何實作一個簡單的RPC架構(一)想法與設計
【遠端調用架構】如何實作一個簡單的RPC架構(二)實作與使用
【遠端調用架構】如何實作一個簡單的RPC架構(三)優化一:利用動态代理改變使用者服務調用方式
【遠端調用架構】如何實作一個簡單的RPC架構(四)優化二:改變底層通信架構
【遠端調用架構】如何實作一個簡單的RPC架構(五)優化三:軟負載中心設計與實作
第一個優化以及第二個優化修改後的工程代碼可下載下傳資源 如何實作一個簡單的RPC架構
- 服務注冊查找中心
- 1 接口設計
- 11 服務注冊接口
- 12 服務ip位址清單查詢接口
- 13 服務資訊查詢接口
- 2 類設計
- 21 UML類圖
- 22 核心代碼
- 1 接口設計
- LCRPC服務架構核心部分
- 1 服務架構UML類圖
- 2 服務調用部分
- 3 服務釋出部分
- LCRPC服務架構使用
- 1 服務釋出
- 2 服務調用
- 3 效果
參考【遠端調用架構】如何實作一個簡單的RPC架構(一)想法與設計,對應四個子產品一共建立了四個Java工程,他們分别是:
- 【ServiceAccessCenter】:一個Java Web應用,服務注冊查找中心
- 【LCRPC】:LCRPC服務架構的核心部分,最終利用Maven生成一個jar包提供服務釋出以及遠端調用功能
- 【LCRPCTest】:服務端的測試部分,釋出一個電腦服務
- 【LCRPCClientTest】:用戶端的測試部分,利用服務釋出者提供的二方包,遠端調用該電腦服務
1. 服務注冊查找中心
一個Java Web應用,服務注冊查找中心
1.1 接口設計
1.1.1 服務注冊接口
- (1)url:localhost:8080/ServiceAccessCenter/serviceRegistry.do POST請求
- (2)參數:JSON格式的字元串,如下:
{
"interfaceName":"interfaceName",
"version":"version",
"implClassName":"imlClassName",
"ip":"ip"
}
(3)響應結果:true(代表注冊成功)false(代表注冊失敗)
1.1.2 服務ip位址清單查詢接口
-
(1)url:
localhost:8080/ServiceAccessCenter/queryServiceIPsByID.do?serviceID=interfaceName_version GET請求
- (2)參數:serviceID 服務的唯一辨別
- (3)響應結果:該服務對應的ip清單數組/空字元串,如下:
["ip","ip"]
1.1.3 服務資訊查詢接口
-
(1)url:
localhost:8080/ServiceAccessCenter/queryServiceInfoByID.do?serviceID=interfaceName_version GET請求
- (2)參數:serviceID 服務的唯一辨別
- (3)響應結果:該服務的所有資訊,如下:
{"interfaceName":"interfaceName","version":"version","implClassName":"imlClassName","ips":["ip","ip"],"ip":"ip"}
1.2 類設計
1.2.1 UML類圖
1.2.2 核心代碼
核心代碼主要是對注冊服務集合的管理,包括增加以及查詢。注意多線程操作的問題。
- (1)描述服務資訊的DO:ServiceInfoDO
package whu.edu.lcrpc.servicecenter.entity;
/**
* Created by apple on 17/3/26.
*/
import lombok.Data;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 服務的描述資訊,包括:
* 服務唯一辨別\實作類全限定名\ip位址清單等
*/
@Data
public class ServiceInfoDO {
private String interfaceName;//服務對應接口名稱
private String version;//版本号
private String implClassName;//實作該接口的類
private Set<String> ips = new HashSet<>();//該服務的位址
private String ip;//某一次注冊服務的位址
}
- (2)以一個單例類儲存服務資訊的集合:
public class ServicesSingle {
private static ServicesSingle servicesSingle = null;
private Map<String,ServiceInfoDO> services = null;
private ServicesSingle(){
services = new ConcurrentHashMap<>();
}
public static ServicesSingle getServiceSingle(){
synchronized (ServicesSingle.class){
if (servicesSingle == null){
servicesSingle = new ServicesSingle();
}
}
return servicesSingle;
}
}
- (3)對服務資訊集合操作的接口:
public interface IServiceAccess {
/**
* 根據使用者提供的服務資訊,進行服務的注冊
* @param serviceInfo 要注冊的服務資訊
* @return
*/
public boolean serviceRegistry(ServiceInfoDO serviceInfo);
/**
* 根據服務的唯一辨別ID查詢服務的位址清單
* @param serviceID
* @return
*/
public Set<String> queryServiceIPsByID(String serviceID);
/**
* 根據服務的唯一辨別ID查詢服務的資訊
* @param serviceID
* @return
*/
public ServiceInfoDO queryServiceInfoByID(String serviceID);
}
- (4)接口的實作類:
/**
* 完成服務的管理操作:注冊\查詢
*/
public class ServiceAccessImpl implements IServiceAccess{
@Override
public boolean serviceRegistry(ServiceInfoDO serviceInfo) {
if (serviceInfo.getInterfaceName() == null || serviceInfo.getInterfaceName().length() == ||
serviceInfo.getImplClassName() == null || serviceInfo.getImplClassName().length() == ||
serviceInfo.getVersion() == null || serviceInfo.getVersion().length() == ||
serviceInfo.getIp() == null || serviceInfo.getIp().length() ==)
return false;
String serviceID = serviceInfo.getInterfaceName() + "_" + serviceInfo.getVersion();
if (ServicesSingle.getServiceSingle().getServices().containsKey(serviceID)){
ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps().add(serviceInfo.getIp());
}else {
serviceInfo.getIps().add(serviceInfo.getIp());
ServicesSingle.getServiceSingle().getServices().put(serviceID,serviceInfo);
}
return true;
}
@Override
public Set<String> queryServiceIPsByID(String serviceID) {
if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
return null;
return ServicesSingle.getServiceSingle().getServices().get(serviceID).getIps();
}
@Override
public ServiceInfoDO queryServiceInfoByID(String serviceID) {
if (!ServicesSingle.getServiceSingle().getServices().containsKey(serviceID))
return null;
return ServicesSingle.getServiceSingle().getServices().get(serviceID);
}
}
2. LCRPC服務架構核心部分
LCRPC服務架構的核心部分,最終利用Maven生成一個jar包提供服務釋出以及遠端調用功能
整個工程是由maven以及spring開發建構的,是一個Java工程,最終利用maven建構jar包提供使用者使用。
pom依賴配置如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>whu.edu.lcrpc.</groupId>
<artifactId>lcrpc-core</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>lcrpc-core</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--spring依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--gson依賴-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<!--lombok依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!--json解析依賴-->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.1 服務架構UML類圖
(看不清可通路服務架構UML類圖)
2.2 服務調用部分
服務調用端提供LCRPCConsumerImpl類給使用者使用,該類中包含兩個必要的屬性變量分别是interfaceNeme(接口名)、version(版本号),用來辨別某一個LCRPCConsumer執行個體對象是對哪一個服務的調用,同時提供方法serviceConsumer,使用者通過傳入要調用的方法名稱以及參數資訊,實作對該接口某一個方法的遠端調用。LCRPCConsumer類使用一個幫助類ConsumerServiceImpl來實作整個調用過程。同時,設計了一些自定義異常,用于在程式出錯時抛出給使用者進行捕獲。
核心代碼
- (1)類LCRPCConsumerImpl提供使用者使用,主要實作方法的遠端調用
@Data
public class LCRPCConsumerImpl implements ILCRPCConsumer {
/**
* 以下兩個變量共同組成服務的唯一辨別
*/
private String interfaceName;//服務對應接口的全限定名
private String version;//服務版本号
private IConsumerService consumerService;//初始化用戶端輔助類
public LCRPCConsumerImpl(){
consumerService = new ConsumerServiceImpl();
}
@Override
public Object serviceConsumer(String methodName, List<Object> params) throws LCRPCServiceIDIsIllegal,LCRPCServiceNotFound,LCRPCRemoteCallException {
//若服務唯一辨別沒有提供,則抛出異常
if (interfaceName == null || interfaceName.length() ==
|| version == null || version.length() == )
throw new LCRPCServiceIDIsIllegal();
//step1. 根據服務的唯一辨別擷取該服務的ip位址清單
String serviceID = interfaceName + "_" + version;
Set<String> ips = consumerService.getServiceIPsByID(serviceID);
if (ips == null || ips.size() == )
throw new LCRPCServiceNotFound();
//step2. 路由,擷取該服務的位址,路由的結果會傳回至少一個位址,是以這裡不需要抛出異常
String serviceAddress = consumerService.getIP(serviceID,methodName,params,ips);
//step3. 根據傳入的參數,拼裝Request對象,這裡一定會傳回一個合法的request對象,是以不需要抛出異常
LCRPCRequestDO requestDO = consumerService.getRequestDO(interfaceName,version,methodName,params);
//step3. 傳入Request對象,序列化并傳入服務端,拿到響應後,反序列化為object對象
Object result = null;
try {
result = consumerService.sendData(serviceAddress,requestDO);
}catch (Exception e){
//在服務調用的過程種出現問題
throw new LCRPCRemoteCallException(e.getMessage());
}
if (result == null)throw new LCRPCRemoteCallException(Constant.SERVICEUNKNOWNEXCEPTION);
//step4. 傳回object對象
return result;
}
}
- (2)幫助類 ConsumerServiceImpl,主要實作一些輔助函數,包括服務ip清單的查詢,路由,資料的發送等。
public class ConsumerServiceImpl implements IConsumerService {
@Override
public Set<String> getServiceIPsByID(String serviceID) {
//調用服務注冊查找中心的服務,擷取ip清單
Set<String> ips = new HashSet<>();
String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
+ Constant.QUERYSERVICEIPSBYID + "?serviceID=" + serviceID;
Set status = new HashSet<Integer>();
status.add();
StringBuilder response = new StringBuilder();
GetRemoteInfo.getRemoteInfo(url,"GET",null,null,response,status);
if (response.length() == )return ips;
ips = new Gson().fromJson(response.toString(),ips.getClass());
return ips;
}
@Override
public String getIP(String serviceID, String methodName,List<Object> params, Set<String> ips) {
//可以根據接口\方法\參數進行路由,這裡我們先簡單實作,選出清單的第一個,模拟路由的過程
String[] temparr = new String[ips.size()];
ips.toArray(temparr);
return temparr[];
}
@Override
public LCRPCRequestDO getRequestDO(String interfaceName, String version, String methodName, List<Object> params) {
LCRPCRequestDO requestDO = new LCRPCRequestDO();
requestDO.setInterfaceName(interfaceName);
requestDO.setMethodName(methodName);
requestDO.setParams(params);
requestDO.setVersion(version);
return requestDO;
}
@Override
public Object sendData(String ip,LCRPCRequestDO requestDO) throws IOException, ClassNotFoundException {
ObjectOutputStream objectOutputStream = null;
Socket socket = null;
ObjectInputStream objectInputStream = null;
try {
socket = new Socket(ip, Constant.PORT);//向遠端服務端建立連接配接
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//獲得輸出流
objectOutputStream.writeObject(requestDO);//發送序列化結果
objectOutputStream.flush();
socket.shutdownOutput();
//等待響應
objectInputStream = new ObjectInputStream(socket.getInputStream());//獲得輸入流
Object result = objectInputStream.readObject();//序列化為Object對象
objectInputStream.close();
objectOutputStream.close();
socket.close();
return result;
}catch (Exception e){
throw e;
}finally {
try {
if(objectInputStream != null)objectInputStream.close();
if (objectOutputStream != null)objectOutputStream.close();
if (socket !=null )socket.close();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
}
2.3 服務釋出部分
服務釋出部分,主要包括兩部分,第一是服務的監聽,第二是服務的注冊與處理。服務的監聽,通過開啟一個socket監聽線程ServerThread來實作。當有用戶端請求後,開啟新的處理線程ServerProcessThread,來進行資料的解析、服務的調用、資料響應等操作。提供給使用者使用的是類LCRPCProviderImpl,主要包含四個必要屬性變量(interfaceName、version、implClassName、ip),對要釋出的服務進行描述。該類主要提供的方法是服務的注冊。同時還設計了自定義異常類,用于在出錯時抛出,他們分别是:LCRPCServiceInfoNotComplete(要釋出的服務的資訊不完整)、LCRPCServiceListenFailed(服務監聽失敗)、LCRPCServiceRegistryFailed(服務注冊失敗)。
核心代碼
- (1)LCRPCProviderImpl提供給使用者使用,用于服務的釋出與注冊
@Data
public class LCRPCProviderImpl implements ILCRPCProvider{
/**
* 以下變量為釋出一個服務的必要變量
*/
private String interfaceName;//服務對應接口的全限定名
private String version;//服務版本号
private String implClassName;//實作該服務接口的類
private String ip;//釋出該服務的位址
private static boolean isListened = false;//是否已經開啟監聽
private IProviderService providerService;
public LCRPCProviderImpl(){
providerService = new ProviderServiceImpl();
//開啟服務監聽端口
if (!isListened ){
if (providerService.startListen())
isListened = true;
else throw new LCRPCServiceListenFailed();
}
}
public void checkInfo(){
//先判斷服務參數資訊是否完整
if (interfaceName == null || interfaceName.length() == ||
version == null || version.length() == ||
implClassName == null || implClassName.length() == ||
ip == null || ip.length() ==)
throw new LCRPCServiceInfoNotComplete();
}
@Override
public boolean servicePublish() {
checkInfo();
//step1. 注冊服務.注冊服務之前先判斷服務是否開啟,若沒有開啟,則首先開啟服務
synchronized (LCRPCProviderImpl.class){
if (!isListened){
if (providerService.startListen())
isListened = true;
else throw new LCRPCServiceListenFailed();
}
providerService.serviceregistry(interfaceName,version,implClassName,ip);
}
return true;
}
}
- (2)線程類ServerThread開啟socket監聽,等待用戶端的請求,開啟處理線程進行處理
public class ServerThread implements Runnable{
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(Constant.PORT);
System.out.println("已經開始監聽,可以注冊服務了");
while (true){
Socket socket = serverSocket.accept();
new Thread(new ServerProcessThread(socket)).start();//開啟新的線程進行連接配接請求的處理
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- (3)服務端連接配接處理線程ServerProcessThread,負責解析用戶端的請求,利用反射對響應服務進行方法調用,将調用結果序列化發送給調用端
public class ServerProcessThread implements Runnable {
private Socket socket;
public ServerProcessThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//擷取用戶端的資料,并寫回
//等待響應
ObjectInputStream objectInputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//step1. 将請求資料反序列化為request對象
objectInputStream = new ObjectInputStream(socket.getInputStream());//獲得輸入流
LCRPCRequestDO requestDO = (LCRPCRequestDO) objectInputStream.readObject();//序列化為Object對象
//step2. 擷取服務接口實作類的資訊
ServiceInfoDO serviceInfoDO = ServicesSingle.getServiceSingle().getServices().get(requestDO.getInterfaceName() + "_" + requestDO.getVersion());
//step3.利用反射建立對象,調用方法,得到結果
Class clz = Class.forName(serviceInfoDO.getImplClassName());
Method methodethod = null;
Object result = null;
if (requestDO.getParams() != null && requestDO.getParams().size() > ){
Class []classes = new Class[requestDO.getParams().size()];
Object []obj = requestDO.getParams().toArray();
int i = ;
for (Object object:requestDO.getParams()){
if(object instanceof Integer){
classes[i] = Integer.TYPE;
}else if(object instanceof Byte){
classes[i] = Byte.TYPE;
}else if(object instanceof Short){
classes[i] = Short.TYPE;
}else if(object instanceof Float){
classes[i] = Float.TYPE;
}else if(object instanceof Double){
classes[i] = Double.TYPE;
}else if(object instanceof Character){
classes[i] = Character.TYPE;
}else if(object instanceof Long){
classes[i] = Long.TYPE;
}else if(object instanceof Boolean){
classes[i] = Boolean.TYPE;
}else {
classes[i] = object.getClass();
}
i++;
}
methodethod = clz.getDeclaredMethod(requestDO.getMethodName(),classes);
result = methodethod.invoke(clz.newInstance(),obj);
}else {
methodethod = clz.getDeclaredMethod(requestDO.getMethodName());
result = methodethod.invoke(clz.newInstance());
}
//step4.将結果序列化,寫回調用端
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//獲得輸出流
objectOutputStream.writeObject(result);//發送序列化結果
objectOutputStream.flush();
socket.shutdownOutput();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(objectInputStream != null)objectInputStream.close();
if (objectOutputStream != null)objectOutputStream.close();
if (socket !=null )socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- (4)服務釋出的幫助類ProviderServiceImpl
public class ProviderServiceImpl implements IProviderService {
@Override
public boolean startListen() {
new Thread(new ServerThread()).start();
return true;
}
@Override
public boolean serviceregistry(String interfaceName, String version, String implClassName, String ip) {
//注冊到服務注冊查找中心的同時也要緩存到記憶體services
//step1. 注冊到服務中心
String url = "http://" + Constant.SERVICEACCESSCENTER_IP + ":" + Constant.SERVICEACCESSCENTER_PORT + "/"
+ Constant.SERVICEREGISTRY;
Map<String,String> headers = new HashMap();
headers.put("Content-Type","application/json");
JSONObject param = new JSONObject();
param.put("interfaceName",interfaceName);
param.put("version",version);
param.put("implClassName",implClassName);
param.put("ip",ip);
Set status = new HashSet<Integer>();
status.add();
StringBuilder response = new StringBuilder();
GetRemoteInfo.getRemoteInfo(url,"POST",headers,param.toString(),response,status);
if (response.equals("false")) throw new LCRPCServiceRegistryFailed();
//step2. 存入到緩存
if (ServicesSingle.getServiceSingle().getServices().containsKey(interfaceName + "_" + version)){
ServicesSingle.getServiceSingle().getServices().get(interfaceName + "_" + version).getIps().add(ip);
}
else {
ServiceInfoDO serviceInfoDO = new ServiceInfoDO();
serviceInfoDO.setInterfaceName(interfaceName);
serviceInfoDO.setVersion(version);
serviceInfoDO.setImplClassName(implClassName);
serviceInfoDO.getIps().add(ip);
ServicesSingle.getServiceSingle().getServices().put(interfaceName + "_" + version,serviceInfoDO);
}
System.out.println("成功注冊服務:[" + interfaceName + "]");
return true;
}
}
3. LCRPC服務架構使用
3.1 服務釋出
服務端的測試部分,釋出一個電腦服務;
該部分為一個基本Java應用,采用spring+maven的開發方式。
- (1)pom檔案如下,包括對LCRPC服務架構提供jar包的依賴、對spring的依賴等
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>whu.edu.lcrpc</groupId>
<artifactId>lcrpc-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>lcrpc-test</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--spring依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--lombok依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!--對LCRPC服務架構的依賴-->
<dependency>
<groupId>whu.edu.lcrpc</groupId>
<artifactId>lcrpc-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
(2)spring的配置檔案:
主要是配置LCRPC的服務釋出端的bean,通過該bean來進行服務的釋出,配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--ILCRPCProvider的bean 進行配置後 自動釋出服務-->
<bean id="lcrpcProvider" class="whu.edu.lcrpc.server.impl.LCRPCProviderImpl" init-method="servicePublish">
<property name="implClassName" value="whu.edu.lcrpc.service.impl.CaculatorImpl"></property>
<property name="version" value="0.1"></property>
<property name="ip" value="127.0.0.1"></property>
<property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator"></property>
</bean>
</beans>
其中四個屬性均是必要的。同時init-method的配置也是必要的,該函數通過使用者配置為屬性資訊進行服務的釋出。
如果是在web容器中進行服務的釋出,例如tomcat,則隻需要在web.xml中配置spring的監聽,就可以進行服務的釋出。或者在main函數中利用ApplicationContext進行配置檔案的讀取。
在本例中,為了簡單,采用main函數的方式。代碼如下:
- (3)main函數
package whu.edu.lcrpc.app;
/**
* Created by apple on 17/3/26.
*/
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 服務端測試類,調用LCRPC接口,進行服務的釋出
*/
public class ProviderTest {
public static void main(String[] args) {
//直接讀取spring的配置檔案就好
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
-
(4)下面就是需要釋出的服務對應的接口,以及接口的實作類。我們為了進行不同情況的測試,是以寫了四個函數,參數和傳回值分别是Java中的基本類型、引用類型。
接口代碼如下:
package whu.edu.lcrpc.service;
/**
* Created by apple on 17/3/26.
*/
import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
/**
* 服務對應接口
* 該服務實作一個簡單的電腦服務,實作加減乘除四個基本功能
*/
public interface ICaculator {
/**
* 加
* @param n1
* @param n2
* @return
*/
public double add(double n1,double n2);
/**
* 減
* @param n1
* @param n2
* @return
*/
public MyResultDO minus(double n1, double n2);
/**
* 乘
* @param myParamDO
* @return
*/
public MyResultDO multiply(MyParamDO myParamDO);
/**
* 除
* @param myParamDO
* @return
*/
public double divide(MyParamDO myParamDO);
}
接口實作類的代碼如下:
package whu.edu.lcrpc.service.impl;
import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.service.ICaculator;
/**
* Created by apple on 17/3/26.
*/
/**
* 服務對應接口的實作類
*/
public class CaculatorImpl implements ICaculator {
@Override
public double add(double n1, double n2) {
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
return n1 + n2;
}
@Override
public MyResultDO minus(double n1, double n2) {
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
MyResultDO myResultDO = new MyResultDO();
myResultDO.setResult(n1 - n2);
return myResultDO;
}
@Override
public MyResultDO multiply(MyParamDO myParamDO) {
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
MyResultDO myResultDO = new MyResultDO();
myResultDO.setResult(myParamDO.getN1() * myParamDO.getN2());
return myResultDO;
}
@Override
public double divide(MyParamDO myParamDO) {
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
return myParamDO.getN2() / myParamDO.getN1();
}
}
最終該工程會生成一個二方包提供給服務調用端使用。
3.2 服務調用
用戶端的測試部分,利用LCRPC提供的包以及服務釋出者提供的二方包,遠端調用該電腦服務。
- (1)pom檔案
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>whu.edu.lcrpc</groupId>
<artifactId>lcrpc-clienttest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>lcrpc-clienttest</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--spring依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--lombok依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!--對服務釋出者二方包的依賴-->
<dependency>
<groupId>whu.edu.lcrpc</groupId>
<artifactId>lcrpc-test</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--對LCRPC服務架構的依賴-->
<dependency>
<groupId>whu.edu.lcrpc</groupId>
<artifactId>lcrpc-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
- (2)spring配置檔案,主要是配置LCRPC提供的用戶端接口,利用該bean,進行方法的調用,每一個consumer的bean代表對一個服務接口的調用
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="rpcConsumer" class="whu.edu.lcrpc.server.impl.LCRPCConsumerImpl">
<property name="interfaceName" value="whu.edu.lcrpc.service.ICaculator" ></property>
<property name="version" value="0.1"></property>
</bean>
<bean id="ConsumerTest" class="whu.edu.lcrpc.app.ConsumerTest"></bean>
</beans>
屬性interfaceName以及version是必要的,辨別該bean是對哪一個遠端服務的調用。
- (3)服務調用幫助類,在該類中我們利用LCRPC提供的接口,對電腦服務進行調用,分别調用該服務的加減乘除四個方法
package whu.edu.lcrpc.app;
/**
* Created by apple on 17/3/26.
*/
import lombok.Data;
import whu.edu.lcrpc.entity.MyParamDO;
import whu.edu.lcrpc.entity.MyResultDO;
import whu.edu.lcrpc.server.ILCRPCConsumer;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* 用戶端測試類,調用LCRPC接口,進行服務的調用
*/
@Data
public class ConsumerTest {
@Resource
ILCRPCConsumer rpcConsumer;
public void add() {
//調用接口 進行加減乘除
List<Object> params = new ArrayList<>();
params.add();
params.add();
Double result = (Double) rpcConsumer.serviceConsumer("add",params);
System.out.println("add: " + result);
}
public void minus(){
List<Object> params = new ArrayList<>();
params.add();
params.add();
MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("minus",params);
System.out.println("minus: " + result.getResult());
}
public void multiply(){
List<Object> params = new ArrayList<>();
MyParamDO myParamDO = new MyParamDO();
myParamDO.setN1();
myParamDO.setN2();
params.add(myParamDO);
MyResultDO result = (MyResultDO) rpcConsumer.serviceConsumer("multiply",params);
System.out.println("multiply: " + result.getResult());
}
public void divide(){
List<Object> params = new ArrayList<>();
MyParamDO myParamDO = new MyParamDO();
myParamDO.setN1();
myParamDO.setN2();
params.add(myParamDO);
Double result = (Double) rpcConsumer.serviceConsumer("divide",params);
System.out.println("divide: " + result);
}
}
- (4)運作主類,開啟四個線程,同時對四個方法進行遠端調用
package whu.edu.lcrpc.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by apple on 17/3/27.
*/
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ConsumerTest consumerTest = (ConsumerTest) context.getBean("ConsumerTest");
new Thread(()->{
consumerTest.add();
}).start();
new Thread(()->{
consumerTest.minus();
}).start();
new Thread(()->{
consumerTest.multiply();
}).start();
new Thread(()->{
consumerTest.divide();
}).start();
}
}
3.3 效果
- (1)首先開啟服務注冊查找中心
- (2)服務測試端,運作main函數進行服務的釋出,運作後,服務釋出成功,如下圖所示:
- (3)服務調用端測試,運作main函數進行服務遠端調用,運作後,服務調用成功,如下圖所示,對1,2兩個數字開啟四個線程分别進行了加減乘除運算,四個結果同時輸出:
注意:所有工程代碼可通路資源:實作自己的遠端調用架構,進行下載下傳
未完待續。。。。這隻是服務架構的初步實作版本,期待優化提升後的第二個版本~
值得期待的是:
(1)怎樣利用動态代理,使得使用者與通路本地接口一樣調用遠端服務
(2)當連接配接增多,服務是否可以支撐?需要改變IO模式。