基于HTTP協(xié)議實現(xiàn)簡單RPC框架的方法詳解
什么是RPC
RPC全名(Remote Procedure Call),翻譯過來就是遠程過程調(diào)用,這里特別說明一點,RPC是一種遠程調(diào)用方式,而不是一種協(xié)議。那么,我們其實很容易聯(lián)想到與之對應的應該是本地過程調(diào)用(Local Procedure Call),而不是什么什么http協(xié)議、tcp協(xié)議.....
本地調(diào)用與遠程調(diào)用
我們最常見的本地調(diào)用是什么樣的?
public interface UserService {
String doSomething();
}
@Service
public class UserServiceImpl implements UserService {
@Override
public String doSomething() {
System.out.println("doSomething......");
}
}
@Controller
public class UserController {
@Autowired
private UserService userService;
public void test(){
String response = userService.doSomething();
}
}我們以最常見的思維來模擬,這就是一種對UserService最簡單的本地調(diào)用,service實現(xiàn)類以及對應暴露出的接口都在本地機器上,不需要網(wǎng)絡之間的通信就能調(diào)用服務執(zhí)行。
那么,怎么樣又算對UserService的遠程調(diào)用呢?我們假設有 A, B兩臺機器,A為客戶端,B為服務端。此時,假設我們對于UserService來說,他的impl實現(xiàn)類在服務端B上,客戶端只知道對應的暴露接口,也就是說,對于客戶端A,沒有辦法直接使用spring幫我們自動注入對應service使用。
此時環(huán)境下 客戶端A的代碼示例:
public interface UserService { //假設這是服務端暴露出來的接口
String doSomething();
}
@Controller
public class UserController {
@Autowired
private UserService userService;//對于客戶端來說,此時沒有實現(xiàn)類,無法直接使用
public void test(){
String response = userService.doSomething();
}
}RPC框架就可以很好地解決這種場景下的問題,下面簡單提供一個實現(xiàn)思路。
實現(xiàn)思路
我們同樣以最直接最暴力的想法去思考怎么解決這個問題:只要我客戶端調(diào)用 UserService 的 doSomething() 后,客戶端再發(fā)送個請求并攜帶相關參數(shù)(如果有的話)給服務端,服務端處理后再響應給客戶端即可。那怎么實現(xiàn)這個過程呢?由于服務端已經(jīng)對我們暴露出了接口,那我們實際上可以用代理類的方式去實現(xiàn),把請求響應的過程放在代理類中實現(xiàn)即可,只要生成了代理類,我們再將這個代理類交給spring容器管理,讓他正常注入,我們就可以實現(xiàn)在客戶端沒有impl類的情況下也能正常使用service方法。
我們模仿Mybatis的@MapperScan注解,自己弄一個@RPC注解,只要將這個注解加在配置類上,即可將對應包下的接口生成RPC代理類
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({ServiceBeanDefinitionRegistry.class}) //注意看這個地方比較重要
public @interface RPC {
String value() default ""; //掃描的路徑
}
//簡單用法
@SpringBootApplication
@RPC("com.yeyeye.client.service") //掃描對應包下的所有接口,service包下就一個上面示例中的 UserService 接口
public class RpcClientApplication {
public static void main(String[] args) {
SpringApplication.run(RpcClientApplication.class, args);
}
}@Import 注解就不在這注釋了,大概就是spring在啟動的時候會導入這個注解里面寫的類,這里詳細介紹一下 ServiceBeanDefinitionRegistry
public class ServiceBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//獲取RPC注解里的路徑
Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(RPC.class.getName());
if (MapUtil.isEmpty(attributes)) {
return;
}
String basePackages = (String) attributes.get("value");
if (basePackages == null) {
return;
}
//掃描路徑
RpcScanner rpcScanner = new RpcScanner(registry);
rpcScanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
rpcScanner.scan(basePackages);
}
}這個類主要就做了兩件事,很好理解,就是:
- 獲取
@RPC注解中的路徑 - 讓spring幫我們掃描這個路徑,將掃描到的符合需求的bean注冊到registry中。
RpcScanner 這個類是利用Spring的掃描機制寫的,掃描機制具體原理不是重點,這里也不贅述。
public class RpcScanner extends ClassPathBeanDefinitionScanner {
public RpcScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
//是不是一個接口,是的話就添加為候選組件
return beanDefinition.getMetadata().isInterface();
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//這里就是具體利用spring幫我們掃描
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
//對掃描出來的結果進行處理
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
//這里獲取的beanDefinition都是接口,并沒有實現(xiàn)類
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
//注冊一個我們自己的beanDefinition,這個beanDefinition是一個bean工廠,工廠用于生成接口的代理類
beanDefinition.setBeanClassName(ServiceBeanFactory.class.getName());
//這里就是工廠bean構造方法需要的參數(shù)
beanDefinition.getConstructorArgumentValues()
.addGenericArgumentValue(beanDefinition.getBeanClassName());
}
return beanDefinitionHolders;
}
}這個類比較重要,主要就干了幾件事:
- 把對應包下的接口全部掃描出來,包裝成beanDefinition。
- 由于此時的beanDefinition里面的類是一個接口,spring沒辦法幫我們生成,所以我們需要用一個beanFactory代替beanDefinition里的類,也就是用bean工廠幫我們生成一個代理類給spring管理
- 添加實例化工廠需要的參數(shù)
工廠bean類,用jdk代理機制簡單實現(xiàn)
public class ServiceBeanFactory<T> implements FactoryBean<T> {
private Class<T> interFaceClazz;
public ServiceBeanFactory(Class<T> interFaceClazz) {
this.interFaceClazz = interFaceClazz;
}
@Override
public T getObject() throws Exception {
return (T) Proxy.newProxyInstance(
interFaceClazz.getClassLoader(),
new Class[]{interFaceClazz},
new ServiceInvocationHandler());
}
@Override
public Class<?> getObjectType() {
return interFaceClazz;
}
}
public class ServiceInvocationHandler implements InvocationHandler {
/**
* 這里就直接進行遠程RPC調(diào)用了
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//指定URL
String url = "http://localhost:8888/" + method.getName();
//發(fā)送get請求并接收響應數(shù)據(jù)
return HttpUtil.createGet(url).execute().body();
}
}因此,spring注入的時候實際上注入的是這個對接口的代理類,我們在執(zhí)行doSomething()方法時,實際上會通過這個代理類向服務端發(fā)一個HTTP請求。
服務端
對于服務端來說,我只需要正常寫一個controller處理一下來自客戶端的請求就好了。這里就不過多演示
倉庫地址
有需要的朋友也可以直接去我的倉庫看相關代碼。如有錯漏,還請指正。github.com/NopeDl/ez-rpc
到此這篇關于基于HTTP協(xié)議實現(xiàn)簡單RPC框架的方法詳解的文章就介紹到這了,更多相關RPC框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
關于feign.codec.DecodeException異常的解決方案
這篇文章主要介紹了關于feign.codec.DecodeException異常的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Java的SpringMVC中控制器返回XML數(shù)據(jù)問題
這篇文章主要介紹了Java的SpringMVC中控制器返回XML數(shù)據(jù)問題,控制器是處理HTTP請求的組件,它們接收來自客戶端的請求,并將其轉換為適當?shù)捻憫?這些響應可以是動態(tài)生成的?HTML?頁面,也可以是JSON或XML格式的數(shù)據(jù),需要的朋友可以參考下2023-07-07
SpringBoot生產(chǎn)環(huán)境和測試環(huán)境配置分離的教程詳解
這篇文章主要介紹了SpringBoot生產(chǎn)環(huán)境和測試環(huán)境配置分離的教程詳解,需要的朋友可以參考下2020-08-08

