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

spring-mvc源码分析v3.3.0

分析下springboot内嵌tomcat启动流程,即springboot-mvc

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.3.0</version>
</dependency>

环境信息

  • Java 22
  • Spring Boot v3.3.0
  • Apache Tomcat/10.1.24
  • spring-boot-starter-web 3.3.0

测试项目主要文件结构:

@RestController
public class Controller {@GetMapping("/test")public String test(){return "test";}
}

参考文章

  • springboot启动流程
  • tomcat源码分析

下面开始分析源码

1. 创建tomcat服务

要从启动springboot开始说起,在springApplication.run.refreshContext.refresh.onRefresh这一步中,创建tomcat服务。

@Override
protected void onRefresh() {super.onRefresh();//设置springboot主题。主要用于国际化和本地化的场景。它允许应用程序根据用户的区域设置动态地更改界面的外观和感觉try {createWebServer();//创建tomcat}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}
}private void createWebServer() {WebServer webServer = this.webServer;//nullServletContext servletContext = getServletContext();//nullif (webServer == null && servletContext == null) {//trueStartupStep createWebServer = getApplicationStartup().start("spring.boot.webserver.create");//记录web开始创建步骤//webServer创建工厂,这个是重点ServletWebServerFactory factory = getWebServerFactory();createWebServer.tag("factory", factory.getClass().toString());this.webServer = factory.getWebServer(getSelfInitializer());createWebServer.end();getBeanFactory().registerSingleton("webServerGracefulShutdown",new WebServerGracefulShutdownLifecycle(this.webServer));//bean注册webServerStartStopgetBeanFactory().registerSingleton("webServerStartStop",new WebServerStartStopLifecycle(this, this.webServer));}else if (servletContext != null) {try {getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context", ex);}}//初始化配置属性,配置环境initPropertySources();
}

1.1. webServer创建工厂getWebServerFactory()

因为程序执行到这一步的时候,springboot beanFactory已经完成所有的bean定义了,然后获取ServletWebServerFactory类型的bean,然后实例化

protected ServletWebServerFactory getWebServerFactory() {// Use bean names so that we don't consider the hierarchyString[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);//只有一个元素:tomcatServletWebServerFactoryif (beanNames.length == 0) {throw new MissingWebServerFactoryBeanException(getClass(), ServletWebServerFactory.class,WebApplicationType.SERVLET);}if (beanNames.length > 1) {throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));}//createBean并返回 factory = {TomcatServletWebServerFactory@6532} return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

1.2. 实例化webServer服务factory.getWebServer(getSelfInitializer())

getSelfInitializer()是一个Lambda方法引用。

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {return this::selfInitialize;
}
//context初始化的时候执行
private void selfInitialize(ServletContext servletContext) throws ServletException {for (ServletContextInitializer beans : getServletContextInitializerBeans()) {beans.onStartup(servletContext);}
}

继续看getWebServer方法。这一步就是模仿了原生的tomcat启动流程,创建server、service、connector、egine、host、context
值得注意的是内嵌的tomcat是直接创建了一个context。而不是扫描webapps目录

//默认的连接协议,nio
public static final String protocol = "org.apache.coyote.http11.Http11NioProtocol";public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {//trueRegistry.disableRegistry();//禁用tomcat注册对象到MBean}Tomcat tomcat = new Tomcat();//这个Tomcat类里面是一些基本的操作,基本的属性,我认为这就是一个配置上下文类,如下// protected Server server;// protected int port = 8080;// protected String hostname = "localhost";File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");//C:\Users\SHENSH~1\AppData\Local\Temp\tomcat.8080.1801441663765902424tomcat.setBaseDir(baseDir.getAbsolutePath());//创建Connector,构造方法中创建了Http11NioProtocolConnector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);//创建Server和Servicetomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);registerConnectorExecutor(tomcat, connector);//创建Host和Enginetomcat.getHost().setAutoDeploy(false);//禁用自动部署,不扫描webApps目录//创建contextprepareContext(tomcat.getHost(), initializers);//创建tomcatWebServer,返回给springboot,用于后续操作return getTomcatWebServer(tomcat);
}

1.2.1. 创建Connector

new Connector("org.apache.coyote.http11.Http11NioProtocol");public Connector(String protocol) {configuredProtocol = protocol;ProtocolHandler p = null;try {p = ProtocolHandler.create(protocol);} catch (Exception e) {log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);}
}

1.2.2. 创建Server和Service

public Service getService() {return getServer().findServices()[0];
}
public Server getServer() {if (server != null) {return server;}System.setProperty("catalina.useNaming", "false");server = new StandardServer();//创建StandardServerinitBaseDir();// Set configuration sourceConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));server.setPort(-1);Service service = new StandardService();//创建StandardServiceservice.setName("Tomcat");server.addService(service);return server;
}

1.2.3. 创建Host和Engine

public Host getHost() {Engine engine = getEngine();if (engine.findChildren().length > 0) {//获取hostreturn (Host) engine.findChildren()[0];}Host host = new StandardHost();//创建StandardHosthost.setName(hostname);getEngine().addChild(host);return host;
}public Engine getEngine() {Service service = getServer().findServices()[0];if (service.getContainer() != null) {//获取enginereturn service.getContainer();}Engine engine = new StandardEngine();//创建StandardEngineengine.setName("Tomcat");engine.setDefaultHost(hostname);engine.setRealm(createDefaultRealm());service.setContainer(engine);return engine;
}

1.2.4. 创建Context

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {TomcatEmbeddedContext context = new TomcatEmbeddedContext();//创建TomcatEmbeddedContext,这个是内嵌的tomcat上下文ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);host.addChild(context);configureContext(context, initializersToUse);//配置context。没有重要逻辑
}

1.2.5. 创建tomcatWebServer封装对象

实例化TomcatWebServer、执行server的init方法

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {Assert.notNull(tomcat, "Tomcat Server must not be null");this.tomcat = tomcat;this.autoStart = autoStart;this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;initialize();//启动server,执行start方法
}
1.2.5.1. 启动server,执行start方法

initialize();
//启动server,执行start方法
//这个初始化方法和原生tomcat的start方法一样
//值得注意的是,这里disableBindOnInit禁用了初始化时候绑定8080端口

//StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[]
Context context = findContext();
context.addLifecycleListener((event) -> {if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {// Remove service connectors so that protocol binding doesn't// happen when the service is started.//当执行start context时候,会删除service中的connector,放到TomcatWebServer中removeServiceConnectors();}
});
//t禁用了初始化时候绑定8080端口
disableBindOnInit();// Start the server to trigger initialization listeners
this.tomcat.start();

我们重点看下TomcatEmbeddedContext的启动代码

// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer,Set<Class<?>>> entry : initializers.entrySet()) {try {//entry.getKey(),key = {TomcatStarter@6708}entry.getKey().onStartup(entry.getValue(), getServletContext());} catch (ServletException e) {log.error(sm.getString("standardContext.sciFail"), e);ok = false;break;}
}@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {try {for (ServletContextInitializer initializer : this.initializers) {//initializer = {ServletWebServerApplicationContext$lambda@6808} initializer.onStartup(servletContext);}}catch (Exception ex) {}
}
//这个onStartup方法,正是context初始化的时候执行的,参考【## 1.2. 实例化webServer服务】标题
private void selfInitialize(ServletContext servletContext) throws ServletException {for (ServletContextInitializer beans : getServletContextInitializerBeans()) {//在beanFactory中匹配ServletContextInitializer类型的//beans = {DispatcherServletRegistrationBean@6931} "dispatcherServlet urls=[/]"beans.onStartup(servletContext);}
}//最终是创建了StandardWrapper并添加到context中
//servlet = {DispatcherServlet@6943}  这个就是最重要的DispatcherServlet,是在DispatcherServletAutoConfiguration类中创建的
wrapper = new StandardWrapper();
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServlet(servlet);

1.3. 注册tomcat bean生命周期WebServerStartStopLifecycle

getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
这个类WebServerStartStopLifecycle继承了Lifecycle

2. connector启动

上面分析过,执行context的启动时候,移除了connector,那么又是在哪一步启动的connector呢?

springApplication.run.refreshContext.refresh.finishRefresh这一步中,connector启动。

// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
//onRefresh调用了startBeans
try {startBeans(true);
}private void startBeans(boolean autoStartupOnly) {Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();//beanFactory中匹配Lifecycle类型的bean//包含WebServerStartStopLifecyclelifecycleBeans.values().forEach(LifecycleGroup::start);
}//this = {TomcatWebServer@5764} 
public void start() throws WebServerException {addPreviouslyRemovedConnectors();
}private void addPreviouslyRemovedConnectors() {Service[] services = this.tomcat.getServer().findServices();for (Service service : services) {//service = {StandardService@6979} "StandardService[Tomcat]"Connector[] connectors = this.serviceConnectors.get(service);if (connectors != null) {for (Connector connector : connectors) {//connector = {Connector@7663} "Connector["http-nio-8080"]" 添加connector到serviceservice.addConnector(connector);}this.serviceConnectors.remove(service);}}
}//启动connector
public void addConnector(Connector connector) {connector.setService(this);connector.start();
}//绑定8080端口
bindWithCleanup();// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();// Start acceptor thread
acceptor = new Acceptor<>(this);
String threadName = getName() + "-Acceptor";
acceptor.setThreadName(threadName);
Thread t = new Thread(acceptor, threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();

这里启动了2个线程执行nio模式。acceptor线程用于阻塞监听8080端口,获取到新连接socketChannel,存到tomcat的events事件队列;
poller线程用于循环读取events事件队列,获取新新连接socketChannel,最终通过Selector获取已准备好的channel,执行具体的controller逻辑。
详情请参考另一篇tomcat源码分析文章。

3. 内嵌tomcatEmbedded请求流程

到此内嵌tomcatEmbedded已经启动成功,接下来我们请求GET http://localhost:8080/test来分析一下执行流程。也和原生tomcat差不多。
我们从阻塞监听8080端口开始分析流程。

3.1. Acceptor获取新连接socketChannel

下面这个方法是sun.nio.ch.ServerSocketChannelImpl#implAccept源码java中的。

private int implAccept(FileDescriptor fd, FileDescriptor newfd, SocketAddress[] saa)throws IOException
{//此类实现 IP 套接字地址 (IP 地址 + 端口号)InetSocketAddress[] issa = new InetSocketAddress[1];//阻塞方法,是native方法,监听8080端口,直到有新连接请求int n = Net.accept(fd, newfd, issa);//我们调用[GET http://localhost:8080/test]后,代码继续执行if (n > 0) //n = 1saa[0] = issa[0];return n;
}//最终返回SocketChannelImpl对象
return new SocketChannelImpl(provider(), family, newfd, sa);//然后进入到NioEndpoint
endpoint.setSocketOptions(socket);//注册socketChannel到events
protected boolean setSocketOptions(SocketChannel socket) {NioChannel channel = new NioChannel();NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);channel.reset(socket, newWrapper);//注册poller.register(newWrapper);return true;
}public void register(final NioSocketWrapper socketWrapper) {socketWrapper.interestOps(SelectionKey.OP_READ);//读事件PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);//events = new SynchronizedQueue<>()events.offer(pollerEvent);
}

3.2. Poller消费SocketChannel

poller线程一直是死循环读取events,然后调用Processor协议的处理器

@Override
public void run() {while (true) {//读取eventsevents();Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator != null && iterator.hasNext()) {SelectionKey sk = iterator.next();NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();//调用协议的处理器processKey(sk, socketWrapper);}}
}//读取events
public boolean events() {PollerEvent pe = null;//遍历eventsfor (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {result = true;SocketChannel sc = socketWrapper.getSocket().getIOChannel();final SelectionKey key = sc.keyFor(getSelector());final NioSocketWrapper attachment = (NioSocketWrapper) key.attachment();//设置socketChannel为读或写事件int ops = key.interestOps() | interestOps;attachment.interestOps(ops);key.interestOps(ops);}
}//调用协议的处理器 {NioEndpoint$Poller@6539}
protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {if (sk.isReadable()) {//读,我们这里的[GET http://localhost:8080/test]请求是读事件processSocket(socketWrapper, SocketEvent.OPEN_READ, true)}if (sk.isWritable()) {processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)}
}//封装Runnable接口,异步处理socket请求
public boolean processSocket(SocketWrapperBase<S> socketWrapper, SocketEvent event, boolean dispatch) {SocketProcessorBase<S> sc = new SocketProcessor(socketWrapper, event);//Runnable接口实现类Executor executor = getExecutor();//异步执行executor.execute(sc);    
}@Override
public void run() {//getHandler() = {AbstractProtocol$ConnectionHandler@6630} getHandler().process(socketWrapper, event);
}//Http11Processor继续处理socket
if (status == SocketEvent.OPEN_READ) {state = service(socketWrapper);
}//下面这几个方法,看之前先了解一下tomcat组件的关系图
//【servlet封装成wrapper,wrapper添加到context,context是host的子容器,host属于engine,engine在service中,service是顶级容器server的子容器。】//getAdapter() = {CoyoteAdapter@8348} 
getAdapter().service(request, response);
//通过请求路径匹配对应的wrapper,根据[localhost:8080/test]匹配
//因为这里是内嵌的tomcat,匹配到了默认的StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[].StandardWrapper[dispatcherServlet]
postParseSuccess = postParseRequest(req, request, res, response);
//engine
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
//host
host.getPipeline().getFirst().invoke(request, response);
//context = {TomcatEmbeddedContext@8433}
context.getPipeline().getFirst().invoke(request, response);
//wrapper
wrapper.getPipeline().getFirst().invoke(request, response);
//过滤器,StandardWrapperValve里面调用了过滤器链
filterChain.doFilter(request.getRequest(), response.getResponse());
//filterChain = {ApplicationFilterChain@8496}//执行到最后一个过滤器后,再调用service,这里的servlet是 {DispatcherServlet@8470} 
servlet.service(request, response);//调用doGet方法
if (method.equals(METHOD_GET))doGet(req, resp);doService(request, response);
//所有的请求最终都走到了DispatcherServlet的doDispatch
doDispatch(request, response);

3.3. DispatcherServlet的doDispatch

这里会DispatcherServlet通过请求路径匹配对应的mappedHandler,然后调用Controller层逻辑并获取返回值,再把返回值封装到ModelAndView

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {//通过请求路径匹配对应的mappedHandlermappedHandler = getHandler(request);//mappedHandler = [cn.xxx.updownloadfile.contr.Controller#test()] 这个是我项目中自己的Controller层的类//再通过反射调用cn.xxx.updownloadfile.contr.Controller#test()方法,获取mv//如果是@ResponseBody修饰的,这个mv返回是空,如果没有这个注解,则返回对应的文件名称,例如返回"t.html"ModelAndView mv = mappedHandler.handle(processedRequest, response); //method.invoke(getBean(), args)//处理程序选择和处理程序调用的结果,即 ModelAndView//如果mv不是null,则会匹配项目中的对应文件,读取文件内容然后返回给前端,例如读取文件t.htmlprocessDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

相关文章:

spring-mvc源码分析v3.3.0

分析下springboot内嵌tomcat启动流程&#xff0c;即springboot-mvc <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.3.0</version> </dependency>环境…...

反爬风控设计之请求头顺序检测

本次主要讲解请求头的顺序检测的反爬实现&#xff0c; 如下使用Django写的headers请求头顺序检验算法&#xff1a; def index(request):# 获取请求头的顺序request_order list(request.META.keys())print(request_order)# 定义标准请求头顺序standard_order ["HTTP_HO…...

力扣 查找元素的位置

二分查找经典例题。 题目 要是只是从数组中用二分查找对应的元素&#xff0c;套一下模板一下就可以得出了&#xff0c;然后这题就在于其中会有多个目标元素&#xff0c;要用不同的方式在找到第一个元素时再做偏移。 时间复杂度&#xff1a;O(log n)&#xff0c;空间复杂度&am…...

Android-V lmkd 中的那些属性值

源码基于&#xff1a;Android V 相关博文&#xff1a; Android lmkd 机制详解&#xff08;一&#xff09; Android lmkd 机制详解&#xff08;二&#xff09; Android lmkd 机制从R到T 1. 汇总 属性名说明默认值 ro.lmk.debug 启动 lmkd 的debug 模式&#xff0c;会打印一…...

LabVIEW 程序中的 R6025 错误

R6025错误 通常是 运行时库 错误&#xff0c;特别是与 C 运行时库 相关。这种错误通常会在程序运行时出现&#xff0c;尤其是在使用 C 编译的程序或依赖 C 运行时库的程序时。 ​ 可能的原因&#xff1a; 内存访问冲突&#xff1a; R6025 错误通常是由于程序在运行时访问无效内…...

vmware虚拟机配置ubuntu 18.04(20.04)静态IP地址

VMware版本 &#xff1a;VMware Workstation 17 Pro ubuntu版本&#xff1a;ubuntu-18.04.4-desktop-amd64 主机环境 win11 1. 修改 VMware虚拟网络编辑器 打开vmware&#xff0c;点击顶部的“编辑"菜单&#xff0c;打开 ”虚拟化网络编辑器“ 。 选择更改设置&#…...

mybatis延迟加载、缓存

目录 一、所需表 二、延迟加载 1.延迟加载概念 2.立即加载和延迟加载的应用场景 3.多对一延迟加载查询演示 (1)实体类 User Account (2)AccountMapper接口 (3)AccountMapper.xml (4)UserMapper接口 (5)UserMapper.xml (6)在总配置文件(mybatis-config.xml)中开启延…...

云上贵州多彩宝荣获仓颉社区先锋应用奖 | 助力数字政务新突破

在信息技术应用创新的浪潮中&#xff0c;仓颉社区吸引了众多企业和开发者的积极参与&#xff0c;已有多个应用成功落地&#xff0c;展现出蓬勃的创新活力。仓颉编程语言精心遴选了在社区建设、应用创新、开源共建、技术布道等方面做出突出贡献的优秀项目应用&#xff0c;并颁发…...

设计模式02:结构型设计模式之适配器模式使用情景及其基础Demo

1.适配器模式 用途&#xff1a;接口兼容评价&#xff1a;复杂、冗余、难以调试&#xff0c;个人认为直接在旧系统那里封装一个新实现调用旧实现就好了场景&#xff1a;系统A、B、C想调用同一个功能接口&#xff0c;但是实现细节存在差异时&#xff08;其实就是入参和出参转化处…...

镭速大文件传输视频文件预览实现原理

镭速可以支持视频预览&#xff0c;在测试过程中需要大量不同格式的视频&#xff0c;如果直接去找各种格式的视频不太现实&#xff0c;所以就会用到一个视频格式转换的工具ffmpeg&#xff0c;本文将介绍ffmpeg的基本使用方法。FFmpeg 是一个免费开源的音视频处理工具&#xff0c…...

Mac上安装Label Studio

在Mac上安装Anaconda并随后安装Label Studio&#xff0c;可以按照以下步骤进行&#xff1a; 1. 在Mac上安装Anaconda 首先&#xff0c;你需要从Anaconda的官方网站下载适用于Mac的安装程序。访问Anaconda官网&#xff0c;点击“Download Anaconda”按钮&#xff0c;选择适合M…...

git操作(Windows中GitHub)

使用git控制GitHub中的仓库版本&#xff0c;并在Windows桌面中创建与修改代码&#xff0c;与GitHub仓库进行同步。 创建自己的GitHub仓库 创建一个gen_code实验性仓库用来学习和验证git在Windows下的使用方法&#xff1a;https://github.com/Martianing/gen_code git初始设置…...

低代码平台:技术复杂性的系统简化

在传统开发模式下&#xff0c;应用构建需要经历需求分析、代码开发、测试部署等多环节&#xff0c;流程繁琐且耗时&#xff0c;往往成为企业技术创新的瓶颈。低代码平台通过模块化和自动化技术重新定义开发流程&#xff0c;使开发者能够在较短时间内实现复杂的应用功能&#xf…...

物联网网关Web服务器--lighttpd服务器部署与应用测试

以下是在国产ARM处理器E2000飞腾派开发板上部署 lighttpd 并进行 CGI 应用开发的步骤&#xff1a; 1、lighttpd简介 Lighttpd 是一款轻量级的开源 Web 服务器软件&#xff0c;具有以下特点和功能&#xff1a; 特点 轻量级&#xff1a;Lighttpd 在设计上注重轻量级和高效性&a…...

Flutter+vsCode 安装问题记录

VSCode在安装完AndroidSDK以及相关插件后&#xff0c;运行Flutter项目时选择模拟器提示&#xff1a;avdmanager is missing from the Android SDK&#xff0c;确保环境变量中存在ANDROID_HOME&#xff0c;且执行了flutter config --android-sdk 地址后&#xff0c;如果还提示&a…...

【CompletableFuture实战】

CompletableFuture实战 前言 前言 过去的一年&#xff0c;匆匆忙忙&#xff0c;换了一次工作&#xff0c;写博客的习惯就落下了&#xff0c;总之&#xff0c;有点懈怠。希望今年能重拾信心&#xff0c;步入正规&#xff01; CompletableFuture的用法网上资料颇多&#xff0c;…...

【k8s面试题2025】3、练气中期

体内灵气的量和纯度在逐渐增加。 文章目录 在 Kubernetes 中自定义 Service端口报错常用控制器Kubernetes 中拉伸收缩副本失效设置节点容忍异常时间Deployment 控制器的升级和回滚日志收集资源监控监控 Docker将 Master 节点设置为可调度 在 Kubernetes 中自定义 Service端口报…...

【高可用自动化体系】自动化体系

架构设计的愿景就是高可用、高性能、高扩展、高效率。为了实现架构设计四高愿景&#xff0c;需要实现自动化系统目标&#xff1a; 标准化。 流程自助化。 可视化&#xff1a;可观测系统各项指标、包括全链路跟踪。 自动化&#xff1a;ci/cd 自动化部署。 精细化&#xff1a…...

Spring boot框架下的RocketMQ消息中间件

1. RocketMQ 基础概念 1.1 核心概念 以下是 RocketMQ 核心概念在 Spring Boot 的 Java 后端代码中的实际使用方式&#xff1a; Producer&#xff08;生产者&#xff09; 定义&#xff1a;Producer 是负责发送消息到 RocketMQ 的组件。它可以将消息发送到指定的 Topic。 实…...

http转化为https生成自签名证书

背景 项目开发阶段前后交互采用http协议&#xff0c;演示环境采用htttps协议 &#xff0c;此处为个人demo案例 组件 后端&#xff1a;springBoot 前端&#xff1a;vue web 服务&#xff1a;tomcat 部署环境&#xff1a;linux 生成自签名证书 创建目录 存储证书位置 # mkdir -p…...

关于2025年智能化招聘管理系统平台发展趋势

2025年&#xff0c;招聘管理领域正站在变革的十字路口&#xff0c;全新的技术浪潮与不断变化的职场生态相互碰撞&#xff0c;促使招聘管理系统成为重塑企业人才战略的关键力量。智能化招聘管理系统平台在这一背景下迅速崛起&#xff0c;其发展趋势不仅影响企业的招聘效率与质量…...

CentOS 9 Stream 上安装 Node.js 18.20.5

要在 CentOS 9 Stream 上安装 Node.js 18.20.5&#xff0c;可以按照以下步骤操作&#xff1a; 1. 安装依赖 首先&#xff0c;确保你已经更新了系统并安装了必要的依赖包。 sudo dnf update -y sudo dnf install -y gcc-c make2. 安装 Node.js 18.20.5 Node.js 官方提供了一…...

NSIS 创建一键安装程序

nsis 安装redis 、mysql 、jdk navicat、 notepad、 使用NSIS 创建一键安装程序 分为两步 下载 NSIS编写 一键安装代码 1.16脚本 ; 请求管理员权限运行安装程序 RequestExecutionLevel admin; 该脚本使用 HM VNISEdit 脚本编辑器向导产生; 安装程序初始定义常量 !define PRO…...

NanoKVM简单开箱测评和拆解,让普通电脑实现BMC/IPMI远程管理功能

Sipeed推出了NanoKVM&#xff0c;简直是没有BMC的台式机和工作站的福音。有了这个就可以轻松实现以往服务器才有的远程管理功能。 NanoKVM 简介 Lichee NanoKVM 是基于 LicheeRV Nano 的 IP-KVM 产品&#xff0c;继承了 LicheeRV Nano 的极致体积 和 强大功能。 NanoKVM 包含…...

【混合开发】CefSharp+Vue桌面应用程序开发

为什么选择CefSharpVue做桌面应用程序 CefSharp 基于 Chromium Embedded Framework (CEF) &#xff0c;它可以将 Chromium 浏览器的功能嵌入到 .NET 应用程序中。通过 CefSharp&#xff0c;开发者可以在桌面应用程序中集成 Web 技术&#xff0c;包括 HTML、JavaScript、CSS 等…...

2024最新版JavaScript逆向爬虫教程-------基础篇之Chrome开发者工具学习

目录 一、打开Chrome DevTools的三种方式二、Elements元素面板三、Console控制台面板四、Sources面板五、Network面板六、Application面板七、逆向调试技巧 7.1 善用搜索7.2 查看请求调用堆栈7.3 XHR 请求断点7.4 Console 插桩7.5 堆内存函数调用7.6 复制Console面板输出 工…...

下定决心不去读研了。。。

大家好&#xff0c;我是苍何。 之前发表过一篇文章&#xff0c;表达了自己读研的困惑和纠结&#xff0c;得到了大家很多的建议&#xff0c;也引起了很多人的共鸣&#xff0c;在留言区分享了自己的故事&#xff0c;看着这些故事&#xff0c;我觉得都够苍何写一部小说了。 可惜苍…...

Java21 正则表达式

在 Java 21 中&#xff0c;正则表达式主要通过 java.util.regex 包提供支持&#xff0c;其核心组件包括 Pattern、Matcher 和 String 类中自带的方法&#xff08;如 replaceAll 和 matches&#xff09;。以下是关于正则表达式在 Java 21 中的详细介绍及一些新的特性或用法。 核…...

Docker安装PostGreSQL docker安装PostGreSQL 完整详细教程

Docker安装PostGreSQL docker安装PostGreSQL 完整详细教程 Docker常用命令大全Docker 运行命令生成Docker 上安装 PostGreSQL 14.15 的步骤&#xff1a;1、拉取 PostGreSQL 14.15 镜像2、创建并运行容器3、测试连接4、设置所有IP都可以运行连接进入容器内 修改配置文件关闭容器…...

leetcode:205. 同构字符串(python3解法)

难度&#xff1a;简单 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符&#xff0c;同时不改变字符的顺序。不同字符不能映射到同一个字…...

【Javascript Day9】对象定义、数组中对象元素排序、对象在内存中存储方法、对象构建联系

目录 . 取值运算符 > 用于对象属性或方法的调用操作 [] 取值运算符 > 可用于数组下标或者对象属性的取值操作 数组对象的排序 对象在内存中存储方式 对象的三种定义方式 1. 字面量对象 2. 基于Object构造对象 3. 自定义对象构造器创建对象 对象的构建练习 . 取值…...

运维作业一

1、shell 脚本写出检测 /tmp/size.log 文件如果存在显示它的内容&#xff0c;不存在则创建一个文件将创建时间写入。 2、写一个 shel1 脚本,实现批量添加 20个用户,用户名为user01-20,密码为user 后面跟5个随机字符。 首先&#xff0c;获得随机字符&#xff0c;需下载pwgen&am…...

数仓建模(三)建模三步走:需求分析、模型设计与数据加载

本文包含&#xff1a; 数据仓库的背景与重要性数据仓库建模的核心目标本文结构概览&#xff1a;需求分析、模型设计与数据加载 目录 第一部分&#xff1a;需求分析 1.1 需求分析的定义与目标 1.2 需求分析的步骤 1.2.1 业务需求收集 1.2.2 技术需求分析 1.2.3 成果输出…...

C语言的网络编程

C语言的网络编程 引言 随着互联网的快速发展&#xff0c;网络编程已经成为计算机科学与技术领域中不可或缺的一部分。C语言作为一种底层语言&#xff0c;以其高效、快速和灵活的特性&#xff0c;广泛应用于网络编程中。本文将深入探讨C语言在网络编程中的应用&#xff0c;包括…...

EE213 Lab3 virtuoso NAND NOR INV XOR HA designlayout(min size layout method)

目录 0 前言 1 设计目标 2 减小面积的layout画法 3 NAND 4 NOR 5 INV 6 XOR 7 HA 0 前言 记录一下来到skd上的强度比较大的一门课&#xff0c;数字集成电路2的lab设计还是蛮好的&#xff0c;该帖非详细教程只是单纯的写一些思虑并用作笔记&#xff0c;新手小白欢迎交…...

Qt——QTableWidget 限制单元格输入范围的方法(正则表达式输入校验法、自定义代理类MyItemDelegrate)

【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C++语言开发基础总结》 《从0到1学习嵌入式Linux开发》...

mono3d汇总

lidar坐标系 lidar坐标系可以简单归纳为标准lidar坐标系和nucense lidar坐标系&#xff0c;参考链接。这个坐标系和车辆的ego坐标系是一致的。 标准lidar坐标系 opendet3d&#xff0c;mmdetection3d和kitt都i使用了该坐标系 up z^ x front| /| /left y <------ 0kitti采…...

机器学习第一道菜(一):线性回归的理论模型

机器学习第一道菜&#xff08;一&#xff09;&#xff1a;线性回归的理论模型 一、问题&#xff1a;千金买笑1.1 散点图1.2 机器学习能搞啥 二、模型的建立2.1 线性回归2.2 回归模型 前面讲了机器学习的“四大绝技”&#xff0c;今天&#xff0c;开始研究第一绝技“回归”&…...

Unity的Transform类

1.position 游戏对象的世界坐标以(0, 0, 0)为原点 2.localPosition 本地坐标&#xff0c;相对父物体坐标 3.eulerAngles 相对世界的欧拉角 4.localEulerAngles 本地欧拉角 5.rotation 相对世界四元数 6.localRotation 本地四元…...

指针的进阶

指针的主题&#xff0c;我们在初级阶段的《指针》章节已经接触过了&#xff0c;我们知道了指针的概念&#xff1a; 1. 指针就是个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间。 2. 指针的大小是固定的4/8个字节&#xff08;32位平台/64位平台&#xff0…...

每日学习30分轻松掌握CursorAI:Cursor插件系统与扩展功能

Cursor插件系统与扩展功能 一、课程概述 今天我们将学习Cursor AI的插件系统&#xff0c;了解如何通过插件扩展和增强IDE功能。由于Cursor AI基于VS Code开发&#xff0c;我们可以利用丰富的VS Code插件生态系统。 1.1 学习目标 了解插件系统原理掌握插件安装管理使用常用开…...

【WEB】网络传输中的信息安全 - 加密、签名、数字证书与HTTPS

文章目录 1. 概述2. 网络传输安全2.1.什么是中间人攻击2.2. 加密和签名2.2.1.加密算法2.2.2.摘要2.2.3.签名 2.3.数字证书2.3.1.证书的使用2.3.2.根证书2.3.3.证书链 2.4.HTTPS 1. 概述 本篇主要是讲解讲一些安全相关的基本知识&#xff08;如加密、签名、证书等&#xff09;&…...

Dexie.js内存管理技巧:在大型数据集操作中避免浏览器崩溃

Dexie.js 内存管理技巧&#xff1a;避免浏览器崩溃 在使用 Dexie.js 操作 大型数据集 时&#xff0c;如果不注意内存管理&#xff0c;可能会导致浏览器内存溢出&#xff08;OOM&#xff0c;Out of Memory&#xff09;或崩溃。因此&#xff0c;以下 内存管理技巧 可用于优化性能…...

vscode项目依赖问题

必读 一定要将前端下拉的项目备份一下&#xff0c;很容易运行导致依赖报错&#xff0c;重新下载 命令 使用幽灵分解器安装 pnpm install 替代 npm install 设置淘宝NPM镜像源 yarn config set registry https://registry.npmmirror.com 查看目前依赖包的版本 npm list ant-d…...

LLMs之RAG:《EdgeRAG: Online-Indexed RAG for Edge Devices》翻译与解读

LLMs之RAG&#xff1a;《EdgeRAG: Online-Indexed RAG for Edge Devices》翻译与解读 导读&#xff1a;这篇论文针对在资源受限的边缘设备上部署检索增强生成 (RAG) 系统的挑战&#xff0c;提出了一种名为 EdgeRAG 的高效方法。EdgeRAG 通过巧妙地结合预计算、在线生成和缓存策…...

宇泰串口卡驱动在Ubuntu22.04编译、安装汇总

从官网下载驱动官网地址 上传到Ubuntu, 目录结构如下&#xff1a; 驱动源代码: 驱动代码是基于开源项目编译来的 编译路径不能有中文路径&#xff0c;否则可能有类似错误 源码是基于Linux2.3内核编译&#xff0c;我当前是6.8.0-51&#xff0c;数据结构有升级&#xff0c;需要调…...

python管理工具:conda部署+使用

python管理工具&#xff1a;conda部署使用 一、安装部署 1、 下载 - 官网下载&#xff1a; https://repo.anaconda.com/archive/index.html - wget方式&#xff1a; wget -c https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh2、 安装 在conda文件的…...

(三)html2canvas将HTML 转为图片并实现下载

将 HTML 转为图片并实现下载&#xff0c;通常可以使用一个叫做 html2canvas 的 JavaScript 库。html2canvas 能够将 HTML 元素及其样式渲染成一个画布 (Canvas)&#xff0c;然后将该画布转换为图片格式&#xff08;如 PNG 或 JPEG&#xff09;&#xff0c;最终提供下载功能。 …...

安装Docker流程

1.卸载旧版 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine 2.配置Docker的yum库 首先要安装一个…...

flutter 使用google_mlkit_image_labeling做图片识别

在AI横行的如今&#xff0c;相信大家或多或少都做过跟AI接轨的需求了吧&#xff1f;今天我说的是关于图片识别的需求&#xff0c;flutter的专属图片识别插件google_mlkit_image_labeling。 google_mlkit_image_labeling它是Google旗下的Google Cloud Vision API中分支出来的一部…...