一. 全局配置加载
1. 需求分析
通常情况下,在RPC框架运行的会涉及到多种配置信息,比如注册中心的地址、序列化方式、网络服务端接口号等。
在简易版框架中,硬编码了这些配置,也就是都写死了,在真实的应用环境中是不利于维护和后期扩展的。同时RPC框架需要被其它项目引入,作为服务提供者和消费者沟通的桥梁,所以应当允许引入框架的项目通过编写配置文件来自定义配置。一般情况下,服务提供者与消费者需要编写相同的RPC配置。
综上,我们需要一套全局配置加载功能。能够让RPC框架轻松地从配置文件中读取配置,并且维护一个全局配置对象,便于框架快速获取到一致的配置。
2. 设计方案
(1)配置项
从最简单出发,先提供几个基础配置项:
- name 名称
- version 版本号
- serverHost 服务器主机名
- serverPort 服务器端口号
之后随着框架功能的扩展再不断增加新配置即可,比如注册中心地址、服务接口、序列化方式等。
可参考:Dubbo RPC框架的配置项。包括应用配置、协议配置、注册中心等。
(2)读取配置文件
配置文件的读取,使用Java的Properties类自行编写。通常情况下,读取的配置文件名称为application.properties,还可以通过指定文件名称后缀的方式来区分多环境,比如application-prod.properties表示生产环境,application-test.properties表示测试环境。
3. 具体实现
(1)项目初始化
创建khr-rpc-core模块,扩展版RPC项目都基于此模块进行。直接复制粘贴easy模块包并改名。
引入日志库(ch.qos.logback)和单元测试(junit)依赖,并将consumer和provider模块引入的RPC依赖都替换成khr-rpc-core。
(2)配置加载
创建配置类RpcConfig:
用于保存配置信息。
可以给属性指定一些默认值,
package com.khr.krpc.config;import lombok.Data;/*** RPC框架配置*/
@Data
public class RpcConfig {/*** 名称*/private String name = "k-rpc";/*** 版本号*/private String version = "1.0";/*** 服务器主机名*/private String serverHost = "localhost";/*** 服务器端口号*/private String serverPort = "8080";
}
创建工具类ConfigUtils:
用于读取配置文件并返回配置对象,可以简化调用。配置类应当尽量通用,不和业务强绑定。
之后调用ConfigUtils的静态方法loadConfig就能读取配置了。
package com.khr.krpc.utils;import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.dialect.Props;/*** 配置工具类*/
public class ConfigUtils {/*** 加载配置对象** @param tClass* @param perfix* @param <T>* @return*/public static <T> T loadConfig(Class<T> tClass,String perfix){return loadConfig(tClass,perfix,"");}/*** 加载配置对象,支持区分环境** @param tClass* @param perfix* @param environment* @param <T>* @return*/public static <T> T loadConfig(Class<T> tClass,String perfix,String environment){StringBuilder configFileBuilder = new StringBuilder("application");if (StrUtil.isNotBlank(environment)){configFileBuilder.append("-").append(environment);}configFileBuilder.append(".properties");Props props = new Props(configFileBuilder.toString());return props.toBean(tClass,perfix);}
}
创建RpcConstant接口:
用于存储RPC框架相关的常量。比如默认配置文件的加载前缀为rpc。
package com.khr.krpc.constant;/*** RPC相关常量*/
public interface RpcConstant {/*** 默认配置文件加载前缀*/String DEFAULT_CONFIG_PREFIX = "rpc";
}
可以读取到类似下面的配置:
rpc.name=krpc
rpc.version=2.0
rpc.serverPort=8081
(3)维护全局配置对象
RPC框架中需要维护一个全局的配置对象。在引入RPC框架后并启动项目时,从配置文件中读取配置并创建对象实例,之后就可以集中地从这个对象中获取配置信息,而不需要每次加载配置时再重新读取并创建对象,减少了性能开销。
使用了设计模式中的单例模式。通常情况下会使用holder来维护全局配置对象实例,在本项目中使用RpcApplication类作为RPC项目的启动入口,并维护项目全局用到的变量。
package com.khr.krpc;import com.khr.krpc.config.RpcConfig;
import com.khr.krpc.constant.RpcConstant;
import com.khr.krpc.utils.ConfigUtils;
import lombok.extern.slf4j.Slf4j;/*** RPC框架应用* 相当于holder,存放了项目全局用到的变量。双检锁单例模式实现。*/
@Slf4j
public class RpcApplication {private static volatile RpcConfig rpcConfig;/*** 框架初始化,支持传入自定义配置** @param newRpcConfig*/public static void init(RpcConfig newRpcConfig){rpcConfig = newRpcConfig;log.info("rpc init, config = {}",newRpcConfig.toString());}/*** 初始化*/public static void init(){RpcConfig newRpcConfig;try {newRpcConfig = ConfigUtils.loadConfig(RpcConfig.class,RpcConstant.DEFAULT_CONFIG_PREFIX);}catch (Exception e){//配置加载失败,使用默认值newRpcConfig =new RpcConfig();}init(newRpcConfig);}/*** 获取配置** @return*/public static RpcConfig getRpcConfig(){if (rpcConfig == null){synchronized (RpcApplication.class){if (rpcConfig == null){init();}}}return rpcConfig;}
}
双检锁单例模式的经典实现,支持在获取配置时才调用init方法实现懒加载。
为了便于扩展,还支持自己传入配置对象,如果不传入的话,默认调用前面写好的ConfigUtils来加载配置。
之后一行代码即可正确加载配置:
RpcConfig rpc = RpcApplication.getRpcConfig();
4. 测试
(1)测试配置文件读取
在example-consumer模块的resources目录下编写配置文件application.properties,
rpc.name=krpc
rpc.version=2.0
rpc.serverPort=8081
创建ConsumerExample作为扩展版RPC项目的示例消费者类,测试配置文件读取,
package com.khr.example.consumer;import com.khr.example.common.model.User;
import com.khr.example.common.service.UserService;
import com.khr.krpc.config.RpcConfig;
import com.khr.krpc.proxy.ServiceProxyFactory;
import com.khr.krpc.utils.ConfigUtils;/*** 服务消费者示例*/
public class ConsumerExample {public static void main(String[] args){RpcConfig rpc = ConfigUtils.loadConfig(RpcConfig.class,"rpc");System.out.println(rpc);}
}
读取结果为,
已成功读到配置文件中的内容。
(2)测试全局配置对象加载
在example-provider模块中创建ProviderExample服务提供者示例类,能够根据配置动态地在不同端口启动Web服务,
package com.khr.example.provider;import com.khr.example.common.service.UserService;
import com.khr.krpc.RpcApplication;
import com.khr.krpc.registry.LocalRegistry;
import com.khr.krpc.server.HttpServer;
import com.khr.krpc.server.VertxHttpServer;/*** 服务提供者示例*/
public class ProviderExample {public static void main(String[] args){//RPC框架初始化RpcApplication.init();//注册服务LocalRegistry.registry(UserService.class.getName(),UserServiceImpl.class);//启动web服务HttpServer httpServer = new VertxHttpServer();httpServer.doStart(Integer.parseInt(RpcApplication.getRpcConfig().getServerPort()));}
}
启动结果为,
已成功在8080端口启动。
至此,扩展功能,全局配置加载完成,后续可能会根据新增的功能逐步修改全局配置信息。