Skip to content

Commit

Permalink
[v3.0] 服务提供侧注解自动注册服务
Browse files Browse the repository at this point in the history
  • Loading branch information
CN-GuoZiyang committed Jun 24, 2020
1 parent 9c79075 commit dd7d02f
Show file tree
Hide file tree
Showing 21 changed files with 334 additions and 98 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ My-RPC-Framework 是一款基于 Nacos 实现的 RPC 框架。网络传输实现
- 如消费端和提供者都采用 Netty 方式,会采用 Netty 的心跳机制,保证连接
- 接口抽象良好,模块耦合度低,网络传输、序列化器、负载均衡算法可配置
- 实现自定义的通信协议
- 服务提供侧自动注册服务

## 项目模块概览

Expand Down Expand Up @@ -72,6 +73,7 @@ package top.guoziyang.test;

import top.guoziyang.rpc.api.HelloService;

@Service
public class HelloServiceImpl implements HelloService {
@Override
public String hello(String name) {
Expand All @@ -89,11 +91,11 @@ import top.guoziyang.rpc.api.HelloService;
import top.guoziyang.rpc.serializer.CommonSerializer;
import top.guoziyang.rpc.transport.netty.server.NettyServer;

@ServiceScan
public class NettyTestServer {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
NettyServer server = new NettyServer("127.0.0.1", 9999, CommonSerializer.PROTOBUF_SERIALIZER);
server.publishService(helloService, HelloService.class);
server.start();
}
}
```
Expand Down
10 changes: 10 additions & 0 deletions rpc-api/src/main/java/top/guoziyang/rpc/api/ByeService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package top.guoziyang.rpc.api;

/**
* @author ziyang
*/
public interface ByeService {

String bye(String name);

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
@Getter
public enum RpcError {

UNKNOWN_ERROR("出现未知错误"),
SERVICE_SCAN_PACKAGE_NOT_FOUND("启动类ServiceScan注解缺失"),
CLIENT_CONNECT_SERVER_FAILURE("客户端连接服务端失败"),
SERVICE_INVOCATION_FAILURE("服务调用出现失败"),
SERVICE_NOT_FOUND("找不到对应的服务"),
Expand Down
152 changes: 152 additions & 0 deletions rpc-common/src/main/java/top/guoziyang/rpc/util/ReflectUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package top.guoziyang.rpc.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
* @author ziyang
*/
public class ReflectUtil {

public static String getStackTrace() {
StackTraceElement[] stack = new Throwable().getStackTrace();
return stack[stack.length - 1].getClassName();
}

public static Set<Class<?>> getClasses(String packageName) {
Set<Class<?>> classes = new LinkedHashSet<>();
boolean recursive = true;
String packageDirName = packageName.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(
packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findAndAddClassesInPackageByFile(packageName, filePath,
recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection())
.getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packageDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx)
.replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class")
&& !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(
packageName.length() + 1, name
.length() - 6);
try {
// 添加到classes
classes.add(Class
.forName(packageName + '.'
+ className));
} catch (ClassNotFoundException e) {
// log
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}

return classes;
}

private static void findAndAddClassesInPackageByFile(String packageName,
String packagePath, final boolean recursive, Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(packagePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
public boolean accept(File file) {
return (recursive && file.isDirectory())
|| (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findAndAddClassesInPackageByFile(packageName + "."
+ file.getName(), file.getAbsolutePath(), recursive,
classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0,
file.getName().length() - 6);
try {
// 添加到集合中去
//classes.add(Class.forName(packageName + '.' + className));
//经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}

}
18 changes: 18 additions & 0 deletions rpc-core/src/main/java/top/guoziyang/rpc/annotation/Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package top.guoziyang.rpc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 表示一个服务提供类,用于远程接口的实现类
* @author ziyang
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {

public String name() default "";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package top.guoziyang.rpc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 服务扫描的基包
* @author ziyang
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceScan {

public String value() default "";

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
public interface ServiceProvider {


<T> void addServiceProvider(T service, Class<T> serviceClass);
<T> void addServiceProvider(T service, String serviceName);

Object getServiceProvider(String serviceName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ public class ServiceProviderImpl implements ServiceProvider {
private static final Set<String> registeredService = ConcurrentHashMap.newKeySet();

@Override
public <T> void addServiceProvider(T service, Class<T> serviceClass) {
String serviceName = serviceClass.getCanonicalName();
public <T> void addServiceProvider(T service, String serviceName) {
if (registeredService.contains(serviceName)) return;
registeredService.add(serviceName);
serviceMap.put(serviceName, service);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.alibaba.nacos.api.naming.pojo.Instance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.guoziyang.rpc.enumeration.RpcError;
import top.guoziyang.rpc.exception.RpcException;
import top.guoziyang.rpc.loadbalancer.LoadBalancer;
import top.guoziyang.rpc.loadbalancer.RandomLoadBalancer;
import top.guoziyang.rpc.util.NacosUtil;
Expand All @@ -29,6 +31,10 @@ public NacosServiceDiscovery(LoadBalancer loadBalancer) {
public InetSocketAddress lookupService(String serviceName) {
try {
List<Instance> instances = NacosUtil.getAllInstance(serviceName);
if(instances.size() == 0) {
logger.error("找不到对应的服务: " + serviceName);
throw new RpcException(RpcError.SERVICE_NOT_FOUND);
}
Instance instance = loadBalancer.select(instances);
return new InetSocketAddress(instance.getIp(), instance.getPort());
} catch (NacosException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package top.guoziyang.rpc.transport;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.guoziyang.rpc.annotation.Service;
import top.guoziyang.rpc.annotation.ServiceScan;
import top.guoziyang.rpc.enumeration.RpcError;
import top.guoziyang.rpc.exception.RpcException;
import top.guoziyang.rpc.provider.ServiceProvider;
import top.guoziyang.rpc.registry.ServiceRegistry;
import top.guoziyang.rpc.util.ReflectUtil;

import java.net.InetSocketAddress;
import java.util.Set;

/**
* @author ziyang
*/
public abstract class AbstractRpcServer implements RpcServer {

protected Logger logger = LoggerFactory.getLogger(this.getClass());

protected String host;
protected int port;

protected ServiceRegistry serviceRegistry;
protected ServiceProvider serviceProvider;

public void scanServices() {
String mainClassName = ReflectUtil.getStackTrace();
Class<?> startClass;
try {
startClass = Class.forName(mainClassName);
if(!startClass.isAnnotationPresent(ServiceScan.class)) {
logger.error("启动类缺少 @ServiceScan 注解");
throw new RpcException(RpcError.SERVICE_SCAN_PACKAGE_NOT_FOUND);
}
} catch (ClassNotFoundException e) {
logger.error("出现未知错误");
throw new RpcException(RpcError.UNKNOWN_ERROR);
}
String basePackage = startClass.getAnnotation(ServiceScan.class).value();
if("".equals(basePackage)) {
basePackage = mainClassName.substring(0, mainClassName.lastIndexOf("."));
}
Set<Class<?>> classSet = ReflectUtil.getClasses(basePackage);
for(Class<?> clazz : classSet) {
if(clazz.isAnnotationPresent(Service.class)) {
String serviceName = clazz.getAnnotation(Service.class).name();
Object obj;
try {
obj = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
logger.error("创建 " + clazz + " 时有错误发生");
continue;
}
if("".equals(serviceName)) {
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> oneInterface: interfaces){
publishService(obj, oneInterface.getCanonicalName());
}
} else {
publishService(obj, serviceName);
}
}
}
}

@Override
public <T> void publishService(T service, String serviceName) {
serviceProvider.addServiceProvider(service, serviceName);
serviceRegistry.register(serviceName, new InetSocketAddress(host, port));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ public Object invoke(Object proxy, Method method, Object[] args) {
method.getName(), args, method.getParameterTypes(), false);
RpcResponse rpcResponse = null;
if (client instanceof NettyClient) {
CompletableFuture<RpcResponse> completableFuture = (CompletableFuture<RpcResponse>) client.sendRequest(rpcRequest);
try {
CompletableFuture<RpcResponse> completableFuture = (CompletableFuture<RpcResponse>) client.sendRequest(rpcRequest);
rpcResponse = completableFuture.get();
} catch (InterruptedException | ExecutionException e) {
} catch (Exception e) {
logger.error("方法调用请求发送失败", e);
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ public interface RpcServer {

void start();

<T> void publishService(T service, Class<T> serviceClass);
<T> void publishService(T service, String serviceName);

}
Loading

0 comments on commit dd7d02f

Please sign in to comment.