成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

徒手?jǐn)]一個(gè)簡(jiǎn)單的RPC框架

Gemini / 2461人閱讀

摘要:徒手?jǐn)]一個(gè)簡(jiǎn)單的框架之前在牛逼哄哄的框架,底層到底什么原理得知了遠(yuǎn)程過程調(diào)用簡(jiǎn)單來說就是調(diào)用遠(yuǎn)程的服務(wù)就像調(diào)用本地方法一樣,其中用到的知識(shí)有序列化和反序列化動(dòng)態(tài)代理網(wǎng)絡(luò)傳輸動(dòng)態(tài)加載反射這些知識(shí)點(diǎn)。

徒手?jǐn)]一個(gè)簡(jiǎn)單的RPC框架

之前在牛逼哄哄的 RPC 框架,底層到底什么原理得知了RPC(遠(yuǎn)程過程調(diào)用)簡(jiǎn)單來說就是調(diào)用遠(yuǎn)程的服務(wù)就像調(diào)用本地方法一樣,其中用到的知識(shí)有序列化和反序列化、動(dòng)態(tài)代理、網(wǎng)絡(luò)傳輸、動(dòng)態(tài)加載、反射這些知識(shí)點(diǎn)。發(fā)現(xiàn)這些知識(shí)都了解一些。所以就想著試試自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的RPC框架,即鞏固了基礎(chǔ)的知識(shí),也能更加深入的了解RPC原理。當(dāng)然一個(gè)完整的RPC框架包含了許多的功能,例如服務(wù)的發(fā)現(xiàn)與治理,網(wǎng)關(guān)等等。本篇只是簡(jiǎn)單的實(shí)現(xiàn)了一個(gè)調(diào)用的過程。

傳參出參分析

一個(gè)簡(jiǎn)單請(qǐng)求可以抽象為兩步

那么就根據(jù)這兩步進(jìn)行分析,在請(qǐng)求之前我們應(yīng)該發(fā)送給服務(wù)端什么信息?而服務(wù)端處理完以后應(yīng)該返回客戶端什么信息?

在請(qǐng)求之前我們應(yīng)該發(fā)送給服務(wù)端什么信息?

由于我們?cè)诳蛻舳苏{(diào)用的是服務(wù)端提供的接口,所以我們需要將客戶端調(diào)用的信息傳輸過去,那么我們可以將要傳輸?shù)男畔⒎譃閮深?/p>

第一類是服務(wù)端可以根據(jù)這個(gè)信息找到相應(yīng)的接口實(shí)現(xiàn)類和方法

第二類是調(diào)用此方法傳輸?shù)膮?shù)信息

那么我們就根據(jù)要傳輸?shù)膬深愋畔⑦M(jìn)行分析,什么信息能夠找到相應(yīng)的實(shí)現(xiàn)類的相應(yīng)的方法?要找到方法必須要先找到類,這里我們可以簡(jiǎn)單的用Spring提供的Bean實(shí)例管理ApplicationContext進(jìn)行類的尋找。所以要找到類的實(shí)例只需要知道此類的名字就行,找到了類的實(shí)例,那么如何找到方法呢?在反射中通過反射能夠根據(jù)方法名和參數(shù)類型從而找到這個(gè)方法。那么此時(shí)第一類的信息我們就明了了,那么就建立相應(yīng)的是實(shí)體類存儲(chǔ)這些信息。

@Data
public class Request implements Serializable {
    private static final long serialVersionUID = 3933918042687238629L;
    private String className;
    private String methodName;
    private Class [] parameTypes;
    private Object [] parameters;
}
服務(wù)端處理完以后應(yīng)該返回客戶端什么信息?

上面我們分析了客戶端應(yīng)該傳輸什么信息給服務(wù)端,那么服務(wù)端處理完以后應(yīng)該傳什么樣的返回值呢?這里我們只考慮最簡(jiǎn)單的情況,客戶端請(qǐng)求的線程也會(huì)一直在等著,不會(huì)有異步處理這一說,所以這么分析的話就簡(jiǎn)單了,直接將得到的處理結(jié)果返回就行了。

@Data
public class Response implements Serializable {
    private static final long serialVersionUID = -2393333111247658778L;
    private Object result;
}
由于都涉及到了網(wǎng)絡(luò)傳輸,所以都要實(shí)現(xiàn)序列化的接口
如何獲得傳參信息并執(zhí)行?-客戶端

上面我們分析了客戶端向服務(wù)端發(fā)送的信息都有哪些?那么我們?nèi)绾潍@得這些信息呢?首先我們調(diào)用的是接口,所以我們需要寫自定義注解然后在程序啟動(dòng)的時(shí)候?qū)⑦@些信息加載在Spring容器中。有了這些信息那么我們就需要傳輸了,調(diào)用接口但是實(shí)際上執(zhí)行的確實(shí)網(wǎng)絡(luò)傳輸?shù)倪^程,所以我們需要?jiǎng)討B(tài)代理。那么就可以分為以下兩步

初始化信息階段:將key為接口名,value為動(dòng)態(tài)接口類注冊(cè)進(jìn)Spring容器中

執(zhí)行階段:通過動(dòng)態(tài)代理,實(shí)際執(zhí)行網(wǎng)絡(luò)傳輸

初始化信息階段

由于我們使用Spring作為Bean的管理,所以要將接口和對(duì)應(yīng)的代理類注冊(cè)進(jìn)Spring容器中。而我們?nèi)绾握业轿覀兿胍{(diào)用的接口類呢?我們可以自定義注解進(jìn)行掃描。將想要調(diào)用的接口全部注冊(cè)進(jìn)容器中。

創(chuàng)建一個(gè)注解類,用于標(biāo)注哪些接口是可以進(jìn)行Rpc的

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcClient {
}

然后創(chuàng)建對(duì)于@RpcClient注解的掃描類RpcInitConfig,將其注冊(cè)進(jìn)Spring容器中

public class RpcInitConfig implements ImportBeanDefinitionRegistrar{
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider provider = getScanner();
        //設(shè)置掃描器
        provider.addIncludeFilter(new AnnotationTypeFilter(RpcClient.class));
        //掃描此包下的所有帶有@RpcClient的注解的類
        Set beanDefinitionSet = provider.findCandidateComponents("com.example.rpcclient.client");
        for (BeanDefinition beanDefinition : beanDefinitionSet){
            if (beanDefinition instanceof AnnotatedBeanDefinition){
                //獲得注解上的參數(shù)信息
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
                String beanClassAllName = beanDefinition.getBeanClassName();
                Map paraMap = annotatedBeanDefinition.getMetadata()
                        .getAnnotationAttributes(RpcClient.class.getCanonicalName());
                //將RpcClient的工廠類注冊(cè)進(jìn)去
                BeanDefinitionBuilder builder = BeanDefinitionBuilder
                        .genericBeanDefinition(RpcClinetFactoryBean.class);
                //設(shè)置RpcClinetFactoryBean工廠類中的構(gòu)造函數(shù)的值
                builder.addConstructorArgValue(beanClassAllName);
                builder.getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
                //將其注冊(cè)進(jìn)容器中
                registry.registerBeanDefinition(
                        beanClassAllName ,
                        builder.getBeanDefinition());
            }
        }
    }
    //允許Spring掃描接口上的注解
    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
            }
        };
    }
}

由于上面注冊(cè)的是工廠類,所以我們建立一個(gè)工廠類RpcClinetFactoryBean繼承Spring中的FactoryBean類,由其統(tǒng)一創(chuàng)建@RpcClient注解的代理類

@Data
public class RpcClinetFactoryBean implements FactoryBean {

    @Autowired
    private RpcDynamicPro rpcDynamicPro;

    private Class classType;


    public RpcClinetFactoryBean(Class classType) {
        this.classType = classType;
    }

    @Override
    public Object getObject(){
        ClassLoader classLoader = classType.getClassLoader();
        Object object = Proxy.newProxyInstance(classLoader,new Class[]{classType},rpcDynamicPro);
        return object;
    }

    @Override
    public Class getObjectType() {
        return this.classType;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
注意此處的getObjectType 方法,在將工廠類注入到容器中的時(shí)候,這個(gè)方法返回的是什么Class類型那么注冊(cè)進(jìn)容器中就是什么Class類型。

然后看一下我們創(chuàng)建的代理類rpcDynamicPro

@Component
@Slf4j
public class RpcDynamicPro implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       String requestJson = objectToJson(method,args);
        Socket client = new Socket("127.0.0.1", 20006);
        client.setSoTimeout(10000);
        //獲取Socket的輸出流,用來發(fā)送數(shù)據(jù)到服務(wù)端
        PrintStream out = new PrintStream(client.getOutputStream());
        //獲取Socket的輸入流,用來接收從服務(wù)端發(fā)送過來的數(shù)據(jù)
        BufferedReader buf =  new BufferedReader(new InputStreamReader(client.getInputStream()));
        //發(fā)送數(shù)據(jù)到服務(wù)端
        out.println(requestJson);
        Response response = new Response();
        Gson gson =new Gson();
        try{
            //從服務(wù)器端接收數(shù)據(jù)有個(gè)時(shí)間限制(系統(tǒng)自設(shè),也可以自己設(shè)置),超過了這個(gè)時(shí)間,便會(huì)拋出該異常
            String responsJson = buf.readLine();
            response = gson.fromJson(responsJson, Response.class);
        }catch(SocketTimeoutException e){
            log.info("Time out, No response");
        }
        if(client != null){
            //如果構(gòu)造函數(shù)建立起了連接,則關(guān)閉套接字,如果沒有建立起連接,自然不用關(guān)閉
            client.close(); //只關(guān)閉socket,其關(guān)聯(lián)的輸入輸出流也會(huì)被關(guān)閉
        }
        return response.getResult();
    }

    public String objectToJson(Method method,Object [] args){
        Request request = new Request();
        String methodName = method.getName();
        Class[] parameterTypes = method.getParameterTypes();
        String className = method.getDeclaringClass().getName();
        request.setMethodName(methodName);
        request.setParameTypes(parameterTypes);
        request.setParameters(args);
        request.setClassName(getClassName(className));
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapterFactory(new ClassTypeAdapterFactory());
        Gson gson = gsonBuilder.create();
        return gson.toJson(request);
    }

    private String getClassName(String beanClassName){
        String className = beanClassName.substring(beanClassName.lastIndexOf(".")+1);
        className = className.substring(0,1).toLowerCase() + className.substring(1);
        return className;
    }
}

我們的客戶端已經(jīng)寫完了,傳給服務(wù)端的信息我們也已經(jīng)拼裝完畢了。剩下的工作就簡(jiǎn)單了,開始編寫服務(wù)端的代碼。

服務(wù)端處理完以后應(yīng)該返回客戶端什么信息?-服務(wù)端

服務(wù)端的代碼相比較客戶端來說要簡(jiǎn)單一些??梢院?jiǎn)單分為下面三步

拿到接口名以后,通過接口名找到實(shí)現(xiàn)類

通過反射進(jìn)行對(duì)應(yīng)方法的執(zhí)行

返回執(zhí)行完的信息

那么我們就根據(jù)這三步進(jìn)行編寫代碼

拿到接口名以后,通過接口名找到實(shí)現(xiàn)類

如何通過接口名拿到對(duì)應(yīng)接口的實(shí)現(xiàn)類呢?這就需要我們?cè)诜?wù)端啟動(dòng)的時(shí)候?qū)⑵鋵?duì)應(yīng)信息加載進(jìn)去

@Component
@Log4j
public class InitRpcConfig implements CommandLineRunner {
    @Autowired
    private ApplicationContext applicationContext;

    public static Map rpcServiceMap = new HashMap<>();

    @Override
    public void run(String... args) throws Exception {
        Map beansWithAnnotation = applicationContext.getBeansWithAnnotation(Service.class);
        for (Object bean: beansWithAnnotation.values()){
            Class clazz = bean.getClass();
            Class[] interfaces = clazz.getInterfaces();
            for (Class inter : interfaces){
                rpcServiceMap.put(getClassName(inter.getName()),bean);
                log.info("已經(jīng)加載的服務(wù):"+inter.getName());
            }
        }
    }

    private String getClassName(String beanClassName){
        String className = beanClassName.substring(beanClassName.lastIndexOf(".")+1);
        className = className.substring(0,1).toLowerCase() + className.substring(1);
        return className;
    }
}

此時(shí)rpcServiceMap 存儲(chǔ)的就是接口名和其對(duì)應(yīng)的實(shí)現(xiàn)類的對(duì)應(yīng)關(guān)系。

通過反射進(jìn)行對(duì)應(yīng)方法的執(zhí)行

此時(shí)拿到了對(duì)應(yīng)關(guān)系以后就能根據(jù)客戶端傳過來的信息找到相應(yīng)的實(shí)現(xiàn)類中的方法。然后進(jìn)行執(zhí)行并返回信息就行

public Response invokeMethod(Request request){
        String className = request.getClassName();
        String methodName = request.getMethodName();
        Object[] parameters = request.getParameters();
        Class[] parameTypes = request.getParameTypes();
        Object o = InitRpcConfig.rpcServiceMap.get(className);
        Response response = new Response();
        try {
            Method method = o.getClass().getDeclaredMethod(methodName, parameTypes);
            Object invokeMethod = method.invoke(o, parameters);
            response.setResult(invokeMethod);
        } catch (NoSuchMethodException e) {
            log.info("沒有找到"+methodName);
        } catch (IllegalAccessException e) {
            log.info("執(zhí)行錯(cuò)誤"+parameters);
        } catch (InvocationTargetException e) {
            log.info("執(zhí)行錯(cuò)誤"+parameters);
        }
        return response;
    }

現(xiàn)在我們兩個(gè)服務(wù)都啟動(dòng)起來并且在客戶端進(jìn)行調(diào)用就發(fā)現(xiàn)只是調(diào)用接口就能調(diào)用過來了。

總結(jié)

到現(xiàn)在一個(gè)簡(jiǎn)單的RPC就完成了,但是其中還有很多的功能需要完善,例如一個(gè)完整RPC框架肯定還需要服務(wù)注冊(cè)與發(fā)現(xiàn),而且雙方通信肯定也不能是直接開啟一個(gè)線程一直在等著,肯定需要是異步的等等的各種功能。后面隨著學(xué)習(xí)的深入,這個(gè)框架也會(huì)慢慢增加一些東西。不僅是對(duì)所學(xué)知識(shí)的一個(gè)應(yīng)用,更是一個(gè)總結(jié)。有時(shí)候?qū)W一個(gè)東西學(xué)起來覺得很簡(jiǎn)單,但是真正應(yīng)用的時(shí)候就會(huì)發(fā)現(xiàn)各種各樣的小問題。比如在寫這個(gè)例子的時(shí)候碰到一個(gè)問題就是@Autowired的時(shí)候一直找不到SendMessage的類型,最后才發(fā)現(xiàn)是工廠類RpcClinetFactoryBean 中的getObjectType 中的返回類型寫錯(cuò)了,我之前寫的是

    public Class getObjectType() {
        return this.getClass();;
    }

這樣的話注冊(cè)進(jìn)容器的就是RpcClinetFactoryBean 類型的而不是SendMessage 的類型。

完整項(xiàng)目地址

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/75318.html

相關(guān)文章

  • 徒手框架--高并發(fā)環(huán)境下請(qǐng)求合并

    摘要:我們就可以將這些請(qǐng)求合并,達(dá)到一定數(shù)量我們統(tǒng)一提交。總結(jié)一個(gè)比較生動(dòng)的例子給大家講解了一些多線程的具體運(yùn)用。學(xué)習(xí)多線程應(yīng)該多思考多動(dòng)手,才會(huì)有比較好的效果。地址徒手?jǐn)]框架系列文章地址徒手?jǐn)]框架實(shí)現(xiàn)徒手?jǐn)]框架實(shí)現(xiàn) 原文地址:https://www.xilidou.com/2018/01/22/merge-request/ 在高并發(fā)系統(tǒng)中,我們經(jīng)常遇到這樣的需求:系統(tǒng)產(chǎn)生大量的請(qǐng)求,但是這...

    劉東 評(píng)論0 收藏0
  • 徒手框架--實(shí)現(xiàn)IoC

    摘要:從而能夠進(jìn)一步深入了解框架。至此我們框架開發(fā)完成。雖然說閱讀源碼是了解框架的最終手段。但是框架作為一個(gè)生產(chǎn)框架,為了保證通用和穩(wěn)定,源碼必定是高度抽象,且處理大量細(xì)節(jié)。下一篇文章應(yīng)該會(huì)是徒手?jǐn)]框架實(shí)現(xiàn)。 原文地址:https://www.xilidou.com/2018/... Spring 作為 J2ee 開發(fā)事實(shí)上的標(biāo)準(zhǔn),是每個(gè)Java開發(fā)人員都需要了解的框架。但是Spring 的...

    rottengeek 評(píng)論0 收藏0
  • 徒手一個(gè) Spring Boot 中 Starter ,解密自動(dòng)化配置黑魔法!

    摘要:先來看代碼吧,一會(huì)松哥再慢慢解釋關(guān)于這一段自動(dòng)配置,解釋如下首先注解表明這是一個(gè)配置類。本文的案例,松哥已經(jīng)上傳到上了,地址。我們使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中。Starter 為我們帶來了眾多的自動(dòng)化配置,有了這些自動(dòng)化配置,我們可以不費(fèi)吹灰之力就能搭建一個(gè)生產(chǎn)級(jí)開發(fā)環(huán)境,有的小伙伴會(huì)覺得這個(gè) Starter 好神奇呀!其實(shí) Starter 也都...

    xiaochao 評(píng)論0 收藏0
  • 徒手一個(gè)簡(jiǎn)單toast組件

    摘要:最近寫了個(gè)簡(jiǎn)單的的頁面,里面需要一個(gè)提交表單的反饋動(dòng)作,于是手?jǐn)]了個(gè)簡(jiǎn)單的組件這是個(gè) 最近寫了個(gè)簡(jiǎn)單的html+js的頁面,里面需要一個(gè)提交表單的反饋動(dòng)作,于是手?jǐn)]了個(gè)簡(jiǎn)單的toast組件 toast.js function showToast(msg, duration) { duration = isNaN(duration) ? 2000 : duration; var ...

    curlyCheng 評(píng)論0 收藏0
  • 徒手一個(gè)簡(jiǎn)單toast組件

    摘要:最近寫了個(gè)簡(jiǎn)單的的頁面,里面需要一個(gè)提交表單的反饋動(dòng)作,于是手?jǐn)]了個(gè)簡(jiǎn)單的組件這是個(gè) 最近寫了個(gè)簡(jiǎn)單的html+js的頁面,里面需要一個(gè)提交表單的反饋動(dòng)作,于是手?jǐn)]了個(gè)簡(jiǎn)單的toast組件 toast.js function showToast(msg, duration) { duration = isNaN(duration) ? 2000 : duration; var ...

    scola666 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<