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启动流程,即springboot-mvc <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.3.0</version> </dependency>环境…...
Rust实现智能助手 - 项目初始化
文章目录 前言环境准备依赖代码运行使用最后 前言 你好,我是醉墨居士,最近准备花一些时间来使用Rust语言实现一个智能助手,希望能够帮助到你。 环境准备 安装Rust语言环境,你可以从官网下载安装包安装。安装Ollama,…...

sparkSQL练习
1.前期准备 (1)建议先把这两篇文章都看一下吧,然后把这个项目也搞下来 (2)看看这个任务 (3)score.txt student_id,course_code,score 108,3-105,99 105,3-105,88 107,3-105,77 105,3-245,87 1…...

QT跨平台应用程序开发框架(2)—— 初识QT
目录 一,创建helloworld 1.1 通过图形化 1.2 通过代码 1.3 通过编辑框 1.4 使用按钮 二,对象树 2.1 关于对象树 2.2 演示释放流程 三,乱码问题 3.1 为什么会有乱码问题 3.2 解决乱码问题 四,认识Qt坐标系 五…...
[创业之路-248]:《华为流程变革:责权利梳理与流程体系建设》华为流程的前端拉动后端,与计算机软件的前端应用与后端程序的类比关系
华为的前端拉动后端模式与计算机前端应用与后端程序的类比关系,虽然两者属于不同的领域,但在某些方面存在有趣的相似性。以下是对这两者的类比关系的详细探讨: 一、华为的前端拉动后端模式 定义与特点: 华为的前端拉动后端模式是…...
汇总统计数据--SQL中聚集函数的使用
目录 1、为什么需要汇总数据 2、聚集函数 (1)AVG函数 (2)COUNT函数 (3)MAX和MIN函数 (4)SUM函数 3、聚集不同值--DISTINCT 4、组合聚集函数 5、小结 博主用的是mysql8 DBMS…...

【C盘清理】C盘清理工具、Unity缓存文件转移
链接: https://pan.baidu.com/s/1yE_7qF741o4NmBIsrd3XzA?pwdbwnn CCleaner 用于清理磁盘垃圾 勾选你要分析的选项,点击分析,分析完毕后,点击清理。 主要别清错东西了。(可以不要勾选网络缓存、网络记录相关的选项࿰…...
C# 迭代,递归,回调--13
目录 一.迭代 迭代器示例: 关键点: 优势: 二.递归 递归示例: 关键点: 优势: 注意: 三.回调 回调示例: 关键点: 优势: 应用场景: 4.三种模式的特点对比: 迭代: 递归: 回调: 一.迭代 在C#中迭代通常指重复执行一系列指令 在C#中,迭代器是一种特殊的结构,允许…...
海康大数据面试题及参考答案
请详细描述 YARN 提交程序的流程。 YARN(Yet Another Resource Negotiator)是一个资源管理系统,用于管理集群中的计算资源。以下是在 YARN 中提交程序的详细流程: 首先是客户端准备阶段。用户编写好应用程序,这个程序可以是 MapReduce、Spark 或者其他基于 YARN 的计算框架…...

软件测试 —— 自动化测试(Selenium)
软件测试 —— 自动化测试(Selenium) 什么是SeleniumPython安装Selenium1.安装webdirver-manager2.安装Selenium 写一个简单用例CSS_SELECTOR和XPATH浏览器快速定位页面元素浏览器的前进(forward),后退(bac…...
华为2024嵌入式研发面试题
01 你认为最好的排序算法是什么? 在实际的编程中,最好的排序算法要根据实际需求和数据规模来选择,因为每种排序算法都有其优势和劣势。以下是一些常见排序算法及其优缺点: 冒泡排序 冒泡排序是一种简单直观的排序算法࿰…...

centos 搭建nginx+配置域名+windows访问
准备工作:一个完整的centos环境,nginx安装包(可以从官网下载)nginx: download 一:centos可能有精简版,部分环境没有相关依赖包, 需要检查以下项: 1.gcc检查:gcc -v(回车后应当有版…...

APP推荐:全新TV端来了,8K原画电视版
▌ 软件介绍 B站都不陌生吧,一个能追番、学习、娱乐的多元平台,之前也分享过几款第三方TV端,其中的BV最近更新了全新版本。 使用了全新的UI界面,由之前的顶部菜单栏改成了侧边布局,已解锁限制&…...

【MySQL】索引(一)
索引 一、磁盘1、物理结构2、示意图3、定位扇区4、读写操作的基本方式 二、页1、介绍2、示例3、作用与结构4、类型(1)数据页(2)其他 5、组织与管理6、性能优化7、示意图(B树) 三、索引1、作用2、注意事项 四…...
ES6的高阶语法特性
一、模板字符串的高级用法 1.1.模板字符串的嵌套 模板字符串的嵌套允许在一个模板字符串内部再嵌入一个或多个模板字符串。这种嵌套结构在处理复杂数据结构或生成具有层级关系的文本时非常有用。 1. 嵌套示例 假设我们有一个包含多个对象的数组,每个对象都有名称、…...

GO:GO程序如何处理缓存加载和大数据缓存
如果我们会在程序启动时,需要加载所有数据,最简单的方式就是程序启动,通过轮训从数据库拉取所有数据,并写入到本地缓存中。 问题:数据量较大的时候,程序加载慢,启动时间长,遇到问题不…...
时序数据库TDengine 3.3.5.0 发布:高并发支持与增量备份功能引领新升级
近日,TDengine 3.3.5.0 版本正式发布,带来了多项重磅更新与优化,从功能拓展到性能提升,再到用户体验进行了全面改进。本次更新围绕用户核心需求展开,涵盖了开发工具、数据管理、安全性、可视化等多个层面,为…...
信息系统项目管理-采购管理-采购清单示例
序号类别产品/服务名称规格/功能描述数量备注1硬件服务器高性能处理器,大容量存储10HP、DELL2网络设备高速路由器和交换机10华为3工作站多核处理器,高分辨率显示器25国产设备4移动检查设备手持式移动检查仪,可连接云平台30国产设备5打印机和扫…...

python识别图片中指定颜色的图案并保存为图片
示例代码: def chuli(color):import cv2import numpy as np# 定义颜色名称到HSV阈值范围的映射color_thresholds {red: ([0, 100, 100], [10, 255, 255], [160, 100, 100], [180, 255, 255]),yellow: ([20, 100, 100], [30, 255, 255]),blue: ([90, 100, 100], [1…...

【git命令行】git pull冲突如何使用stash暂存,不提交当前工作的情况下临时保存修改
1、git add . 暂存区暂存 2、git stash save "message" 保存当前工作目录的临时状态,并将其存储为一个新的stash 3 、git pull 重新拉取 4、**git stash pop**吐出之前暂存的改动,git stash clear 清空所有暂存...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门 
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

Ubuntu系统多网卡多相机IP设置方法
目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机,交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息,系统版本:Ubuntu22.04.5 LTS;内核版本…...

C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...

HTML版英语学习系统
HTML版英语学习系统 这是一个完全免费、无需安装、功能完整的英语学习工具,使用HTML CSS JavaScript实现。 功能 文本朗读练习 - 输入英文文章,系统朗读帮助练习听力和发音,适合跟读练习,模仿学习;实时词典查询 - 双…...
Python打卡训练营学习记录Day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
SE(Secure Element)加密芯片与MCU协同工作的典型流程
以下是SE(Secure Element)加密芯片与MCU协同工作的典型流程,综合安全认证、数据保护及防篡改机制: 一、基础认证流程(参数保护方案) 密钥预置 SE芯片与MCU分别预置相同的3DES密钥(Key1、Key2…...