再見 Feign!Spring Boot + JSON-RPC遠程調用新選擇
環境:SpringBoot3.4.2
1. 簡介
什么是JSON-RPC協議?
JSON-RPC 是一種輕量級的遠程過程調用(Remote Procedure Call,RPC)協議,它允許你在不同的系統之間通過網絡進行數據交換和調用函數。JSON-RPC 使用 JSON(JavaScript Object Notation)作為數據格式,這使得它非常適合于各種編程語言和平臺之間的通信。
目前JSON-RPC的版本是2.0,該版本是目前廣泛使用的版本,它增加了錯誤處理、批處理請求(batch requests)、通知(notifications)等特性。
JSON-RPC 2.0結構
請求,通常包含以下幾個字段:
- jsonrpc: String,版本號,通常為 "2.0"
- method: String,要調用的方法的名稱
- params: 可選,參數列表或對象,傳遞給方法的參數
- id: 可選,一個字符串、數字或 null。用于唯一標識請求,以便響應能夠回傳給正確
響應,通常包含以下幾個字段:
- jsonrpc: 字符串,版本號,通常為 "2.0"
- result: 請求成功時的返回值
- error: 請求失敗時的錯誤對象
- id: 與請求中的 ID 相匹配
說明:result與error,最終只會有一個返回。
JSON-RPC不同的編程語言都有對應的實現,在java中比較流行的則是jsonrpc4j,而本篇文章將基于該開源庫進行詳細的介紹。
什么是jsonrpc4j?
jsonrpc4j 使用 Jackson 庫將 Java 對象與 JSON 對象進行相互轉換(以及其他與 JSON-RPC 相關的功能)。包括以下功能:
- 流式服務器(InputStream / OutputStream)
- HTTP 服務器(HttpServletRequest / HttpServletResponse)
- Portlet 服務器(ResourceRequest / ResourceResponse)
- Socket 服務器(StreamServer)
- 與 Spring 框架集成
- 流式客戶端
- HTTP 客戶端
- 動態客戶端代理
- 注解支持
- 自定義錯誤解析
- 復合服務
2.實戰案例
2.1 依賴管理&接口定義
<dependency>
<groupId>com.github.briandilley.jsonrpc4j</groupId>
<artifactId>jsonrpc4j</artifactId>
<version>1.7</version>
</dependency>
目前最新版本為1.7。
接口定義
public interface UserService {
User createUser(String userName, String firstName, String password);
User createUser(String userName, String password);
User findUserByUserName(String userName);
int getUserCount();
}
// 接口實現
@Service
@Primary
public class UserServiceImpl implements UserService {
private static final List<User> USERS = new ArrayList<>();
public User createUser(String userName, String firstName, String password) {
User user = new User(userName, firstName, password);
USERS.add(user);
return user;
}
public User createUser(String userName, String password) {
return this.createUser(userName, null, password);
}
public User findUserByUserName(String userName) {
System.err.println("admin".equals(userName) ? 1 / 0 : "success") ;
return USERS.stream().filter(user -> user.userName().equals(userName)).findFirst().orElse(null);
}
public int getUserCount() {
return USERS.size();
}
}
接下來,我們將從以下方面對 jsonrpc4j 進行詳細說明與使用示例解析。
- RPC Server接口暴露的2種方式
- RPC Client調用的3種方式
- RPC Server錯誤處理
- RPC Server 流式(Socket)服務
2.2 RPC Server接口暴露
定義方式1:使用JsonServiceExporter
// 注意:這里的beanName以 "/" 開頭,最終會被BeanNameUrlHandlerMapping處理
@Bean("/us")
JsonServiceExporter exporter(UserService userService, ObjectMapper objectMapper) {
JsonServiceExporter exporter = new JsonServiceExporter() ;
// 暴露的接口
exporter.setServiceInterface(UserService.class) ;
// 這里非必須設置
exporter.setObjectMapper(objectMapper) ;
// 對應的接口實現
exporter.setService(userService) ;
return exporter ;
}
通過此種方式,我們就完成了服務的暴露,任何支持 JSON-RPC 的客戶端均可訪問此服務,接口地址:http://localhost:8080/us
定義方式2:使用注解自動發現機制
修改上面的接口已經實現類,添加如下的注解:
@JsonRpcService("/us")
public interface UserService {
}
@AutoJsonRpcServiceImpl
@Service
@Primary
public class UserServiceImpl implements UserService {
}
接下來,還需要定義如下的bean(啟動掃描功能)。
@Bean
AutoJsonRpcServiceImplExporter autoExporter() {
return new AutoJsonRpcServiceImplExporter() ;
}
這里我們推薦使用注解的方式。
2.3 RPC Client調用
配置方式1:使用AutoJsonRpcClientProxyCreator
首先,確保接口上使用了@JsonRpcServer注解:
@JsonRpcService("/us")
public interface UserService {}
接下來,配置AutoJsonRpcClientProxyCreator:
@Bean
AutoJsonRpcClientProxyCreator proxyCreator() throws Exception {
AutoJsonRpcClientProxyCreator creator = new AutoJsonRpcClientProxyCreator() ;
// 該baseUrl會自動拼接到@JsonRpcService的路徑前面; http://localhost:8080/us
creator.setBaseUrl(URI.create("http://localhost:8080").toURL()) ;
// 接口所在的包
creator.setScanPackage("com.pack.rpc.server") ;
return creator ;
}
配置方式2:使用JsonProxyFactoryBean
@Bean
JsonProxyFactoryBean userServiceProxy() {
JsonProxyFactoryBean proxy = new JsonProxyFactoryBean() ;
proxy.setServiceUrl("http://localhost:8080/us") ;
proxy.setServiceInterface(UserService.class);
return proxy ;
}
通過該FactoryBean,會自動的為UserService接口創建代理。
配置方式3:使用JsonRpcHttpClient
上面2種方式都是依賴的Spring環境,下面我們還可以使用如下的2中方式創建客戶端:
JsonRpcHttpClient client = new JsonRpcHttpClient(URI.create("http://localhost:8080/us").toURL()) ;
User user = client.invoke("createUser", new Object[] { "Spring Boot3實戰案例200講", "Pack", "123456" }, User.class);
System.err.println(user) ;
// 也可以通過如下方式創建代理
JsonRpcHttpClient client = new JsonRpcHttpClient(URI.create("http://localhost:8080/us").toURL());
UserService userService = ProxyUtil.createClientProxy(
ClientTest.class.getClassLoader(),
UserService.class,
client) ;
User user = userService.createUser("Pack", "xg") ;
System.err.println(user) ;
2.4 測試
@RestController
@RequestMapping("/rpc")
public class RpcController {
private final UserService userService ;
public RpcController(UserService userService) {
this.userService = userService;
}
@GetMapping("/create")
public ResponseEntity<User> createUser(String userName, String firstName, String password) {
return ResponseEntity.ok(this.userService.createUser(userName, firstName, password)) ;
}
@GetMapping("/query")
public ResponseEntity<User> queryUser(String userName) {
return ResponseEntity.ok(this.userService.findUserByUserName(userName)) ;
}
}
運行結果
圖片
圖片
成功調用。
2.5 異常處理
我們按照上面接口實現,我們通過如下參數訪問查詢接口:
圖片
控制臺異常信息如下:
圖片
RPC Client接收到如上的異常數據。
我們可以通過如下方式自定義異常信息,在JSON-RPC 服務的接口上添加注解:
@JsonRpcErrors({
@JsonRpcError(exception = Exception.class, code = -1, message = "服務發生異常")
})
User findUserByUserName(String userName);
再次訪問上面的接口后,控制臺輸出:
圖片
2.6 流式(Socket)服務
我們可以通過Socket方式提供服務,如下示例:
服務端:
JsonRpcServer server = new JsonRpcServer(new UserServiceImpl()) ;
int maxThreads = 50 ;
int port = 8080 ;
ServerSocket serverSocket = new ServerSocket(port) ;
StreamServer ss = new StreamServer(server, maxThreads, serverSocket) ;
ss.start() ;
客戶端:
Socket socket = new Socket(InetAddress.getLocalHost(), 8080) ;
OutputStream os = socket.getOutputStream() ;
Map<String, Object> data = Map.of("jsonrpc", "2.0", "method", "createUser",
"params", new Object[] {"Spring Boot3實戰案例200講", "Pack", "111111"}, "id", "s-0001") ;
os.write(new ObjectMapper().writeValueAsBytes(data)) ;
socket.shutdownOutput();
InputStream is = socket.getInputStream() ;
System.err.println(new String(is.readAllBytes())) ;
運行結果
客戶端輸出: