RPC框架-Gitee代码(麻烦点个Starred, 支持一下吧)
RPC框架-GitHub代码(麻烦点个Starred, 支持一下吧)
扫描包,进行批量发布的思路和逻辑也很简单,具体步骤如下:
在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方法
/**
* 批量扫包发布
* @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;
}
所有的配置相关的内容全部定义在了启动引导程序中,这样其实有一些不合理,事实上全局配置我们应该统一放在一个类中:
在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;
}
}
因添加了配置类,已经在启动类中删除了部分内容,所以会有许多类报错。按照启动类的写法,重新修改报错的代码
在core模块下创建resources
文件夹,创建dcyrpc.xml
文件
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;
}
}
在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" >