当前位置: 首页 > news >正文

【吃透Java手写】5-RPC-简易版

【吃透Java手写】RPC-简易版-源码解析

  • 1 RPC
    • 1.1 RPC概念
    • 1.2 常用RPC技术或框架
    • 1.3 初始工程
      • 1.3.1 Productor-common:HelloService
      • 1.3.2 Productor:HelloServiceImpl
      • 1.3.3 Consumer
  • 2 模拟RPC
    • 2.1 Productor
    • 2.2 模拟一个RPC框架
      • 2.2.1 HttpServer
      • 2.2.2 HttpClient
      • 2.2.2 用rpc启动tomcat
      • 2.2.3 启动Productor
    • 2.3 DispatcherServlet
      • 2.3.1 Handler
      • 2.3.2 Invocation
      • 2.3.3 完善Handler
    • 2.4 注册中心LocalRegister
      • 2.4.1 Productor
    • 2.5 Handler
    • 2.6 Consumer测试
  • 3 优化
    • 3.1 ProxyFactory
    • 3.2 Consumer
    • 3.3 测试
  • 4 rpc服务注册和服务发现
    • 4.1 URL
    • 4.2 MapRemoteRegister
    • 4.3 注册中心注册
    • 4.4 负载均衡
    • 4.5 测试
      • 4.5.1 解决
    • 4.6 BootStrap
  • 5 服务重试


1 RPC

1.1 RPC概念

  1. RPC(Remote Procedure Call Protocol) 远程过程调用协议。
  2. RPC是一种通过网络从远程计算机程序上请求服务,不需要了解底层网络技术的协议。
  3. RPC主要作用就是不同的服务间方法调用就像本地调用一样便捷。

1.2 常用RPC技术或框架

  1. 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
  2. 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
  3. 通信框架:MINA 和 Netty

1.3 初始工程

在这里插入图片描述

1.3.1 Productor-common:HelloService

在Productor-common中创建com.sjb.HelloService

public interface HelloService {String sayHello(String name);
}

1.3.2 Productor:HelloServiceImpl

在Productor中创建com.sjb.HelloServiceImpl

public class HelloServiceImpl implements HelloService {@Overridepublic String sayHello(String name) {return "Hello, " + name;}
}

pom.xml依赖

<dependencies><dependency><groupId>org.example</groupId><artifactId>Productor-common</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

1.3.3 Consumer

在Consumer中创建com.sjb.Consumer

public class Consumer {public static void main(String[] args) {HelloService helloService = ?;System.out.println(helloService.sayHello("world"));}
}

pom.xml依赖

<dependencies><dependency><groupId>org.example</groupId><artifactId>Productor-common</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

2 模拟RPC

2.1 Productor

我们需要在springboot启动时完成一部分功能。启动时要能接收一部分功能的调用。只能通过网络来接收一定的请求,比如netty或者tomcat、socket。

在Productor中创建com.sjb.Productor

public class Productor {public static void main(String[] args) {//netty、tomcat}
}

2.2 模拟一个RPC框架

创捷sjbRPC模块,并且使Consumer模块和Productor模块依赖于sjbRPC模块

<dependency><groupId>org.example</groupId><artifactId>sjbRPC</artifactId><version>1.0-SNAPSHOT</version>
</dependency>

2.2.1 HttpServer

在sjbRPC模块中创建com.sjb.Productorcom.sjb.register.HttpServer,负责网络服务启动

public class HttpServer {public void start(String hostname, int port) {System.out.println("HttpServer start at " + hostname + ":" + port);}   
}

然后Productor就可以创建HttpServer对象调用里面的start方法

public class Productor {public static void main(String[] args) {//netty、tomcatHttpServer httpServer = new HttpServer();httpServer.start("localhost", 8080);}
}

2.2.2 HttpClient

创建com.sjb.protocol.HttpClient

public class HttpClient {public String send(String hostName, int port, Invocation invocation) {//读取用户的发送方式//http、netty、tcptry{URL url = new URL("http", hostName, port, "/");HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("POST");connection.setDoOutput(true);//配置OutputStream outputStream = connection.getOutputStream();ObjectOutputStream oos = new ObjectOutputStream(outputStream);//发送oos.writeObject(invocation);oos.flush();oos.close();//接收InputStream inputStream = connection.getInputStream();String result = IOUtils.toString(inputStream);return result;} catch (MalformedURLException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}
}

2.2.2 用rpc启动tomcat

为rpc添加tomcat依赖

<dependencies><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>8.5.31</version></dependency>
</dependencies>

rpc第一步应当扫描当前模块的配置,获取需要启动的网络服务,这里写死直接启动tomcat

在com.sjb.register.HttpServer#start中

public void start(String hostname, int port) {//1.读取用户的配置(application.yaml或者Nacos配置)//2.这里启动一个TomcatTomcat tomcat = new Tomcat();Server server = tomcat.getServer();Service service = server.findService("Tomcat");Connector connector = new Connector();connector.setPort(port);Engine engine = new StandardEngine();engine.setDefaultHost(hostname);Host host = tomcat.getHost();host.setName(hostname);String contextPath = "";Context context = new StandardContext();context.setPath(contextPath);context.addLifecycleListener(new Tomcat.FixContextListener());host.addChild(context);engine.addChild(host);service.setContainer(engine);service.addConnector(connector);try{tomcat.start();tomcat.getServer().await();}catch (LifecycleException e){e.printStackTrace();}
}

2.2.3 启动Productor

public class Productor {public static void main(String[] args) {//netty、tomcatHttpServer httpServer = new HttpServer();httpServer.start("localhost", 8080);}
}
D:\Software\software_with_code\idea\jdk\jdk-17\bin\java.exe "-javaagent:D:\Software\software_with_code\idea\software\IntelliJ IDEA 2023.2\lib\idea_rt.jar=13802:D:\Software\software_with_code\idea\software\IntelliJ IDEA 2023.2\bin" -Dfile.encoding=UTF-8 -classpath D:\Code\JavaCode\handwith-Spring\handwith-Spring\RPC\Productor\target\classes;D:\Code\JavaCode\handwith-Spring\handwith-Spring\RPC\Productor-common\target\classes;D:\Code\JavaCode\handwith-Spring\handwith-Spring\RPC\sjbRPC\target\classes;D:\Software\software_with_code\apache-maven-3.9.5-bin\apache-maven-3.9.5\mvn_repo\org\apache\tomcat\embed\tomcat-embed-core\8.5.31\tomcat-embed-core-8.5.31.jar;D:\Software\software_with_code\apache-maven-3.9.5-bin\apache-maven-3.9.5\mvn_repo\org\apache\tomcat\tomcat-annotations-api\8.5.31\tomcat-annotations-api-8.5.31.jar com.sjb.Productor
5月 13, 2024 1:26:00 下午 org.apache.coyote.AbstractProtocol init
信息: Initializing ProtocolHandler ["http-nio-8080"]
5月 13, 2024 1:26:00 下午 org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
信息: Using a shared selector for servlet write/read
5月 13, 2024 1:26:00 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Tomcat]
5月 13, 2024 1:26:00 下午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet Engine: Apache Tomcat/8.5.31
5月 13, 2024 1:26:01 下午 org.apache.catalina.util.SessionIdGeneratorBase createSecureRandom
警告: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [117] milliseconds.
5月 13, 2024 1:26:01 下午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-nio-8080"]

2.3 DispatcherServlet

大家如果学过Spring MVC的底层原理就会知道,在SpringMVC中有一个Servlet非常核心,那就是DispatcherServlet,这个DispatcherServlet需要绑定一个Spring容器,因为DispatcherServlet接收到请求后,就会从所绑定的Spring容器中找到所匹配的Controller,并执行所匹配的方法,所有的服务都会放入DispatchServlet中。我们rpc框架启动的服务也要放入DispatcherServlet

在com.sjb.protocol.HttpServer#start中

tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
context.addServletMappingDecoded("/*", "dispatcher");try{tomcat.start();tomcat.getServer().await();
}
catch (LifecycleException e){e.printStackTrace();

context.addServletMappingDecoded("/*", "dispatcher");接收到的请求都会交由dispatcher处理

创建com.sjb.register.DispatcherServlet

public class DispatcherServlet extends HttpServlet {@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {new HttpServerHandler().handle(req, res);}
}

2.3.1 Handler

创建com.sjb.register.HttpServerHandler,因为有可能有很多请求请求dispatcher,相当于一个过滤器的作用,相当可以用每一个请求都可以用一个独立的handler类处理,也就是new一个新handler来处理。

public class HttpServerHandler {public void handle(ServletRequest req, ServletResponse res) {//处理请求}
}

2.3.2 Invocation

创建com.sjb.common.Invocation,记录传入的接口名、方法名、参数列表、参数值

implements Serializable序列化是方便解析request

public class Invocation implements Serializable {private String interfaceName;private String methodName;private Class[] paramTypes;private Object[] params;public String getInterfaceName() {return interfaceName;}public void setInterfaceName(String interfaceName) {this.interfaceName = interfaceName;}public String getMethodName() {return methodName;}public void setMethodName(String methodName) {this.methodName = methodName;}public Class[] getParamTypes() {return paramTypes;}public void setParamTypes(Class[] paramTypes) {this.paramTypes = paramTypes;}public Object[] getParams() {return params;}public void setParams(Object[] params) {this.params = params;}public Invocation(String interfaceName, String methodName, Class[] paramTypes, Object[] params) {this.interfaceName = interfaceName;this.methodName = methodName;this.paramTypes = paramTypes;this.params = params;}
}

2.3.3 完善Handler

public class HttpServerHandler {public void handle(ServletRequest req, ServletResponse res) {//处理请求-->接口,方法,参数try {Invocation invocation=(Invocation)new ObjectInputStream(req.getInputStream()).readObject();String interfaceName=invocation.getInterfaceName();} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
}

这样就获取到调用请求的类的接口,那么怎么找到接口的实现类呢?如果是扫描全包查看谁实现了HelloService这样性能就非常的低。所以我们需要一个注册中心。

2.4 注册中心LocalRegister

创建com.sjb.register.LocalRegister

public class LocalRegister {private static Map<String, Class> map = new HashMap<>();public static void register(String interfaceName, Class implClass) {map.put(interfaceName, implClass);}public static Class get(String interfaceName) {return map.get(interfaceName);}
}

2.4.1 Productor

这样就可以在Productor中将接口和实现类放入,在com.sjb.Productor中

public class Productor {public static void main(String[] args) {//注册服务LocalRegister.register(HelloService.class.getName(), HelloServiceImpl.class);//netty、tomcatHttpServer httpServer = new HttpServer();httpServer.start("localhost", 8080);}
}

这样在Handler中就可以从LocalRegister的map中拿到对应的接口和实现类

2.5 Handler

添加commons-io依赖

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version>
</dependency>
public class HttpServerHandler {public void handle(ServletRequest req, ServletResponse res) {//处理请求-->接口,方法,参数try {Invocation invocation=(Invocation)new ObjectInputStream(req.getInputStream()).readObject();String interfaceName=invocation.getInterfaceName();Class implClass= LocalRegister.get(interfaceName);Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());Object result = method.invoke(implClass.newInstance(), invocation.getParams());//res.getOutputStream().write(invoke.toString().getBytes());IOUtils.write(result.toString(), res.getOutputStream());} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);}}
}
  • Invocation invocation=(Invocation)new ObjectInputStream(req.getInputStream()).readObject();反序列化获取invocation
  • String interfaceName=invocation.getInterfaceName();获取接口名
  • Class implClass= LocalRegister.get(interfaceName);通过注册中心获取接口实现类
  • Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());获取实现类中的方法
  • Object result = method.invoke(implClass.newInstance(), invocation.getParams());执行方法返回返回值
  • IOUtils.write(result.toString(), res.getOutputStream());写入response中

2.6 Consumer测试

public class Consumer {public static void main(String[] args) {
//        HelloService helloService = ?;
//        System.out.println(helloService.sayHello("world"));Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello", new Class[]{String.class}, new Object[]{"world"});HttpClient httpClient = new HttpClient();String result = httpClient.send("localhost", 8080, invocation);System.out.println(result);}
}

输出

Hello, world

3 优化

我们想让网络调用像调用本地方法一样,创建一个HelloService对象,直接传参就好了

HelloService helloService = ?;
System.out.println(helloService.sayHello("world"));

所以我们需要在rpc框架中创建一个代理对象代理HelloService

3.1 ProxyFactory

创建com.sjb.proxy.ProxyFactory

public class ProxyFactory {public static <T> T getProxy(Class interfaceClass) {//读取用户配置Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Invocation invocation = new Invocation(interfaceClass.getName(),method.getName(),method.getParameterTypes(),args);HttpClient httpClient = new HttpClient();String result = httpClient.send("localhost", 8080, invocation);return result;}});return (T) proxyInstance;}}

3.2 Consumer

public class Consumer {public static void main(String[] args) {
//        HelloService helloService = ?;
//        System.out.println(helloService.sayHello("world"));HelloService helloService = ProxyFactory.getProxy(HelloService.class);System.out.println(helloService.sayHello("world"));}
}

helloService.sayHello("world")调用invoke方法返回

3.3 测试

Hello, world

4 rpc服务注册和服务发现

我们希望String result = httpClient.send("localhost", 8080, invocation);在send的时候可以灵活的找到传入的接口对应的ip和端口是多少,也就是应用所对应的ip和端口是多少,所以就自然而然的想到注册中心,在Productor创建的时候,将对应服务的ip和端口保存到rpc中起来,以供其他服务使用。

在这里插入图片描述

4.1 URL

public class URL {private String hostname;private Integer port;

这样我们Productor启动的时候,不仅需要注册服务,还要注册注册中心

4.2 MapRemoteRegister

创建com.sjb.register.MapRemoteRegister

public class MapRemoteRegister {private static Map<String, List<URL>> mapRemoteRegister = new HashMap<>();public static void register(String interfaceName,URL url) {List<URL> list = mapRemoteRegister.get(interfaceName);if (list == null) {list = new java.util.ArrayList<>();}list.add(url);mapRemoteRegister.put(interfaceName, list);}public static List<URL> get(String interfaceName) {return mapRemoteRegister.get(interfaceName);}
}

4.3 注册中心注册

public class Productor {public static void main(String[] args) {//注册服务LocalRegister.register(HelloService.class.getName(), HelloServiceImpl.class);//注册中心注册URL url = new URL("localhost", 8080);MapRemoteRegister.register(HelloService.class.getName(), url);

那么在创建HelloService的代理对象时,就要读取注册中心

public class ProxyFactory {public static <T> T getProxy(Class interfaceClass) {//读取用户配置Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Invocation invocation = new Invocation(interfaceClass.getName(),method.getName(),method.getParameterTypes(),args);HttpClient httpClient = new HttpClient();//服务发现List<URL> urls = MapRemoteRegister.get(interfaceClass.getName());//负载均衡URL url = LoadBalance.random(urls);//服务调用String result = httpClient.send(url.getHostname(), url.getPort(), invocation);return result;}});return (T) proxyInstance;}}

4.4 负载均衡

创建com.sjb.loadbalance.LoadBalance

public class LoadBalance {public static URL random(List<URL> list) {int i = new Random().nextInt(list.size());return list.get(i);}
}

4.5 测试

感觉没问题,测试一下

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "list" is nullat com.sjb.loadbalance.LoadBalance.random(LoadBalance.java:10)at com.sjb.proxy.ProxyFactory$1.invoke(ProxyFactory.java:29)at jdk.proxy1/jdk.proxy1.$Proxy0.sayHello(Unknown Source)at com.sjb.Consumer.main(Consumer.java:11)

报错,发现在Product中

//注册服务
LocalRegister.register(HelloService.class.getName(), HelloServiceImpl.class);
//注册中心注册
URL url = new URL("localhost", 8080);
MapRemoteRegister.register(HelloService.class.getName(), url);

LocalRegister.register的调用是在Product启动的HttpServer的handler处理中,等于LocalRegister这个map还是在Product这个进程中。而MapRemoteRegister.register的存放是在Product进程中,而调用却是在Consumer中的代理方法的invoke中,自然调用不到。

4.5.1 解决

要么使用redis等统一管理,但是又涉及心跳检测等等。我们这里使用一个简单的存入一个文件,再从文件里读取

public class MapRemoteRegister {private static Map<String, List<URL>> mapRemoteRegister = new HashMap<>();public static void register(String interfaceName,URL url) {List<URL> list = mapRemoteRegister.get(interfaceName);if (list == null) {list = new java.util.ArrayList<>();}list.add(url);mapRemoteRegister.put(interfaceName, list);saveFile();}public static List<URL> get(String interfaceName) {mapRemoteRegister = getFile();return mapRemoteRegister.get(interfaceName);}public static void saveFile(){try{FileOutputStream fos = new FileOutputStream("/temp.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(mapRemoteRegister);oos.close();} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}public static Map<String,List<URL>> getFile(){try{FileInputStream fis = new FileInputStream("/temp.txt");ObjectInputStream ois = new ObjectInputStream(fis);Map<String,List<URL>> map = (Map<String,List<URL>>)ois.readObject();ois.close();return map;} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
}

因为我们的URL也要存入文件,所以也要序列化

public class URL implements Serializable {

再次测试,成功输出

Hello, world

在实际的分布式系统中,通常会使用专门的分布式服务注册中心(例如ZooKeeper、Consul等)来管理服务的注册和发现。这样可以确保注册信息的一致性、可靠性和可扩展性。

4.6 BootStrap

我们注册服务和注册注册中心的操作可以作为一个方法一起使用

创建com.sjb.bootstrap.BootStrap

public class BootStrap {public static void bindAndStart(Class interfaceClass, Class implClass, String hostname, Integer port) {//注册服务LocalRegister.register(interfaceClass.getName(), implClass);//注册中心注册URL url = new URL("localhost", 8080);MapRemoteRegister.register(interfaceClass.getName(), url);//netty、tomcatHttpServer httpServer = new HttpServer();httpServer.start(url.getHostname(), url.getPort());}
}

product调用的时候

public class Productor {public static void main(String[] args) {BootStrap.bindAndStart(HelloService.class, HelloServiceImpl.class, "localhost", 8080);}
}

5 服务重试

可以设置默认的重试次数,直到全部失败

在com.sjb.proxy.ProxyFactory#getProxy中

//服务发现
List<URL> urls = MapRemoteRegister.get(interfaceClass.getName());
//负载均衡
URL url = LoadBalance.random(urls);
//服务调用
String result =null;
int defaltRetry = 3;
for (int i = 0; i < defaltRetry; i++) {try {result = httpClient.send(url.getHostname(), url.getPort(), invocation);if (result != null) {break;}} catch (Exception e) {e.printStackTrace();}
}
return result;

相关文章:

【吃透Java手写】5-RPC-简易版

【吃透Java手写】RPC-简易版-源码解析 1 RPC1.1 RPC概念1.2 常用RPC技术或框架1.3 初始工程1.3.1 Productor-common&#xff1a;HelloService1.3.2 Productor&#xff1a;HelloServiceImpl1.3.3 Consumer 2 模拟RPC2.1 Productor2.2 模拟一个RPC框架2.2.1 HttpServer2.2.2 Http…...

express 本地https服务 接口、静态文件,并支持跨域

var express require(express); var app express(); //设置跨域访问 app.all(*, function (req, res, next) {res.header(Access-Control-Allow-Origin, *);res.header(Access-Control-Allow-Credentials, true);res.header(Access-Control-Allow-Headers, Content-Type,Cont…...

从零手写实现 tomcat-08-tomcat 如何与 springboot 集成?

创作缘由 平时使用 tomcat 等 web 服务器不可谓不多&#xff0c;但是一直一知半解。 于是想着自己实现一个简单版本&#xff0c;学习一下 tomcat 的精髓。 系列教程 从零手写实现 apache Tomcat-01-入门介绍 从零手写实现 apache Tomcat-02-web.xml 入门详细介绍 从零手写…...

yarn 安装以及报错处理

前一种报错是由于没有安装yarn导致的&#xff0c;使用以下命令即可安装&#xff1a; npm install -g yarn 如果成功安装&#xff0c;将显示Yarn的版本号。 yarn --version 第二种报错是因为系统上的执行策略限制导致的。执行策略是一种安全功能&#xff0c;用于控制在计算机…...

31万奖金池等你挑战!IJCAI 2024 第九届“信也科技杯”全球AI算法大赛正式开赛!聚焦AI尖端赛题!

文章目录 ⭐️ 赛事概况⭐️ 赛事奖励⭐️ 赛事日程速览⭐️ 报名通道与赛事交流群⭐️ 关于 “信也科技杯”⭐️ 关于信也科技 ⭐️ 赛事概况 随着语音合成技术的不断进步,合成语音与真实语音之间的界限变得模糊,这不仅对数据安全构成威胁,也对科技伦理提出了新的要求。 第九…...

线性表—栈的实现

目录 栈的概念及结构 栈的实现 创建栈 栈的初始化 入栈 出栈 取出栈顶数据 判断栈是否为空 有效数据个数 栈的销毁 全代码 stack.h stack.c 应用 题目 示例 解题思路 代码实现 栈的概念及结构 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入…...

react+antd --- 日期选择器,动态生成日期表格表头

先看一下效果---有当前月的日期 技术&#xff1a; 1&#xff1a; react 2&#xff1a;antd-UI库 -- table 3&#xff1a;moment--时间处理库 代码效果&#xff1a; import { Button, DatePicker, Table } from antd; import { useEffect, useState } from react; import momen…...

webgl入门-js与着色器间的数据传输

js与着色器间的数据传输 前言 课堂目标 使用js向着色器传递数据获取鼠标在canvas 中的webgl 坐标系位置 知识点 attribute 变量gl.vertextAttribute3f() 的同族函数鼠标在canvas 中的css 位置转webgl 坐标位uniform 变量gl.uniform4f() 的同族函数 第一章 用js控制一个点…...

springmvc异常处理

springmvc异常处理 spring中有三种方式可以优雅的处理异常 使用ExceptionHandler 使用HandlerExceptionResolver 使用ControllerAdviceExceptionHandler 使用ExceptionHandler 该方式只在指定的Controller有效&#xff0c;不会对其他的Controller产生影响 ControllerRequestMap…...

可拖动、连线的React画布组件有哪些? 官网分别是什么?

下面是一些常用的可拖动、连线的React画布组件以及它们的官方网站&#xff1a; react-dagre-d3&#xff1a;这是一个基于React和D3.js的可拖动、连线的图形编辑器组件。它使用DAG&#xff08;有向无环图&#xff09;布局算法&#xff0c;支持节点拖拽、连线、缩放等功能。官网&…...

专访 Staynex 创始人 Yuen Wong:酒店行业的变革者

整理&#xff1a;Tia&#xff0c;Techub News 传统酒店业其实已经很中心化了&#xff0c;几大巨头 OTA 平台几乎已经完成对行业的垄断&#xff0c;而酒店商家也不得不受制于平台的规则制度&#xff0c;向平台支付高比例的费用。Staynex 看到了其中的机会&#xff0c;并想利用区…...

最新版Ceph( Reef版本)块存储简单对接k8s(上集)

当前ceph 你的ceph集群上执行 1.创建名为k8s-rbd 的存储池 ceph osd pool create k8s-rbd 64 642.初始化 rbd pool init k8s-rbd3 创建k8s访问块设备的认证用户 ceph auth get-or-create client.kubernetes mon profile rbd osd profile rbd poolk8s-rbd部署 ceph-rbd-csi c…...

稳态大面积光伏组件IV测试太阳光模拟器

稳态大面积光伏组件IV测试太阳光模拟器是太阳能光伏组件质量检测和评价的重要步骤之一。本文将介绍光伏组件IV测试的原理及标准板选择。 I. 光伏组件IV测试原理 光伏组件IV测试即电流电压特性测试&#xff0c;是评估光伏组件性能的重要手段。其测量的主要参数为组件的电流和电…...

编写HTTP协议代理的一些知识(源码)

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 早期上网经常需要使用代理服务…...

LabVIEW天然气压缩因子软件设计

LabVIEW天然气压缩因子软件设计 项目背景 天然气作为一种重要的能源&#xff0c;其压缩因子的准确计算对于流量的计量和输送过程的优化具有关键意义。传统的计算方法不仅步骤繁琐&#xff0c;而且难以满足现场快速响应的需求。因此&#xff0c;开发一款既能保证计算精度又便于…...

GCP谷歌云有什么数据库类型,该怎么选择

GCP谷歌云提供的数据库类型主要包括&#xff1a; 关系型数据库&#xff1a;这类数据库适用于结构化数据&#xff0c;通常用于数据结构不经常发生变化的场合。在GCP中&#xff0c;关系型数据库选项包括Cloud SQL和Cloud Spanner。Cloud SQL提供托管的MySQL、PostgreSQL和SQL Se…...

项目经理之路:裁员与内卷下的生存策略

作为一名项目经理&#xff0c;身处这个充满挑战与机遇的行业中&#xff0c;今年所面临的裁员潮和内卷化趋势无疑给我的工作带来了前所未有的压力。然而&#xff0c;正是这些压力和挑战&#xff0c;让我们更加深刻地思考了在这个快速变化的时代中&#xff0c;我们项目经理应该如…...

MWM触摸屏工控机维修TEM-EV0 EN00-Z312yy-xx

触摸屏维修是一个比较复杂的过程&#xff0c;并且其中会涉及到各个部件的问题&#xff0c;这对于操作人员来说&#xff0c;关键在于是否可以找到问题所在。维修过程中建议先检查各接线接口是否出现松动&#xff0c;然后检查串口及中断号是否有冲突&#xff0c;若有冲突&#xf…...

idm下载到99.99%不动了 idm突然不下载了 idm下载到最后没速度咋办 IDM下载后没网了是怎么回事

idm能够帮助我们下载不同类型的网页视频&#xff0c;并且基于多线程下载技术的助力下使其下载速度比原来提升数倍以上&#xff0c;因此成为了许多朋友下载的小助手。但也有朋友反映idm下载网页视频超时连接不上&#xff0c;idm下载网页视频突然停止&#xff0c;究竟这些情况我们…...

设计模式-07 设计模式-观察者模式(Observer Pattern)

设计模式-07 设计模式-观察者模式&#xff08;Observer Pattern&#xff09; 1.定义 观察者模式是一种软件设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;其中一个对象&#xff08;称为“主题”&#xff09;维护了一个依赖对象的列表&#xff08;称为“观察者”…...

QMCDecode终极解决方案:突破QQ音乐加密格式限制的完全指南

QMCDecode终极解决方案&#xff1a;突破QQ音乐加密格式限制的完全指南 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac&#xff0c;qmc0,qmc3转mp3, mflac,mflac0等转flac)&#xff0c;仅支持macOS&#xff0c;可自动识别到QQ音乐下载目录&#xff0c;默…...

WIN11 + WSL2 + Ubuntu22.04 + CUDA + PyTorch 环境搭建避坑全指南:从零到一,告别配置焦虑

1. 为什么选择WSL2Ubuntu22.04做AI开发&#xff1f; 很多刚接触AI开发的Windows用户都会遇到一个灵魂拷问&#xff1a;到底是在Windows原生环境装Python和PyTorch&#xff0c;还是装双系统&#xff1f;实测下来&#xff0c;这两种方案都有明显缺陷。Windows原生安装经常遇到CUD…...

惊艳效果!立知lychee-rerank-mm图文匹配案例分享,看看它有多准

惊艳效果&#xff01;立知lychee-rerank-mm图文匹配案例分享&#xff0c;看看它有多准 1. 为什么我们需要多模态重排序 在信息爆炸的时代&#xff0c;我们每天都会遇到这样的场景&#xff1a;搜索引擎返回几十个结果&#xff0c;但真正相关的可能只有两三个&#xff1b;电商平…...

仪器设备显示屏选型:从交期与服务看适配价值

作为仪器设备厂商的客户品质人员&#xff0c;在显示屏选型与品质把关工作中&#xff0c;交期稳定性与全流程服务能力&#xff0c;是影响设备研发进度、量产交付与长期运维的核心要素&#xff0c;仪器设备行业研发迭代快、量产周期紧、售后要求高&#xff0c;显示屏供应商能否稳…...

软件驱动与应用开发-RK3588实战

一、RK3588设备树关键配置 1.1 I2C与SPI引脚复用配置 dts // 文件: rk3588-smart-monitor.dts / {// I2C2: 使用GPIO4_B1/B2 (功能3)&i2c2 {status = "okay";clock-frequency = <400000>;pinctrl-0 = <&i2c2m0_xfer>;pinctrl-names = "d…...

AI建站工具哪家强?看懂这份选型标准与对比指南再做决定

面对市面上五花八门的AI建站工具&#xff0c;很多人陷入选择困难&#xff1a;有的号称全智能生成&#xff0c;有的强调设计感强&#xff0c;有的主打SEO友好。到底哪款适合自己&#xff1f;抛开品牌和营销话术&#xff0c;真正需要看懂的&#xff0c;是工具背后的底层逻辑和核心…...

SQLPad多租户架构实现:构建企业级SQL查询服务平台的终极指南

SQLPad多租户架构实现&#xff1a;构建企业级SQL查询服务平台的终极指南 【免费下载链接】sqlpad Web-based SQL editor 项目地址: https://gitcode.com/gh_mirrors/sq/sqlpad 在现代企业数据管理中&#xff0c;多租户架构已成为支撑多团队协作的关键技术。SQLPad作为一…...

从视频收藏到内容管理:BilibiliDown图形化下载器深度解析

从视频收藏到内容管理&#xff1a;BilibiliDown图形化下载器深度解析 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader &#x1f633; 项目地址: https://gitcode.com/gh_mirror…...

HunterPie:怪物猎人世界终极数据覆盖层工具,实时监控提升狩猎效率

HunterPie&#xff1a;怪物猎人世界终极数据覆盖层工具&#xff0c;实时监控提升狩猎效率 【免费下载链接】HunterPie-legacy A complete, modern and clean overlay with Discord Rich Presence integration for Monster Hunter: World. 项目地址: https://gitcode.com/gh_m…...

DXVK 2.7.1:Linux游戏图形性能的终极Vulkan转换层深度解析

DXVK 2.7.1&#xff1a;Linux游戏图形性能的终极Vulkan转换层深度解析 【免费下载链接】dxvk Vulkan-based implementation of D3D8, 9, 10 and 11 for Linux / Wine 项目地址: https://gitcode.com/gh_mirrors/dx/dxvk DXVK 2.7.1作为基于Vulkan的Direct3D 8/9/10/11转…...