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 清空所有暂存...
JavaScript 中的单例内置对象:Global 与 Math 的深度解析
JavaScript 中的单例内置对象:Global 与 Math 的深度解析 在 JavaScript 的世界中,单例内置对象是开发者必须了解的核心概念之一。它们是语言规范中预定义的对象,无需显式创建即可直接使用。本文将深入解析 JavaScript 中最重要的两个单例内…...

html文字红色粗体,闪烁渐变动画效果,中英文切换版本
1. 代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>红色粗体闪烁文字表格 - 中英文切换</t…...

【SSM】SpringBoot笔记2:整合Junit、MyBatis
前言: 文章是系列学习笔记第9篇。基于黑马程序员课程完成,是笔者的学习笔记与心得总结,供自己和他人参考。笔记大部分是对黑马视频的归纳,少部分自己的理解,微量ai解释的内容(ai部分会标出)。 …...
C#中的依赖注入
1. 依赖注入(Dependency Injection, DI)概述 定义 :依赖注入是一种设计模式,允许将组件的依赖关系从内部创建转移到外部提供。这样可以降低组件之间的耦合度,提高代码的可测试性、可维护性和可扩展性。 核心思想 &…...

网络安全逆向分析之rust逆向技巧
rust逆向技巧 rust逆向三板斧: 快速定位关键函数 (真正的main函数):观察输出、输入,字符串搜索,断点等方法。定位关键 加密区 :根据输入的flag,打硬件断点,快速捕获程序中对flag访问的位置&am…...
k8s下离线搭建elasticsearch
前提 已经完成k8s安装 已经完成相关组件如helm的安装 下载es的chart包 如下地址 https://helm.elastic.co/helm/elasticsearch/elasticsearch-版本号.tgz 如6.8.10 https://helm.elastic.co/helm/elasticsearch/elasticsearch-6.8.10.tgz 修改配置 修改value.yaml文件…...
无字母数字webshell的命令执行
在Web安全领域,WebShell是一种常见的攻击手段,通过它攻击者可以远程执行服务器上的命令,获取敏感信息或控制系统。而无字母数字WebShell则是其中一种特殊形式,通过避免使用字母和数字字符,来绕过某些安全机制的检测。 …...
php中实现邮件发送功能
要在php项目中实现邮件发送功能,推荐使用phpmailer库通过smtp协议配置。首先安装phpmailer扩展,可通过composer命令composer require phpmailer/phpmailer安装;若未使用composer则手动引入源码。接着配置smtp信息,包括服务器地址&…...
基于 BGE 模型与 Flask 的智能问答系统开发实践
基于 BGE 模型与 Flask 的智能问答系统开发实践 一、前言 在人工智能快速发展的今天,智能问答系统成为了提升信息检索效率和用户体验的重要工具。本文将详细介绍如何利用 BGE(Base General Embedding)模型、Faiss 向量检索库以及 Flask 框架…...

【多线程初阶】阻塞队列 生产者消费者模型
文章目录 一、阻塞队列二、生产者消费者模型(一)概念(二)生产者消费者的两个重要优势(阻塞队列的运用)1) 解耦合(不一定是两个线程之间,也可以是两个服务器之间)2) 削峰填谷 (三)生产者消费者模型付出的代价 三、标准库中的阻塞队列(一)观察模型的运行效果(二)观察阻塞效果1) 队…...