1.需求介绍
dubbo 底层使用了 Netty 作为网络通讯框架,要求用 Netty 实现一个简单的 RPC 框架,消费者和提
供者约定接口和协议,消费者远程调用提供者的服务,
1. 创建一个接口,定义抽象方法。用于消费者和提供者之间的约定,
2. 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据
3. 创建一个消费者,该类需要透明的调用自己不存在的方法,通过controller方式进行轮询访问,内部需要使用 Netty 进行数据通信
4. 提供者与消费者数据传输使用json字符串数据格式
5. 提供者与消费者使用netty集成spring boot 环境实现
2.需求分析
可以通过上面的需求介绍将需求分为三个模块,一个是接口模块,一个是服务端提供者模块,一个是消费者模块。其中接口模块需要实现一个User实体类,一个IUSerService接口,一个Request,和一个Response对象,因为数据是通过json字符串的数据格式,所以通过将Request和Response请求进行封装,然后进行交互;服务端(提供者)模块需要一个启动类,在启动类中添加一个线程运行提供者服务端,还需要实现一个接口实现类,用来取数据。然后提供一个服务端类和一个业务处理逻辑类,以及一个注解,注解作用是标记实现类;最后客户端(消费者)需要实现一个客户端,以及业务处理类,还有一个代理类去调用对应的方法。
3.具体实现过程
首选需要创建一个maven项目,作为父类,然后建立三个子模块,分别是api模块(接口模块),consumer模块(消费者模块),以及provider模块(提供者模块),这里我提供了两个provider,目的是实现轮询的效果,其实可以通过修改端口,然后提供两个不同的提供者。
其中maven的pom.xml文件实现如下
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.com.w.wrpcwebconsumer.WRpcWebConsumerApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
w-api模块
pom.xml文件
<dependencies>
<!--netty依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!--json依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<!--lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
IUserService接口
public interface IUserService {
/**
* 根据ID查询用户
*
* @param id
* @return
*/
User getById(int id);
}
RPCRequest类
/**
* 封装的请求对象
*/
@Data
public class RpcRequest {
/**
* 请求对象的ID
*/
private String requestId;
/**
* 类名
*/
private String className;
/**
* 方法名
*/
private String methodName;
/**
* 参数类型
*/
private Class<?>[] parameterTypes;
/**
* 入参
*/
private Object[] parameters;
}
RPCResponse类
/**
* 封装的响应对象
*/
@Data
public class RpcResponse {
/**
* 响应ID
*/
private String requestId;
/**
* 错误信息
*/
private String error;
/**
* 返回的结果
*/
private Object result;
}
w-consumer类
其中pom.xml文件
<dependencies>
<dependency>
<groupId>com.w</groupId>
<artifactId>w-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
application.yml文件
spring:
application:
# 应用名称
name: system
# 开启模板缓存(默认值: true )
thymeleaf:
# 开启模板缓存(默认值: true )
cache: true
# 检查模板是否存在,然后再呈现
check-template: true
servlet:
# Content-Type 的值(默认值: text/html )
content-type: text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
enabled: true
# 模板编码
encoding: utf-8
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
excluded-view-names:
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
mode: HTML
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
prefix: classpath:/templates/
# # 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
server:
port: 8084
mybatis:
#指定Mybatis的Mapper文件
mapper-locations: classpath:mappers/*.xml
#指定Mybatis的实体目录
type-aliases-package: com.unittec.system.mybatis.entity
ignore-host-url: /external/*
logging:
config: classpath:log/logback-spring.xml
RpcClient
import com.w.handler.NettyClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.springframework.beans.factory.DisposableBean;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class RpcClient implements DisposableBean {
EventLoopGroup loopGroup;
private Channel channel;
String ip;
int port;
NettyClientHandler nettyClientHandler = new NettyClientHandler();
private ExecutorService executorService = Executors.newCachedThreadPool();
public RpcClient(String ip, int port) {
this.ip = ip;
this.port = port;
init();
}
public void init() {
try {
System.out.println("客户端init方法执行了。。。。");
Bootstrap bootstrap = new Bootstrap();
loopGroup = new NioEventLoopGroup();
bootstrap.group(loopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(nettyClientHandler);
}
})
;
ChannelFuture future = bootstrap.connect(ip, port);
channel = future.sync().channel();
System.out.println("客户端连接上了");
} catch (Exception e) {
e.printStackTrace();
shutdown();
}
}
public void destroy() throws Exception {
shutdown();
}
public void shutdown() {
if (channel != null) {
channel.close();
}
if (loopGroup != null) {
loopGroup.shutdownGracefully();
}
}
public Object send(String msg) throws ExecutionException, InterruptedException {
System.out.println("客户端执行发送数据了");
System.out.println(msg);
nettyClientHandler.setRequestMsg(msg);
Future submit = executorService.submit(nettyClientHandler);
System.out.println("客户端接收了数据" + submit.get());
return submit.get();
}
}
这里通过send方法,实现消费者向提供者请求发送数据,发送数据是request封装的json格式的字符串。
controller
@Controller
public class TestController {
@RequestMapping("/test")
@ResponseBody
private String getUserById(HttpServletRequest request, String id) {
Object time = request.getSession().getAttribute("time");
int index = 0;
if (time != null) {
index = (Integer) time;
}
IUserService userService = (IUserService) RpcClientProxy.createProxy(IUserService.class,index);
User user = userService.getById(Integer.parseInt(id));
index++;
request.getSession().setAttribute("time", index);
return "id:"+user.getId() + "====" + user.getName();
}
}
这里的controller是通过轮询的方式去调用不同的服务提供者。
NettyClientHandler
public class NettyClientHandler extends SimpleChannelInboundHandler<String> implements Callable {
ChannelHandlerContext context;
String requestMsg;
String responseMsg;
public void setRequestMsg(String requestMsg) {
this.requestMsg = requestMsg;
}
/**
* 通道就绪事件
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端与服务端连上了");
context = ctx;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端与服务端断开了");
}
/**
* 读取通道就绪事件
*
* @param channelHandlerContext
* @param msg
* @throws Exception
*/
@Override
protected synchronized void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
//读取结果
responseMsg = msg;
System.out.println("客户端接收到数据了。。。" + msg + "唤醒线程。。。");
//唤醒等待的线程
notify();
}
public synchronized Object call() throws Exception {
//发送消息
context.writeAndFlush(requestMsg);
System.out.println("客户端发送数据了,执行了call方法。。。等待了");
//等待
wait();
return responseMsg;
}
}
因为netty实现的是异步通信,所以这里通过线程等待唤醒的方式执行接受和发送数据。
RpcClientProxy
public class RpcClientProxy {
/**
* 客户端代理类-创建代理对象
* 1.封装request请求
* 2.创建RpcClient对象
* 3.发送消息
* 4.返回结果
*
* @return
*/
public static Object createProxy(Class serviceClass, final Integer index) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serviceClass}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest request = new RpcRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
System.out.println(index);
RpcClient rpcClient;
if(index%2==0){
rpcClient = new RpcClient("127.0.0.1", 10086);
}else {
rpcClient = new RpcClient("127.0.0.1", 10087);
}
try {
Object responseMsg = rpcClient.send(JSON.toJSONString(request));
System.out.println("收到结果了。。。");
RpcResponse response = JSON.parseObject(responseMsg.toString(), RpcResponse.class);
if (response.getError() != null) {
throw new RuntimeException(response.getError());
}
Object result = response.getResult();
return JSON.parseObject(result.toString(), method.getReturnType());
} catch (Exception e) {
throw e;
} finally {
rpcClient.shutdown();
}
}
});
}
}
这里通过代理的方式,实现调用。
application
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
w-provider模块
pom.xml
<dependencies>
<dependency>
<groupId>com.w</groupId>
<artifactId>w-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--spring相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
RpcService接口
@Retention(RetentionPolicy.RUNTIME)//在运行时可以获取到
@Target(ElementType.TYPE)//作用于接口和类上
public @interface RpcService {
}
RpcServerHandler
@Service
@ChannelHandler.Sharable
public class RpcServerHandler extends SimpleChannelInboundHandler<String> implements ApplicationContextAware {
private static final Map<String, Object> SERVICE_INSTANCE_MAP = new ConcurrentHashMap<>(8);
/**
* 将标有RpcService的类注入到map中
*
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("服务端开始处理业务了。。。。");
/**
* 首先获取所有的带RpcService的实体类UserServiceImpl
*/
Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(RpcService.class);
if (beanMap.size() != 0) {
//遍历实体类UserServiceImpl 各种ServiceImpl
Set<Map.Entry<String, Object>> beans = beanMap.entrySet();
for (Map.Entry<String, Object> bean : beans) {
//获得当前实体类UserServiceImpl
Object beanObject = bean.getValue();
//获取当前实体类的所有接口IUserService
Class<?>[] interfaces = beanObject.getClass().getInterfaces();
//判断是否存在接口
if (interfaces.length == 0) {
throw new RuntimeException(bean.getKey() + "类必须实现接口");
}
//获取第一个接口IUserService,将接口名字作为key,实体类作为结果,然后存入到结果中
SERVICE_INSTANCE_MAP.put(interfaces[0].getName(), beanObject);
}
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("有客户端连接进来了。。。。");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端掉线了。。。");
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
System.out.println("服务端接受客户端请求");
//接受客户端请求
RpcRequest request = JSON.parseObject(msg, RpcRequest.class);
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
try {
//处理业务端逻辑
response.setResult(handler(request));
} catch (Exception e) {
//处理异常逻辑
response.setError(e.getMessage());
}
//回写结果
System.out.println("处理逻辑执行完了,准备返回了" + JSON.toJSON(response));
channelHandlerContext.writeAndFlush(JSON.toJSONString(response));
}
/**
* 业务处理类
*
* @param request
* @return
* @throws InvocationTargetException
*/
private Object handler(RpcRequest request) throws InvocationTargetException {
System.out.println("执行处理逻辑");
//获取class名称
String className = request.getClassName();
//获取method名称
String methodName = request.getMethodName();
//获取参数类型
Class<?>[] parameterTypes = request.getParameterTypes();
//获取参数
Object[] parameters = request.getParameters();
//根据接口名称找到对应的类
Object bean = SERVICE_INSTANCE_MAP.get(className);
if (bean == null) {
throw new RuntimeException("根据beanName找不到服务,beanName:" + className);
}
//通过cglib的方式反射执行对应的方法
FastClass fastClass = FastClass.create(bean.getClass());
FastMethod method = fastClass.getMethod(methodName, parameterTypes);
Object invoke = method.invoke(bean, parameters);
return invoke;
}
}
nettyServerProvider
@Service
public class NettyServerProvider implements DisposableBean {
NioEventLoopGroup bossGroup;
NioEventLoopGroup workerGroup;
@Autowired
RpcServerHandler handler;
public void runServer(String ip, int port) {
try {
bossGroup = new NioEventLoopGroup(1);
workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
//todo 业务处理类
pipeline.addLast(handler);
}
})
;
ChannelFuture future = bootstrap.bind(ip, port).sync();
System.out.println("服务端启动。。。"+ip+port);
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.toString());
} finally {
shutdownGroup();
}
}
@Override
public void destroy() throws Exception {
shutdownGroup();
}
public void shutdownGroup() {
if (bossGroup != null) {
bossGroup.shutdownGracefully();
}
if (workerGroup != null) {
workerGroup.shutdownGracefully();
}
}
}
UserServiceImpl
@Service
@RpcService
public class UserServiceImpl implements IUserService {
Map<Integer, User> userMap = new HashMap<>(8);
@Override
public User getById(int id) {
if (userMap == null || userMap.size() == 0) {
User u1 = new User();
u1.setName("系统1-张三");
u1.setId(1);
User u2 = new User();
u2.setId(2);
u2.setName("系统1-李四");
userMap.put(u1.getId(), u1);
userMap.put(u2.getId(), u2);
}
return userMap.get(id);
}
}
@SpringBootApplication
public class ProviderSpringbootApplication implements CommandLineRunner {
@Autowired
private NettyServerProvider provider;
public static void main(String[] args) {
SpringApplication.run(ProviderSpringbootApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
provider.runServer("127.0.0.1", 10086);
}
}).start();
}
}
最后运行结果,通过访问localhost:8084/test?id=1会的得到结果。