Springboot整合Netty实现RPC服务器(1)

本文详细解读了Java面试中可能涉及的RPC服务容器实现,包括SpringBoot中的服务注册与调用,JSON编解码器,以及动态代理的使用。作者还分享了面试准备建议,强调技术深度和广度的重要性,以及正确的心态和选择公司的考量。
摘要由CSDN通过智能技术生成

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
// RPC服务实现容器

private Map<String, Object> rpcServices = new HashMap<>();

@Value(“${rpc.server.port}”)

private int port;

@Override

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

Map<String, Object> services = applicationContext.getBeansWithAnnotation(RpcService.class);

for (Map.Entry<String, Object> entry : services.entrySet()) {

Object bean = entry.getValue();

Class<?>[] interfaces = bean.getClass().getInterfaces();

for (Class<?> inter : interfaces) {

rpcServices.put(inter.getName(), bean);

}

}

log.info(“加载RPC服务数量:{}”, rpcServices.size());

}

@Override

public void afterPropertiesSet() {

start();

}

private void start(){

new Thread(() -> {

EventLoopGroup boss = new NioEventLoopGroup(1);

EventLoopGroup worker = new NioEventLoopGroup();

try {

ServerBootstrap bootstrap = new ServerBootstrap();

bootstrap.group(boss, worker)

.childHandler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel ch) throws Exception {

ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new IdleStateHandler(0, 0, 60));

pipeline.addLast(new JsonDecoder());

pipeline.addLast(new JsonEncoder());

pipeline.addLast(new RpcInboundHandler(rpcServices));

}

})

.channel(NioServerSocketChannel.class);

ChannelFuture future = bootstrap.bind(port).sync();

log.info(“RPC 服务器启动, 监听端口:” + port);

future.channel().closeFuture().sync();

}catch (Exception e){

e.printStackTrace();

boss.shutdownGracefully();

worker.shutdownGracefully();

}

}).start();

}

}

RpcServerInboundHandler 负责处理RPC请求

@Slf4j

public class RpcServerInboundHandler extends ChannelInboundHandlerAdapter {

private Map<String, Object> rpcServices;

public RpcServerInboundHandler(Map<String, Object> rpcServices){

this.rpcServices = rpcServices;

}

@Override

public void channelActive(ChannelHandlerContext ctx) throws Exception {

log.info(“客户端连接成功,{}”, ctx.channel().remoteAddress());

}

public void channelInactive(ChannelHandlerContext ctx) {

log.info(“客户端断开连接,{}”, ctx.channel().remoteAddress());

ctx.channel().close();

}

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg){

RpcRequest rpcRequest = (RpcRequest) msg;

log.info(“接收到客户端请求, 请求接口:{}, 请求方法:{}”, rpcRequest.getClassName(), rpcRequest.getMethodName());

RpcResponse response = new RpcResponse();

response.setRequestId(rpcRequest.getRequestId());

Object result = null;

try {

result = this.handleRequest(rpcRequest);

response.setResult(result);

} catch (Exception e) {

e.printStackTrace();

response.setSuccess(false);

response.setErrorMessage(e.getMessage());

}

log.info(“服务器响应:{}”, response);

ctx.writeAndFlush(response);

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

log.info(“连接异常”);

ctx.channel().close();

super.exceptionCaught(ctx, cause);

}

@Override

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

if (evt instanceof IdleStateEvent){

IdleStateEvent event = (IdleStateEvent)evt;

if (event.state()== IdleState.ALL_IDLE){

log.info(“客户端已超过60秒未读写数据, 关闭连接.{}”,ctx.channel().remoteAddress());

ctx.channel().close();

}

}else{

super.userEventTriggered(ctx,evt);

}

}

private Object handleRequest(RpcRequest rpcRequest) throws Exception{

Object bean = rpcServices.get(rpcRequest.getClassName());

if(bean == null){

throw new RuntimeException("未找到对应的服务: " + rpcRequest.getClassName());

}

Method method = bean.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());

method.setAccessible(true);

return method.invoke(bean, rpcRequest.getParameters());

}

}

四、RPC客户端

========

/**

  • RPC远程调用的客户端

*/

@Slf4j

@Component

public class RpcClient {

@Value(“${rpc.remote.ip}”)

private String remoteIp;

@Value(“${rpc.remote.port}”)

private int port;

private Bootstrap bootstrap;

// 储存调用结果

private final Map<String, SynchronousQueue> results = new ConcurrentHashMap<>();

public RpcClient(){

}

@PostConstruct

public void init(){

bootstrap = new Bootstrap().remoteAddress(remoteIp, port);

NioEventLoopGroup worker = new NioEventLoopGroup(1);

bootstrap.group(worker)

.channel(NioSocketChannel.class)

.handler(new ChannelInitializer() {

@Override

protected void initChannel(SocketChannel channel) throws Exception {

ChannelPipeline pipeline = channel.pipeline();

pipeline.addLast(new IdleStateHandler(0, 0, 10));

pipeline.addLast(new JsonEncoder());

pipeline.addLast(new JsonDecoder());

pipeline.addLast(new RpcClientInboundHandler(results));

}

});

}

public RpcResponse send(RpcRequest rpcRequest) {

RpcResponse rpcResponse = null;

rpcRequest.setRequestId(UUID.randomUUID().toString());

Channel channel = null;

try {

channel = bootstrap.connect().sync().channel();

log.info(“连接建立, 发送请求:{}”, rpcRequest);

channel.writeAndFlush(rpcRequest);

SynchronousQueue queue = new SynchronousQueue<>();

results.put(rpcRequest.getRequestId(), queue);

// 阻塞等待获取响应

rpcResponse = queue.take();

results.remove(rpcRequest.getRequestId());

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

if(channel != null && channel.isActive()){

channel.close();

}

}

return rpcResponse;

}

}

RpcClientInboundHandler负责处理服务端的响应

@Slf4j

public class RpcClientInboundHandler extends ChannelInboundHandlerAdapter {

private Map<String, SynchronousQueue> results;

public RpcClientInboundHandler(Map<String, SynchronousQueue> results){

this.results = results;

}

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

RpcResponse rpcResponse = (RpcResponse) msg;

log.info(“收到服务器响应:{}”, rpcResponse);

if(!rpcResponse.isSuccess()){

throw new RuntimeException(“调用结果异常,异常信息:” + rpcResponse.getErrorMessage());

}

// 取出结果容器,将response放进queue中

SynchronousQueue queue = results.get(rpcResponse.getRequestId());

queue.put(rpcResponse);

}

@Override

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

if (evt instanceof IdleStateEvent){

IdleStateEvent event = (IdleStateEvent)evt;

if (event.state() == IdleState.ALL_IDLE){

log.info(“发送心跳包”);

RpcRequest request = new RpcRequest();

request.setMethodName(“heartBeat”);

ctx.channel().writeAndFlush(request);

}

}else{

super.userEventTriggered(ctx, evt);

}

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){

log.info(“异常:{}”, cause.getMessage());

ctx.channel().close();

}

}

接口代理

为了使客户端像调用本地方法一样调用远程服务,我们需要对接口进行动态代理。

代理类实现

@Component

public class RpcProxy implements InvocationHandler {

@Autowired

private RpcClient rpcClient;

@Override

public Object invoke(Object proxy, Method method, Object[] args){

RpcRequest rpcRequest = new RpcRequest();

rpcRequest.setClassName(method.getDeclaringClass().getName());

rpcRequest.setMethodName(method.getName());

rpcRequest.setParameters(args);

rpcRequest.setParameterTypes(method.getParameterTypes());

RpcResponse rpcResponse = rpcClient.send(rpcRequest);

return rpcResponse.getResult();

}

}

实现FactoryBean接口,将生产动态代理类纳入 Spring 容器管理。

public class RpcFactoryBean implements FactoryBean {

private Class interfaceClass;

@Autowired

private RpcProxy rpcProxy;

public RpcFactoryBean(Class interfaceClass){

this.interfaceClass = interfaceClass;

}

@Override

public T getObject(){

return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, rpcProxy);

}

@Override

public Class<?> getObjectType() {

return interfaceClass;

}

}

自定义类路径扫描器,扫描包下的RPC接口,动态生产代理类,纳入 Spring 容器管理

public class RpcScanner extends ClassPathBeanDefinitionScanner {

public RpcScanner(BeanDefinitionRegistry registry) {

super(registry);

}

@Override

protected Set doScan(String… basePackages) {

Set beanDefinitionHolders = super.doScan(basePackages);

for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {

GenericBeanDefinition beanDefinition = (GenericBeanDefinition)beanDefinitionHolder.getBeanDefinition();

beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());

beanDefinition.setBeanClassName(RpcFactoryBean.class.getName());

}

return beanDefinitionHolders;

}

@Override

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {

return true;

}

@Override

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {

return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();

}

}

@Component

public class RpcBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

@Override

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

RpcScanner rpcScanner = new RpcScanner(registry);

// 传入RPC接口所在的包名

rpcScanner.scan(“com.ygd.rpc.common.service”);

}

@Override

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

}

}

JSON编解码器

/**

  • 将 RpcRequest 编码成字节序列发送

  • 消息格式: Length + Content

  • Length使用int存储,标识消息体的长度

  • ±-------±---------------+

  • | Length | Content |

  • | 4字节 | Length个字节 |

  • ±-------±---------------+

*/

public class JsonEncoder extends MessageToByteEncoder {

@Override

protected void encode(ChannelHandlerContext ctx, RpcRequest rpcRequest, ByteBuf out){

byte[] bytes = JSON.toJSONBytes(rpcRequest);

// 将消息体的长度写入消息头部

out.writeInt(bytes.length);

// 写入消息体

out.writeBytes(bytes);

}

}

/**

  • 将响应消息解码成 RpcResponse

*/

public class JsonDecoder extends LengthFieldBasedFrameDecoder {

public JsonDecoder(){

super(Integer.MAX_VALUE, 0, 4, 0, 4);

}

@Override

protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {

ByteBuf msg = (ByteBuf) super.decode(ctx, in);

byte[] bytes = new byte[msg.readableBytes()];

最后

无论是哪家公司,都很重视基础,大厂更加重视技术的深度和广度,面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。

针对以上面试技术点,我在这里也做一些分享,希望能更好的帮助到大家。

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

/**

  • 将响应消息解码成 RpcResponse

*/

public class JsonDecoder extends LengthFieldBasedFrameDecoder {

public JsonDecoder(){

super(Integer.MAX_VALUE, 0, 4, 0, 4);

}

@Override

protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {

ByteBuf msg = (ByteBuf) super.decode(ctx, in);

byte[] bytes = new byte[msg.readableBytes()];

最后

无论是哪家公司,都很重视基础,大厂更加重视技术的深度和广度,面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。

针对以上面试技术点,我在这里也做一些分享,希望能更好的帮助到大家。

[外链图片转存中…(img-Ip4R7z6f-1714757884753)]

[外链图片转存中…(img-2fTf9CpN-1714757884753)]

[外链图片转存中…(img-srJahBnD-1714757884754)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 SpringBoot 整合 Netty 实现 TCP 服务器可以让我们更方便地管理和部署我们的应用程序。下面是一些基本的步骤: 1. 创建一个 SpringBoot 项目,并添加 Netty 和相应的依赖。 2. 创建一个 Netty 服务类,实现 ChannelInboundHandlerAdapter 接口。在这个类中,你可以实现接收、处理和发送 TCP 消息的逻辑。 3. 通过 SpringBoot 的配置文件,配置 Netty 服务器的端口和其他参数。 4. 在 SpringBoot 的启动类中,使用 @Bean 注解将 Netty 服务类注册为 bean。 5. 启动 SpringBoot 应用程序,Netty 服务器将开始监听传入的连接。 下面是一个简单的示例: ``` // 服务类 @Component public class MyNettyServer extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 处理接收到的消息 ByteBuf buf = (ByteBuf) msg; String message = buf.toString(CharsetUtil.UTF_8); // 返回响应消息 String response = "Hello, " + message; ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes())); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 处理异常 cause.printStackTrace(); ctx.close(); } } // 启动类 @SpringBootApplication public class MyApplication { @Autowired private MyNettyServer myNettyServer; @Value("${netty.port}") private int port; @PostConstruct public void start() throws Exception { // 创建 EventLoopGroup EventLoopGroup group = new NioEventLoopGroup(); try { // 创建 ServerBootstrap ServerBootstrap b = new ServerBootstrap(); b.group(group) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { // 添加处理器 ch.pipeline().addLast(myNettyServer); } }); // 启动服务器 ChannelFuture f = b.bind().sync(); f.channel().closeFuture().sync(); } finally { // 关闭 EventLoopGroup group.shutdownGracefully().sync(); } } public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } // 配置文件 netty.port=8080 ``` 在这个例子中,我们创建了一个名为 `MyNettyServer` 的服务类,并实现了 `ChannelInboundHandlerAdapter` 接口。在 `channelRead` 方法中,我们处理接收到的消息,并返回响应消息。在 `exceptionCaught` 方法中,我们处理异常。 在启动类中,我们使用 `@Autowired` 注解将 `MyNettyServer` 注入到启动类中,并使用 `@Value` 注解获取配置文件中的端口号。在 `start` 方法中,我们创建了一个 `EventLoopGroup`,并使用 `ServerBootstrap` 创建了一个 Netty 服务器。然后,我们将 `MyNettyServer` 添加到 `SocketChannel` 的处理器中。最后,我们启动服务器,并在关闭服务器之前等待连接。 这只是一个简单的示例,你可以根据你的需求修改和扩展它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值