手写RPC框架--10.实现包扫描

RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧)
RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧)

包扫描

  • 包扫描
    • a.实现包扫描批量发布服务
    • b.引入上下文配置
    • c.读取xml配置文件
    • d.引入dtd约束配置文件(拓展)

包扫描

a.实现包扫描批量发布服务

扫描包,进行批量发布的思路和逻辑也很简单,具体步骤如下:

  • 1.将包名转化为文件夹
  • 2.遍历文件夹下的所有class文件
  • 3.获取class文件的完整路径,转化为全限定名
  • 4.通过反射进行加载,封装成ServiceConfig对象,调用发布接口进行发布即可

在common模块下的com.dcyrpc包下,创建annotation

在该包中创建DcyRpcApi自定义注解类

  • @Retention(RetentionPolicy.RUNTIME) 运行时注解生效
  • @Target(ElementType.TYPE) 在类上使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DcyRpcApi {
}

provider-demo模块下的impl包,DcyRpcImpl类中

  • 加入自定义的注解
@DcyRpcApi
public class DcyRpcImpl implements DcyRpc {
    // 略...
}

provider-demo模块下的,Application启动类中

 // 发布服务
.publish(service)
// 批量扫包发布
.scan("com.dcyrpc")
// 启动服务
.start();

DcyRpcBootstrap类中,实现scan方法

  • 1.需要通过packageName获取其下的所有类的权限定名称
  • 2.通过反射,获取接口,构建具体的实现
  • 3.发布
/**
 * 批量扫包发布
 * @param packageName
 * @return
 */
public DcyRpcBootstrap scan(String packageName) {
    // 1.需要通过packageName获取其下的所有类的权限定名称
    List<String> classNameList = getAllClassNames(packageName);

    // 2.通过反射,获取接口,构建具体的实现
    List<Class<?>> classes = classNameList.stream()
            .map(className -> {
                try {
                    return Class.forName(className);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }).filter(clazz -> clazz.getAnnotation(DcyRpcApi.class) != null)
            .collect(Collectors.toList());

    for (Class<?> clazz : classes) {
        // 1.获取接口
        Class<?>[] interfaces = clazz.getInterfaces();
        Object instance;
        try {
            instance = clazz.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

        List<ServiceConfig<?>> serviceConfigList = new ArrayList<>();
        for (Class<?> anInterface : interfaces) {
            ServiceConfig<?> serviceConfig = new ServiceConfig<>();
            serviceConfig.setInterface(anInterface);
            serviceConfig.setRef(instance);
            serviceConfigList.add(serviceConfig);
        }

        // 3.发布
        publish(serviceConfigList);
    }

    return this;
}

/**
 * 获取所有类的权限定名称
 * @param packageName
 * @return
 */
private List<String> getAllClassNames(String packageName) {
    // 1.通过传入packageName获取绝对路径
    // com.dcyrpc.xxx.yyy -> D://xxx/xww/sss/com/dcyrpc/xxx/yyyl
    String basePath = packageName.replaceAll("\\.", "/");
    URL url = ClassLoader.getSystemClassLoader().getResource(basePath);

    if (url == null) {
        throw new RuntimeException("包扫描时路径不存在");
    }

    String absolutePath = url.getPath();

    List<String> classNameList = new ArrayList<>();
    classNameList = recursionFile(absolutePath, classNameList, basePath);

    return classNameList;
}

/**
 * 递归处理文件
 * @param absolutePath
 * @param classNameList
 * @param basePath
 * @return
 */
private List<String> recursionFile(String absolutePath, List<String> classNameList, String basePath) {
    // 1.获取文件
    File file = new File(absolutePath);
    // 2.判断文件是否是文件夹
    if (file.isDirectory()) {
        // 找到文件夹的所有的文件
        File[] children = file.listFiles(pathname -> pathname.isDirectory() || pathname.getPath().contains(".class"));
        if (children == null || children.length == 0) {
            return classNameList;
        }
        for (File child : children) {
            if (child.isDirectory()) {
                // 递归调用
                recursionFile(child.getAbsolutePath(), classNameList, basePath);
            } else {
                // 文件 --> 类的权限定名称
                String className = getCLassNameByAbsolutePath(child.getAbsolutePath(), basePath);
                classNameList.add(className);
            }
        }

    } else {
        // 文件 --> 类的权限定名称
        String className = getCLassNameByAbsolutePath(absolutePath, basePath);
        classNameList.add(className);
    }
    return classNameList;
}

/**
 * 通过绝对路径 获取 类的权限定名
 * @param absolutePath
 * @return
 */
private String getCLassNameByAbsolutePath(String absolutePath, String basePath) {
    String fileName = absolutePath.substring(absolutePath.indexOf(basePath.replaceAll("/", "\\\\"))).replaceAll("\\\\", ".");
    String substring = fileName.substring(0, fileName.indexOf(".class"));
    return substring;
}

b.引入上下文配置

所有的配置相关的内容全部定义在了启动引导程序中,这样其实有一些不合理,事实上全局配置我们应该统一放在一个类中:

  • 1.硬编码的形式配置
  • 2.设置默认配置
  • 3.独立配置(使用xml或者yaml等)

在core模块下的com.dcyrpc包下,创建config包,创建Configuration类:配置项

  • 定义成全局的单例
  • 定义成员变量为配置属性
  • 定义静态方法/静态类来完成配置
/**
 * 全局的配置类,代码配置-->xml配置-->默认项
 *  - 代码配置由引导程序完成
 */
@Data
public class Configuration {

    // 端口号
    private int port = 8083;

    // 应用程序的名字
    private String applicationName = "default";

    // 注册中心
    private RegistryConfig registryConfig;

    // 序列化协议
    private ProtocolConfig protocolConfig;

    // 序列化使用的协议
    private String serializeType = "jdk";

    // 压缩使用的协议
    private String compressTrpe = "gzip";

    // id生成器
    private IdGenerator idGenerator = new IdGenerator(1, 2);

    // 负载均衡策略
    private LoadBalancer loadBalancer = new RoundRobinLoadBalancer();

    // 读xml
    public Configuration() {
        // 读取xml上的配置信息
    }
}

修改DcyRpcBootstrap类的相关变量:

  • 将已在配置类中定义的变量删除
  • 获取Configuration配置类的对象
  • 将报错的内容都改为,从配置类中获取
@Slf4j
public class DcyRpcBootstrap {

    // 单例
    private static final DcyRpcBootstrap dcyRpcBootstrap = new DcyRpcBootstrap();

    // 全局的配置中心
    private Configuration configuration;

    // 保证request对象,可以在当前线程中随时获取
    public static final ThreadLocal<DcyRpcRequest> REQUEST_THREAD_LOCAL = new ThreadLocal<>();

    // Netty的连接缓存
    public static final Map<InetSocketAddress, Channel> CHANNEL_CACHE = new ConcurrentHashMap<>(16);

    // 响应时间的缓存
    public static final TreeMap<Long, Channel> ANSWER_TIME_CHANNEL_CACHE = new TreeMap<>();

    // 维护已经发布且暴露的服务列表 key:interface的全限定名  value:ServiceConfig
    public static final Map<String, ServiceConfig<?>> SERVERS_LIST = new HashMap<>(16);

    // 定义全局的对外挂起的 completableFuture
    public static final Map<Long, CompletableFuture<Object>> PENDING_REQUEST = new HashMap<>(128);

    private DcyRpcBootstrap(){
        // 构造启动引导程序时,需要做一些什么初始化的事
        configuration = new Configuration();
    }

    public static DcyRpcBootstrap getInstance() {
        return dcyRpcBootstrap;
    }

    /**
     * 定义当前应用的名字
     * @param applicationName 应用名称
     * @return
     */
    public DcyRpcBootstrap application(String applicationName) {
        configuration.setApplicationName(applicationName);
        return this;
    }

    /**
     * 配置注册中心
     * @param registryConfig 注册中心
     * @return this
     */
    public DcyRpcBootstrap registry(RegistryConfig registryConfig) {
        // 维护一个zookeeper实例,但是,如果这样写就会将zookeeper和当前的工程耦合
        // 使用 registryConfig 获取一个注册中心
        configuration.setRegistryConfig(registryConfig);
        return this;
    }

    /**
     * 配置负责均衡策略
     * @param loadBalancer
     * @return this
     */
    public DcyRpcBootstrap loadBalancer(LoadBalancer loadBalancer) {
        configuration.setLoadBalancer(loadBalancer);
        return this;
    }

    /**
     * 配置当前暴露的服务使用的协议
     * @param protocolConfig 协议的封装
     * @return this
     */
    public DcyRpcBootstrap protocol(ProtocolConfig protocolConfig) {
        configuration.setProtocolConfig(protocolConfig);
        log.info("当前工程使用了:{}协议进行序列化", protocolConfig.toString());
        return this;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    /**
     * --------------------------------服务提供方的相关api--------------------------------
     */

    /**
     * 发布服务:将接口与匹配的实现注册到服务中心
     * @param service 封装需要发布的服务
     * @return
     */
    public DcyRpcBootstrap publish(ServiceConfig<?> service) {
        // 抽象了注册中心的概念,使用注册中心的一个实现完成注册
        configuration.getRegistryConfig().getRegistry().register(service);
        // 1.当服务调用方,通过接口、方法名、具体的方法参数列表 发起调用,提供方怎么知道使用哪一个实现
        //  (1) new 1 个
        //  (2) spring beanFactory.getBean(Class)
        //  (3) 自己维护映射关系
        SERVERS_LIST.put(service.getInterface().getName(),  service);
        return this;
    }

    /**
     * 批量发布服务
     * @param services 封装需要发布的服务集合
     * @return this
     */
    public DcyRpcBootstrap publish(List<ServiceConfig<?>> services) {
        for (ServiceConfig<?> service : services) {
            this.publish(service);
        }
        return this;
    }

    /**
     * 批量扫包发布
     * @param packageName
     * @return
     */
    public DcyRpcBootstrap scan(String packageName) {
        // 1.需要通过packageName获取其下的所有类的权限定名称
        List<String> classNameList = getAllClassNames(packageName);

        // 2.通过反射,获取接口,构建具体的实现
        List<Class<?>> classes = classNameList.stream()
                .map(className -> {
                    try {
                        return Class.forName(className);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }).filter(clazz -> clazz.getAnnotation(DcyRpcApi.class) != null)
                .collect(Collectors.toList());

        for (Class<?> clazz : classes) {
            // 1.获取接口
            Class<?>[] interfaces = clazz.getInterfaces();
            Object instance;
            try {
                instance = clazz.getConstructor().newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }

            for (Class<?> anInterface : interfaces) {
                ServiceConfig<?> serviceConfig = new ServiceConfig<>();
                serviceConfig.setInterface(anInterface);
                serviceConfig.setRef(instance);

                log.info("-------->已经通过包扫描,将服务【{}】发布", anInterface);

                // 3.发布
                publish(serviceConfig);
            }

        }

        return this;
    }

    /**
     * 获取所有类的权限定名称
     * @param packageName
     * @return
     */
    private List<String> getAllClassNames(String packageName) {
        // 1.通过传入packageName获取绝对路径
        // com.dcyrpc.xxx.yyy -> D://xxx/xww/sss/com/dcyrpc/xxx/yyyl
        String basePath = packageName.replaceAll("\\.", "/");
        URL url = ClassLoader.getSystemClassLoader().getResource(basePath);

        if (url == null) {
            throw new RuntimeException("包扫描时路径不存在");
        }

        String absolutePath = url.getPath();

        List<String> classNameList = new ArrayList<>();
        classNameList = recursionFile(absolutePath, classNameList, basePath);

        return classNameList;
    }

    /**
     * 递归处理文件
     * @param absolutePath
     * @param classNameList
     * @param basePath
     * @return
     */
    private List<String> recursionFile(String absolutePath, List<String> classNameList, String basePath) {
        // 1.获取文件
        File file = new File(absolutePath);
        // 2.判断文件是否是文件夹
        if (file.isDirectory()) {
            // 找到文件夹的所有的文件
            File[] children = file.listFiles(pathname -> pathname.isDirectory() || pathname.getPath().contains(".class"));
            if (children == null || children.length == 0) {
                return classNameList;
            }
            for (File child : children) {
                if (child.isDirectory()) {
                    // 递归调用
                    recursionFile(child.getAbsolutePath(), classNameList, basePath);
                } else {
                    // 文件 --> 类的权限定名称
                    String className = getCLassNameByAbsolutePath(child.getAbsolutePath(), basePath);
                    classNameList.add(className);
                }
            }

        } else {
            // 文件 --> 类的权限定名称
            String className = getCLassNameByAbsolutePath(absolutePath, basePath);
            classNameList.add(className);
        }
        return classNameList;
    }

    /**
     * 通过绝对路径 获取 类的权限定名
     * @param absolutePath
     * @return
     */
    private String getCLassNameByAbsolutePath(String absolutePath, String basePath) {
        String fileName = absolutePath.substring(absolutePath.indexOf(basePath.replaceAll("/", "\\\\"))).replaceAll("\\\\", ".");
        String substring = fileName.substring(0, fileName.indexOf(".class"));
        return substring;
    }

    /**
     * 启动netty服务
     */
    public void start() {
        // 1.创建EventLoopGroup,老板只负责处理请求,之后会将请求分发给worker,1比2的比例
        NioEventLoopGroup boss = new NioEventLoopGroup(2);
        NioEventLoopGroup worker = new NioEventLoopGroup(10);

        try{
            // 2.服务器端启动辅助对象
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            // 3.配置服务器
            serverBootstrap = serverBootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // TODO 核心内容,需要添加很多入栈和出栈的handler
                            socketChannel.pipeline().addLast(new LoggingHandler(LogLevel.INFO))
                                    // 对报文进行解码
                                    .addLast(new DcyRpcRequestDecoder())
                                    // 根据请求进行方法调用
                                    .addLast(new MethodCallHandler())
                                    // 对响应结果进行编码
                                    .addLast(new DcyRpcResponseEncoder());
                        }
                    });

            // 4.绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(configuration.getPort()).sync();

            // 5.阻塞操作
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                boss.shutdownGracefully().sync();
                worker.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * --------------------------------服务调用方的相关api--------------------------------
     */

    public DcyRpcBootstrap reference(ReferenceConfig<?> reference) {
        // 启动心跳检测
        log.info("开始心跳检测");
        HeartbeatDetector.detectHeartbeat(reference.getInterface().getName());

        // 配置reference,将来调用get方法时,方便生成代理对象
        // 1.reference需要一个注册中心
        reference.setRegistry(configuration.getRegistryConfig().getRegistry());
        return this;
    }

    /**
     * 配置序列化的方式
     * @param serializeType
     * @return
     */
    public DcyRpcBootstrap serialize(String serializeType) {
        configuration.setSerializeType(serializeType);
        log.info("配置的序列化方式为【{}】", serializeType);
        return this;
    }

    /**
     * 配置压缩的方式
     * @param compressType
     * @return
     */
    public DcyRpcBootstrap compress(String compressType) {
        configuration.setCompressType(compressType);
        log.info("配置的压缩算法为【{}】", compressType);
        return this;
    }
}

因添加了配置类,已经在启动类中删除了部分内容,所以会有许多类报错。按照启动类的写法,重新修改报错的代码

c.读取xml配置文件

在core模块下创建resources文件夹,创建dcyrpc.xml文件

  • 将该文件复制粘贴到provider-demo模块下的resources文件夹


<configuration>

    
    <port>8099port>
    
    <appName>dcyrpc-default-appNameappName>
    
    <registry url="zookeeper://127.0.0.1:2181"/>

    
    <compressType type="gzip"/>
    <compressor class="com.dcyrpc.compress.impl.GzipCompressor"/>

    
    <serializeType type="Hessian"/>
    <serializr class="com.dcyrpc.serialize.impl.HessianSerializer" />

    
    <loadBalancerType type="RoundRobin"/>
    <loadBalancer class="com.dcyrpc.loadbalancer.impl.RoundRobinLoadBalancer"/>

    
    <idGenerator class="com.dcyrpc.IdGenerator" dataCenterId="2" machineId="4"/>

configuration>

在core模块下的config.Configuration配置类的构造器中,写入代码:读取xml的配置信息

public Configuration() {
    // 读取xml上的配置信息
    XmlResolver xmlResolver = new XmlResolver();
    xmlResolver.loadFromXml(this);
}

在core模块下的config包下,创建XmlResolver类:xml解析类

/**
 * 解析xml的配置
 */
@Slf4j
public class XmlResolver {

    /**
     * 读取xml上的配置信息
     * @param configuration
     */
    public void loadFromXml(Configuration configuration) {

        try {
            // 1.创建一个document
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();

            // 2.获取一个xpath的解析器
            InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("dcyrpc.xml");
            Document doc = builder.parse(inputStream);
            XPathFactory xPathFactory = XPathFactory.newInstance();
            XPath xPath = xPathFactory.newXPath();

            // 3.解析所有的标签
            configuration.setPort(resolvePort(xPath, doc));
            configuration.setApplicationName(resolveAppName(xPath, doc));
            configuration.setRegistryConfig(resolveRegistryConfig(xPath, doc));
            configuration.setCompressType(resolveCompressType(xPath, doc));
            configuration.setCompressor(resolveCompressCompressor(xPath, doc));
            configuration.setSerializeType(resolveSerializeType(xPath, doc));
            configuration.setSerializer(resolveSerializer(xPath, doc));
            configuration.setProtocolConfig(new ProtocolConfig(configuration.getSerializeType()));
            configuration.setLoadBalancer(resolveLoadBalancer(xPath, doc));
            configuration.setIdGenerator(resolveIdGenerator(xPath, doc));

        } catch (ParserConfigurationException | SAXException | IOException e) {
            log.error("解析xml配置文件时发送异常", e);
        }
    }

    /**
     * 解析端口
     * @param xPath
     * @param doc
     * @return
     */
    private int resolvePort(XPath xPath, Document doc) {
        String portExpression = "/configuration/port";
        String portString = parseString(xPath, doc, portExpression);
        return Integer.parseInt(portString);
    }

    /**
     * 解析应用名称
     * @param xPath
     * @param doc
     * @return
     */
    private String resolveAppName(XPath xPath, Document doc) {
        String portExpression = "/configuration/appName";
        return parseString(xPath, doc, portExpression);
    }

    /**
     * 解析注册中心
     * @param xPath
     * @param doc
     * @return
     */
    private RegistryConfig resolveRegistryConfig(XPath xPath, Document doc) {
        String expression = "/configuration/registry";
        String url = parseString(xPath, doc, expression, "url");
        return new RegistryConfig(url);
    }

    /**
     * 解析压缩方式
     * @param xPath
     * @param doc
     * @return
     */
    private String resolveCompressType(XPath xPath, Document doc) {
        String expression = "/configuration/compressType";
        return parseString(xPath, doc, expression,  "type");
    }

    /**
     * 解析压缩的具体实现
     * @param xPath
     * @param doc
     * @return
     */
    private Compressor resolveCompressCompressor(XPath xPath, Document doc) {
        String expression = "/configuration/compressor";
        return parseObject(xPath, doc, expression, null);
    }

    /**
     * 解析序列化类型
     * @param xPath
     * @param doc
     * @return
     */
    private String resolveSerializeType(XPath xPath, Document doc) {
        String expression = "/configuration/serializeType";
        return parseString(xPath, doc, expression, "type");
    }

    /**
     * 解析序列化器
     * @param xPath
     * @param doc
     * @return
     */
    private Serializer resolveSerializer(XPath xPath, Document doc) {
        String expression = "/configuration/serializr";
        return parseObject(xPath, doc, expression, null);
    }


    /**
     * 解析负载均衡策略
     * @param xPath
     * @param doc
     * @return
     */
    private LoadBalancer resolveLoadBalancer(XPath xPath, Document doc) {
        String expression = "/configuration/loadBalancer";
        return parseObject(xPath, doc, expression, null);
    }

    /**
     * 解析Id生成器
     * @param xPath
     * @param doc
     * @return
     */
    private IdGenerator resolveIdGenerator(XPath xPath, Document doc) {
        String portExpression = "/configuration/idGenerator";
        String classString = parseString(xPath, doc, portExpression, "class");
        String dataCenterIdString = parseString(xPath, doc, portExpression, "dataCenterId");
        String machineIdString = parseString(xPath, doc, portExpression, "machineId");

        try {
            Class<?> clazz = Class.forName(classString);
            IdGenerator instance = (IdGenerator) clazz.getConstructor(new Class[]{long.class, long.class})
                    .newInstance(Long.parseLong(dataCenterIdString), Long.parseLong(machineIdString));
            return instance;
        }catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
                InvocationTargetException e) {

        }
        return null;
    }

    /**
     * 获得一个节点文本的值 7777
     * @param xPath xpath解析器
     * @param doc 文档对象
     * @param expression xpath表达式
     * @return 节点的值
     */
    private String parseString(XPath xPath, Document doc, String expression) {

        try {
            XPathExpression expr = xPath.compile(expression);
            Node targetNode = (Node) expr.evaluate(doc, XPathConstants.NODE);
            return targetNode.getTextContent();
        } catch (XPathExpressionException e) {
            log.error("解析表达式时发生异常", e);
        }

        return null;
    }

    /**
     * 获得一个节点属性的值 
     * @param xPath xpath解析器
     * @param doc 文档对象
     * @param expression xpath表达式
     * @param attributeName 节点名称
     * @return 节点的值
     */
    private String parseString(XPath xPath, Document doc, String expression, String attributeName) {

        try {
            XPathExpression expr = xPath.compile(expression);
            Node targetNode = (Node) expr.evaluate(doc, XPathConstants.NODE);
            return targetNode.getAttributes().getNamedItem(attributeName).getNodeValue();
        } catch (XPathExpressionException e) {
            log.error("解析表达式时发生异常", e);
        }

        return null;
    }

    /**
     * 解析一个节点,返回一个实例
     * @param xPath xpath解析器
     * @param doc 文档对象
     * @param expression xpath表达式
     * @param paramType 参数列表
     * @param param 参数
     * @return 配置的实例
     * @param 
     */
    private <T> T parseObject(XPath xPath, Document doc, String expression, Class<?>[] paramType, Object... param) {

        try {
            XPathExpression expr = xPath.compile(expression);
            Node targetNode = (Node) expr.evaluate(doc, XPathConstants.NODE);
            String className = targetNode.getAttributes().getNamedItem("class").getNodeValue();
            Class<?> clazz = Class.forName(className);

            Object instance = null;
            if (paramType == null) {
                instance = clazz.getConstructor().newInstance();
            } else {
                instance = clazz.getConstructor(paramType).newInstance(param);
            }

            return (T) instance;
        } catch (XPathExpressionException | ClassNotFoundException | NoSuchMethodException | InstantiationException |
                 IllegalAccessException | InvocationTargetException e) {
            log.error("解析表达式时发生异常", e);
        }

        return null;
    }
    
}

d.引入dtd约束配置文件(拓展)

在core模块下的com.dcyrpc包下,创建builder.xml文件夹,创建dcyrpc.xml文件:约束文件(规范xml文件的格式)

<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT configuration (port?, appName?, registry?,
        serializeType?, serializr?,
        compressType?, compressor?
        loadBalancerType?, loadBalancer?
        idGenerator?)>

<!ELEMENT port ANY>

<!ELEMENT appName ANY>

<!ELEMENT registry EMPTY>
<!ATTLIST registry
        url CDATA #REQUIRED>

<!ELEMENT serializeType EMPTY>
<!ATTLIST serializeType
        type CDATA #REQUIRED>

<!ELEMENT serializr EMPTY>
<!ATTLIST serializr
        class CDATA #REQUIRED>

<!ELEMENT compressType EMPTY>
<!ATTLIST compressType
        type CDATA #REQUIRED>

<!ELEMENT compressor EMPTY>
<!ATTLIST compressor
        class CDATA #REQUIRED>

<!ELEMENT loadBalancerType EMPTY>
<!ATTLIST loadBalancerType
        type CDATA #REQUIRED>

<!ELEMENT loadBalancer EMPTY>
<!ATTLIST loadBalancer
        class CDATA #REQUIRED>

<!ELEMENT idGenerator EMPTY>
<!ATTLIST idGenerator
        class CDATA #REQUIRED
        dataCenterId CDATA #REQUIRED
        machineid CDATA #REQUIRED>

在core模块下的resources文件夹下的dcyrpc.xml文件:配置dtd的约束文件

DOCTYPE configuration SYSTEM "dcyrpc-config.dtd" >

你可能感兴趣的:(手写RPC框架,rpc,网络协议,网络,spring,设计模式,java)