likes
comments
collection
share

手撸RPC框架之RPC核心注解实现与扫描解析

作者站长头像
站长
· 阅读数 34

大家好,我是小趴菜,接下来我会从0到1手写一个RPC框架,该专题包括以下专题,有兴趣的小伙伴就跟着我一起学习吧

本章源码地址:gitee.com/baojh123/se…

自定义注解 -> opt-01
服务提供者收发消息基础实现 -> opt-01
自定义网络传输协议的实现 -> opt-02
自定义编解码实现 -> opt-03
服务提供者调用真实方法实现 -> opt-04
完善服务消费者发送消息基础功能 -> opt-05
注册中心基础功能实现 -> opt-06
服务提供者整合注册中心 -> opt-07
服务消费者整合注册中心 -> opt-08
完善服务消费者接收响应结果 -> opt-09
服务消费者,服务提供者整合SpringBoot -> opt-10
动态代理屏蔽RPC服务调用底层细节 -> opt-10
SPI机制基础功能实现 -> opt-11
SPI机制扩展随机负载均衡策略 -> opt-12
SPI机制扩展轮询负载均衡策略 -> opt-13
SPI机制扩展JDK序列化 -> opt-14
SPI机制扩展JSON序列化 -> opt-15
SPI机制扩展protustuff序列化 -> opt-16

1:核心注解的实现

一个完整的RPC框架,一共包括四个部分,分别是服务提供者 服务消费者 注册中心 监控中心

手撸RPC框架之RPC核心注解实现与扫描解析

最核心的注解也就是 服务提供者 和 服务消费者,对于服务提供者来说,我们需要扫描到哪些类是作为服务提供者的,这些类还要注册到注册中心去。 对于服务消费者来说,我们需要根据注解去作为代理去调用服务提供者的方法,我们需要针对这二个部分去设计对应的注解

2:项目初始化

整体项目架构如下

手撸RPC框架之RPC核心注解实现与扫描解析

我们先创建一个普通的maven父工程,pom.xml文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<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>com.xpc</groupId>
    <artifactId>xpc-rpc</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>xpc-rpc-annotation</module>
        <module>xpc-rpc-common</module>
        <module>xpc-rpc-provider</module>
        <module>xpc-rpc-consumer</module>
        <module>xpc-rpc-test</module>
    </modules>


    <properties>
        <string.version>5.2.20.RELEASE</string.version>
        <junit.version>4.12</junit.version>
        <slf4j.version>1.7.21</slf4j.version>
        <logback.version>1.1.7</logback.version>
        <common.logging>1.2</common.logging>
        <netty.version>4.1.77.Final</netty.version>
        <protostuff.version>1.0.8</protostuff.version>
        <zookeeper.version>3.5.5</zookeeper.version>
        <curator.version>2.12.0</curator.version>
        <commons.collections4.version>4.0</commons.collections4.version>
        <commons.lang3.version>3.12.0</commons.lang3.version>
        <objenesis.version>2.1</objenesis.version>
        <cglib.version>3.2.2</cglib.version>
        <bytebuddy.version>1.10.13</bytebuddy.version>
        <jackson.version>2.10.0</jackson.version>
        <javassist.version>3.21.0-GA</javassist.version>
        <hessian.version>4.0.63</hessian.version>
        <kyro.version>5.2.0</kyro.version>
        <fst.version>2.57</fst.version>
        <protobuf.version>3.11.0</protobuf.version>
        <fastjson.version>1.2.73</fastjson.version>
        <spring.boot.version>2.2.6.RELEASE</spring.boot.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!-- SLF4J -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${string.version}</version>
        </dependency>


        <!-- Netty -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty.version}</version>
        </dependency>

        <!-- Apache Commons Collections -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>${commons.collections4.version}</version>
        </dependency>

        <!--Apache Commons lang3-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons.lang3.version}</version>
        </dependency>


        <!-- Objenesis -->
        <dependency>
            <groupId>org.objenesis</groupId>
            <artifactId>objenesis</artifactId>
            <version>${objenesis.version}</version>
        </dependency>

        <!-- CGLib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>${cglib.version}</version>
        </dependency>

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>${javassist.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

接下来创建一个子工程 xpc-rpc-annotation 来定义我们的核心注解

3:核心注解的定义

3.1: 服务提供者的注解 com.xpc.rpc.annotation.DubboService

被@DubboService 注解标注的类,那么它将作为一个服务提供者来提供服务

package com.xpc.rpc.annotation;

import java.lang.annotation.*;

/**
 * 标记在接口实现类上,服务提供者的注解
 * @author baojiahao
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DubboService {

    /**
     * 接口的class
     * @return
     */
    Class<?> interfaceClass() default void.class;

    /**
     * 接口名称
     * @return
     */
    String interfaceName() default "";

}

3.2: 服务消费者的注解 com.xpc.rpc.annotation.DubboReference

被@DubboReference 注解标注的,那么它将作为一个服务消费者,它主要是标注在类的字段上

package com.xpc.rpc.annotation;

import java.lang.annotation.*;

/**
 * 服务消费者的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DubboReference {

    /**
     * 注册中心类型,zk,nacos,redis等
     */
    String registerType() default "zookeeper";

    /**
     * 注册地址
     */
    String registerAddress() default "127.0.0.1:2181";

    /**
     * 代理方式
     */
    String proxyType() default "jdk";

    /**
     * 负载均衡策略,默认是随机
     */
    String loadbalancerType() default "random";

    /**
     * 序列化方式
     */
    String serializationType() default "protostuff";

    /**
     * 是否异步调用
     */
    boolean async() default false;

    /**
     * 是否单向调用
     */
    boolean oneway() default false;


    /**
     * 调用超时时间,默认是5秒
     */
    long timeOut() default 5000;

}

4:核心注解的扫描和解析

核心注解设计完以后,接下来就是要在项目启动的时候去扫描这些注解了,首先我们要实现自定义的通用的扫描器

创建一个子工程 xpc-rpc-provider,pom.xml文件如下

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>xpc-rpc</artifactId>
        <groupId>com.xpc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>xpc-rpc-provider</artifactId>


    <dependencies>
        <dependency>
            <groupId>com.xpc</groupId>
            <artifactId>xpc-rpc-annotation</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.xpc</groupId>
            <artifactId>xpc-rpc-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

创建通用扫描器 com.xpc.rpc.provider.scanner.ClassScanner

package com.xpc.rpc.provider.scanner;

import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class ClassScanner {


    private static final String CLASS_FILE_SUFFIX = ".class";

    private static final String CHARSET_UTF = "UTF-8";
    
    
    /**
     * 根据传入的包名,来获取所有的.class文件
     * @param packageName 传入的包名
     * @return
     * @throws Exception
     */
    public static List<String> getClassList(String packageName) throws Exception{
        List<String> classList = new ArrayList<>();
        String packageDirName = packageName.replace(".","/");
        Enumeration<URL> fileDir = Thread.currentThread().getContextClassLoader().getResources(packageDirName);

        while (fileDir.hasMoreElements()) {
            //获取下一个元素
            URL url = fileDir.nextElement();
            //获取包的物理路径
            String filePath = URLDecoder.decode(url.getFile(), CHARSET_UTF);
            findClassByFile(filePath,packageName,classList);
        }
        return classList;
    }

    public static void findClassByFile(String filePath,String packageName, List<String> classList) {
        File dir = new File(filePath);
        //如果不存在,或者不是一个目录就直接返回
        if(!dir.exists() || !dir.isDirectory()) {
            return;
        }

        File[] listFiles = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isDirectory()) || file.getName().endsWith(CLASS_FILE_SUFFIX);
            }
        });

        for(File file : listFiles) {
            //如果是目录,就递归查询
            if(file.isDirectory()) {
                String fileAbsolutePath = file.getAbsolutePath();
                String[] fileSplits = fileAbsolutePath.split("/");
                String newPackageName = packageName + "." + fileSplits[fileSplits.length-1];
                findClassByFile(file.getAbsolutePath(),newPackageName,classList);
            }else {
                //如果是java文件,就去掉.class,只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                classList.add(packageName + "." +className);
            }
        }
    }
}

@DubboService注解扫描器 com.xpc.rpc.provider.scanner.service.DubboServiceScanner

package com.xpc.rpc.provider.scanner.service;

import com.xpc.rpc.annotation.DubboService;
import com.xpc.rpc.provider.scanner.ClassScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;

/**
 * 扫描@DubboService注解
 */
public class DubboServiceScanner extends ClassScanner {

    private static final Logger LOGGER = LoggerFactory.getLogger(DubboServiceScanner.class);

    /**
     * 根据指定的包名获取所有的class文件,并过滤出标注了@DubboService注解的class
     * @param packageName
     * @return
     * @throws Exception
     */
    public Map<String,Object> doScanDubboServiceByPackages(String packageName) throws Exception{
        Map<String,Object> serviceBeanMap = new HashMap<>();
        //获取包路径下所有的.class文件
        List<String> classList = getClassList(packageName);
        if(classList == null || classList.isEmpty()) {
            return serviceBeanMap;
        }
        classList.forEach(item -> {
            try {
                Class<?> clazz = Class.forName(item);
                if(clazz.isInterface()) {
                    //如果是接口,就不做处理
                    return;
                }
                DubboService dubboService = clazz.getAnnotation(DubboService.class);
                if(dubboService != null) {
                    String interfaceName = getServiceName(dubboService);
                    //TODO 将服务提供者注册到注册中心
                    register(interfaceName,dubboService);
                    //将服务提供者的实例缓存到全局缓存中
                    serviceBeanMap.put(interfaceName,clazz.newInstance());
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                LOGGER.error("scan DubboService @Interface error {}",e);
            }
        });
        return serviceBeanMap;
    }

    private void register(String interfaceName, DubboService dubboService) {
    }

    private String getServiceName(DubboService dubboService) {
        //先取接口的class
        Class<?> interfaceClass = dubboService.interfaceClass();
        if(interfaceClass != null) {
            return interfaceClass.getName();
        }
        //如果接口class为空,就去接口名称
        return dubboService.interfaceName();
    }
}

5:测试

写好了的程序怎么能够不测试呢,我们创建一个子工程 xpc-rpc-test

先定义一个接口 com.xpc.test.scanner.DemoService

public interface DemoService {
}

定义服务提供者 com.xpc.test.scanner.DemoServiceImp

package com.xpc.test.scanner;

import com.xpc.rpc.annotation.DubboService;

@DubboService(interfaceClass = DemoService.class)
public class DemoServiceImpl implements DemoService{
    
}

定义一个服务消费者com.xpc.test.scanner.DemoServiceConsumer

package com.xpc.test.scanner;

import com.xpc.rpc.annotation.DubboReference;

public class DemoServiceConsumer {


    @DubboReference
    private DemoService demoService;
}

编写测试类

package com.xpc.test.scanner;

import com.xpc.rpc.annotation.DubboReference;
import com.xpc.rpc.provider.scanner.reference.ReferenceScanner;
import com.xpc.rpc.provider.scanner.service.DubboServiceScanner;
import org.junit.Test;

import java.util.Map;

public class ScannerTest {


    @Test
    public void dubboServiceTest() throws Exception{
        DubboServiceScanner dubboServiceScanner = new DubboServiceScanner();
        Map<String, Object> stringObjectMap = dubboServiceScanner.doScanDubboServiceByPackages("com.xpc");
        for(String key : stringObjectMap.keySet()) {
            System.out.println("key = " + key);
            System.out.println("value = " + stringObjectMap.get(key));
        }
    }

    @Test
    public void dubboReferenceTest() throws Exception{
        ReferenceScanner referenceScanner = new ReferenceScanner();
        Map<String, Object> stringObjectMap = referenceScanner.doSacnDubboReferenceByPackage("com.xpc");
        for(String key : stringObjectMap.keySet()) {
            System.out.println("key = " + key);
            System.out.println("value = " + stringObjectMap.get(key));
        }
    }
}

服务提供者 手撸RPC框架之RPC核心注解实现与扫描解析

服务消费者 手撸RPC框架之RPC核心注解实现与扫描解析

至此,我们RPC核心的注解设置和扫描解析就已经完成了

转载自:https://juejin.cn/post/7252684645992529981
评论
请登录