1 概述

前面搭建工程的例子,运行的是一个桌面程序,并不是一个Web程序,在这篇中我们把它改为Web程序,同时从启动角度看看它们的区别。

2 Web模式

2.1 桌面例子

回顾一下前面的例子,其pom.xml的配置如下:

// pom.xml
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version><relativePath/>
</parent>
<groupId>com.qqian.stepfmk</groupId>
<artifactId>srvpro</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

代码如下:

@SpringBootApplication
public class SrvproApplication {public static void main(String[] args) {SpringApplication.run(SrvproApplication.class, args);}@Beanpublic CommandLineRunner commandLineRunner(ApplicationContext ctx) {return args -> {System.out.println("Hello World");};}
}

2.2 Web例子

(1) 之前看<parent>节点上一级的parent所用的spring-boot-dependencies的时候,pom文件见 https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-dependencies/2.7.18/spring-boot-dependencies-2.7.18.pom ,里面比较多starter,其中spring-boot-starter-web就是和web有关的starter。在pom.xml中,用spring-boot-starter-web代替spring-boot-starter即可转换为web程序。

// pom.xml
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version><relativePath/>
</parent>
<groupId>com.qqian.stepfmk</groupId>
<artifactId>srvpro</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

查看一下spring-boot-starter-web里的依赖:https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-web/2.7.18/spring-boot-starter-web-2.7.18.pom

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.7.18</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.7.18</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>2.7.18</version><scope>compile</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.31</version><scope>compile</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.31</version><scope>compile</scope></dependency>
</dependencies>

从里面看到,也引用了spring-boot-starter,由这个starter提供springboot的基础功能;另外引用的spring-boot-starter-tomcat、spring-web和spring-webmvc,则提供了web相关的基础功能。

(2) 在入口代码中去掉CommandLineRunner这个bean,在web程序中一般不需要用到它(留着也可以运行)。

// 只留main()方法的运行
@SpringBootApplication
public class SrvproApplication {public static void main(String[] args) {SpringApplication.run(SrvproApplication.class, args);}
}

(3) 新建一个Controller类,提供一个接口方法:

// com.qqian.stepfmk.srvpro.hello.HelloController
@RestController
public class HelloController {@RequestMapping("sayHello")public String say(@RequestParam("message") String messge) {return "Hello world: " + messge;}
}

(4) 运行程序,在控制台上打印的日志

Starting SrvproApplication using Java 1.8.0_60 on DESKTOP-1 with PID 21336
No active profile set, falling back to 1 default profile: "default"
Tomcat initialized with port(s): 8080 (http)
Starting service [Tomcat]
Starting Servlet engine: [Apache Tomcat/9.0.83]
Initializing Spring embedded WebApplicationContext
Root WebApplicationContext: initialization completed in 1602 ms
Tomcat started on port(s): 8080 (http) with context path ''
Started SrvproApplication in 2.673 seconds (JVM running for 3.208)

从日志可以看出,web程序运行在8080端口上,context path是空字符串。

5、从浏览器上访问:http://localhost:8080/sayHello?message=zhangsan,返回以下结果:

Hello world: zhangsan

3 原理

从上面例子看,就更换了一个依赖,再增加Controller接口,就可以用浏览器的方式访问了,main函数里还是只有一行代码这么整洁,传统的tomcat和把war发布到tomcat里等操作都不需要了,简单了很多。如此简洁的代码,是如何实现web功能的?

3.1 run()方法的主流程

// 1. 通过SpringApplication.run运行程序
// 源码位置:com.qqian.stepfmk.srvpro.SrvproApplication
public static void main(String[] args) {SpringApplication.run(SrvproApplication.class, args);
}// 2. 在SpringApplication提供了两个静态方法run,和一个对象方法run,在第二个静态run方法中new了一个SpringApplication,执行对象方法run()
// 源码位置:org.springframework.boot.SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {// 省略部分代码...try {// 3. 创建上下文类,通过上下文类区分是否是web程序context = createApplicationContext();// 4. 初始化程序refreshContext(context);// 5. 执行RunnercallRunners(context, applicationArguments);}// 省略部分代码...return context;
}

3.2 初始化Web应用类型标记

在主流程步骤2里new了一个SpringApplication,在里面初始化了webApplicationType这个Web应用类型标识,应用类型大致分为Servlet Web应用、响应式Web应用、普通应用,这里把Application翻译为“应用”:

// 源码位置:org.springframework.boot.SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 1. 创建SpringApplication对象,并运行其run()方法return new SpringApplication(primarySources).run(args);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 2. 推演Web应用类型标识,因为其是根据依赖的类来确定的,而不是在哪里有对应的配置,所以是推演来的this.webApplicationType = WebApplicationType.deduceFromClasspath();this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();
}// 源码位置:org.springframework.boot.WebApplicationType
public enum WebApplicationType {// 3. 在枚举中定义三种Web应用类型,NONE代表不是Web应用(普通应用),另外两种分别代表Servlet Web应用、响应式Web应用NONE, SERVLET, REACTIVE;// 4. 预先初始化一些帮助推演的常量,大概是当引用的包里面有哪些类的时候,就认为是哪种应用类型// javax.servlet.Servlet在tomcat-embed-core包里,ConfigurableWebApplicationContext在spring-web包里private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };// DispatcherServlet在spring-webmvc包里private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";// DispatcherHandler在spring-webflux包里private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";// ServletContainer在org.glassfish.jersey.containers:jersey-container-servlet-core包里,Jersey是一个Web框架private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";static WebApplicationType deduceFromClasspath() {// 5. 只有引了spring-webflux包且没引另外两个包的任意一个,才是响应式Web模式if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}// 6. 没有引tomcat-embed-core包和spring-web包中的任意一个则不是Web模式for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}// 7. 引了tomcat-embed-core包和spring-web包中的任意一个则是Web模式return WebApplicationType.SERVLET;}
}

3.3 创建上下文

主流程步骤3中的创建上下文SpringApplication.createApplicationContext():

// 源码位置:org.springframework.boot.SpringApplication
protected ConfigurableApplicationContext createApplicationContext() {// 1. 调用工厂的create()方法创建上下文,这里以DefaultApplicationContextFactory工厂为例//    applicationContextFactory为org.springframework.boot.DefaultApplicationContextFactoryreturn this.applicationContextFactory.create(this.webApplicationType);
}
// 源码位置:org.springframework.boot.DefaultApplicationContextFactory
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {try {// 2. 调用getFromSpringFactories方法创建Context//    提供的webApplicationType这个Web应用类型标识作为参数,参考前面推演这个值的说明//    另外两个是方法的引用,类似函数式编程的函数,前一个是预期用来场景Context的,后一个是在没有创建到Context的时候作为默认补救的return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create, AnnotationConfigApplicationContext::new);} catch (Exception ex) {throw new IllegalStateException("Unable create a default ApplicationContext instance, "+ "you may need a custom ApplicationContextFactory", ex);}
}
private <T> T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {// 3. SpringFactoriesLoader.loadFactories()加载到工厂有两个,遍历工厂去调用工厂创建Context对象://     org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory//     org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factoryfor (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) {// 4. action为ApplicationContextFactory::create,尝试根据Web应用类型标识创建Context对象T result = action.apply(candidate, webApplicationType);if (result != null) {return result;}}return (defaultResult != null) ? defaultResult.get() : null;
}// 源码位置:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {// 5. 如果webApplicationType类型为REACTIVE则创建AnnotationConfigReactiveWebServerApplicationContext,否则为nullreturn (webApplicationType != WebApplicationType.REACTIVE) ? null : new AnnotationConfigReactiveWebServerApplicationContext();
}
// 源码位置:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {// 6. 如果webApplicationType类型为SERVLET则创建AnnotationConfigServletWebServerApplicationContext,否则为nullreturn (webApplicationType != WebApplicationType.SERVLET) ? null : new AnnotationConfigServletWebServerApplicationContext();
}// 回到DefaultApplicationContextFactory的getFromSpringFactories()
// 源码位置:org.springframework.boot.DefaultApplicationContextFactory
private <T> T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {// 3. SpringFactoriesLoader.loadFactories()加载到工厂有两个,遍历工厂去调用工厂创建Context对象for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) {// 4. action为ApplicationContextFactory::create,尝试根据Web应用类型标识创建Context对象T result = action.apply(candidate, webApplicationType);// 7. AnnotationConfigReactiveWebApplicationContext.Factory只能创建webApplicationType=REACTIVE的Context对象,两者不匹配时result为AnnotationConfigReactiveWebServerApplicationContext对象//    AnnotationConfigServletWebServerApplicationContext.Factory只能创建webApplicationType=SERVLET的Context对象,两者匹配时result为AnnotationConfigServletWebServerApplicationContext对象//    匹配到一个就返回,REACTIVE的工厂排在前面,优先级更高if (result != null) {return result;}}// 8. 不是web相关的模式则使用默认的org.springframework.context.annotation.AnnotationConfigApplicationContext//    defaultResult为AnnotationConfigApplicationContext::new,defaultResult.get()就是执行new AnnotationConfigApplicationContext()的结果return (defaultResult != null) ? defaultResult.get() : null;
}

3.4 初始化

在主流程4进行初始化refreshContext(context),这个方法名称起得不太表意,就当是刷新吧。

// 源码位置:org.springframework.boot.SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {if (this.registerShutdownHook) {shutdownHook.registerApplicationContext(context);}// 1. 调私refresh()方法刷新refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {// 2. 调用context的refresh()刷新方法//    从上面看这个context可能有三种,需分别大致看一下各个context的刷新://    AnnotationConfigReactiveWebServerApplicationContext//    AnnotationConfigServletWebServerApplicationContext//    AnnotationConfigApplicationContextapplicationContext.refresh();
}

3.4.1 AnnotationConfigReactiveWebServerApplicationContext刷新

AnnotationConfigReactiveWebServerApplicationContext本身并没有refresh()刷新方法,刷新方法来自于父类:

// 源码位置:org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext
// 1. 继承关系:AnnotationConfigReactiveWebServerApplicationContext < ReactiveWebServerApplicationContext < GenericReactiveWebApplicationContext < GenericApplicationContext < AbstractApplicationContext
public class ReactiveWebServerApplicationContext extends GenericReactiveWebApplicationContext implements ConfigurableWebServerApplicationContext {public final void refresh() throws BeansException, IllegalStateException {try {// 2. 调用父类refresh()方法刷新,直接父类GenericReactiveWebApplicationContext、GenericApplicationContext没有重载refresh()方法,//    调的是AbstractApplicationContext的refresh()方法super.refresh();}catch (RuntimeException ex) {WebServerManager serverManager = this.serverManager;if (serverManager != null) {serverManager.getWebServer().stop();}throw ex;}}
}// 源码位置:org.springframework.context.support.AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {            // 省略部分代码try {postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");invokeBeanFactoryPostProcessors(beanFactory);registerBeanPostProcessors(beanFactory);beanPostProcess.end();initMessageSource();initApplicationEventMulticaster();// 3. 不同子类有不同的的初始化,Web应用的体现就在此方法onRefresh();registerListeners();finishBeanFactoryInitialization(beanFactory);finishRefresh();}// 省略部分代码}}
}
protected void onRefresh() throws BeansException {// 4. AbstractApplicationContext没有实现此方法,实际实现要回到子类当中
}// 5. AnnotationConfigReactiveWebServerApplicationContext没有重载onRefresh()方法
//    在其父类ReactiveWebServerApplicationContext(为AbstractApplicationContext子类)重载了onRefresh()方法
// 源码位置:org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext
protected void onRefresh() {super.onRefresh();try {// 6. 创建web server对象createWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start reactive web server", ex);}
}
private void createWebServer() {WebServerManager serverManager = this.serverManager;if (serverManager == null) {StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");String webServerFactoryBeanName = getWebServerFactoryBeanName();ReactiveWebServerFactory webServerFactory = getWebServerFactory(webServerFactoryBeanName);createWebServer.tag("factory", webServerFactory.getClass().toString());boolean lazyInit = getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();// 7. 在此创建web server对象this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.serverManager.getWebServer()));getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this.serverManager));createWebServer.end();}initPropertySources();
}

3.4.2 AnnotationConfigServletWebServerApplicationContext刷新

AnnotationConfigServletWebServerApplicationContext本身并没有refresh()刷新方法,刷新方法来自于父类,整个过程和AnnotationConfigReactiveWebServerApplicationContext的创建基本相似,只有最后用来创建Web Server的工厂ServletWebServerFactory不一样,创建出来的Web Server就不一样:

// 源码位置:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
// 1. 继承关系:AnnotationConfigServletWebServerApplicationContext < ServletWebServerApplicationContext < GenericApplicationContext < AbstractApplicationContext
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {public final void refresh() throws BeansException, IllegalStateException {try {// 2. 调用父类refresh()方法刷新,调的是AbstractApplicationContext的refresh()方法super.refresh();}catch (RuntimeException ex) {WebServer webServer = this.webServer;if (webServer != null) {webServer.stop();}throw ex;}}
}// 源码位置:org.springframework.context.support.AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {            // 省略部分代码try {postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");invokeBeanFactoryPostProcessors(beanFactory);registerBeanPostProcessors(beanFactory);beanPostProcess.end();initMessageSource();initApplicationEventMulticaster();// 3. 调用子类刷新onRefresh();registerListeners();finishBeanFactoryInitialization(beanFactory);finishRefresh();}// 省略部分代码}}
}// 源码位置:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
protected void onRefresh() {super.onRefresh();try {createWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}
}
private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");ServletWebServerFactory factory = getWebServerFactory();createWebServer.tag("factory", factory.getClass().toString());// 4. 创建web server对象this.webServer = factory.getWebServer(getSelfInitializer());createWebServer.end();getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));getBeanFactory().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();
}

3.4.3 AnnotationConfigApplicationContext刷新

AnnotationConfigApplicationContext本身没有refresh()方法,需要找到父类AbstractApplicationContext,最终会调onRefresh()方法,由于这几层类都没有重载该方法,所以此onRefresh()没有做什么,跟之前两个Context比,最大的区别在于没有创建Web Server。

// 源码位置:org.springframework.context.support.AbstractApplicationContext
// 1. 继承关系:AnnotationConfigApplicationContext < GenericApplicationContext < AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 省略部分代码try {postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");invokeBeanFactoryPostProcessors(beanFactory);registerBeanPostProcessors(beanFactory);beanPostProcess.end();initMessageSource();initApplicationEventMulticaster();// 2. 调用子类的onRefresh()刷新onRefresh();registerListeners();finishBeanFactoryInitialization(beanFactory);finishRefresh();}// 省略部分代码}}
}
protected void onRefresh() throws BeansException {// 3. 由于子类GenericApplicationContext和AnnotationConfigApplicationContext都没有重载此方法,所以执行了空方法
}

3.5 执行Runner

在主流程步骤5执行runner:

// 源码位置:org.springframework.boot.SpringApplication
private void callRunners(ApplicationContext context, ApplicationArguments args) {// 1. 用context.getBeanProvider()找所有实现了Runner接口的类,并遍历这些类context.getBeanProvider(Runner.class).orderedStream().forEach((runner) -> {// 2. 支持两种Runner:ApplicationRunner、CommandLineRunner,分别都执行if (runner instanceof ApplicationRunner) {callRunner((ApplicationRunner) runner, args);}if (runner instanceof CommandLineRunner) {callRunner((CommandLineRunner) runner, args);}});
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {try {// 3. 执行Runner,传的参数是ApplicationArguments(runner).run(args);}catch (Exception ex) {throw new IllegalStateException("Failed to execute ApplicationRunner", ex);}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {try {// 4. 执行Runner,传的参数是原始数组类型(main方法的参数类型)(runner).run(args.getSourceArgs());}catch (Exception ex) {throw new IllegalStateException("Failed to execute CommandLineRunner", ex);}
}

3.6 小结

概括地看,SpringApplication的启动就是根据导入的包情况,分三种情况创建不同的Context:响应式流web、普通web、非web,然后执行Context的refresh进行初始化,下图为Context体系的继承情况,对于响应式流web、普通web这两种Context,分别在子类实现了不同的onRefresh(),用来创建不同的web server。

最后,还执行了runner(如果有实现runner的话)。注意,runner的执行与context种类无关,也就是不管哪种context都会执行。如果没有引任何web相关的包,那么就不会有web server的执行,只执行了runner,就变成了一个普通的桌面程序。runner的继承情况如下,两种Runner的差别仅在于参数的类型:

注:上面并没有把启动流程的每个细节都进行解析,这算看源码的一个小技巧,先看自己关心的部分(或者重点部分),如这次只想了解Web程序和普通程序的区别,以及SpringBoot用什么方式来区分的。

5 架构一小步

依赖spring-boot-starter-web,开启Web应用模式。

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version><relativePath/>
</parent>
<groupId>com.qqian.stepfmk</groupId>
<artifactId>srvpro</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87726.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87726.shtml
英文地址,请注明出处:http://en.pswp.cn/web/87726.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LoRaWAN的设备类型有哪几种?

LoRaWAN&#xff08;Long Range Wide Area Network&#xff09;是一种专为物联网&#xff08;IoT&#xff09;设备设计的低功耗、长距离通信协议。它根据设备的功能和功耗需求&#xff0c;将设备分为三种类型&#xff1a;Class A、Class B 和 Class C。每种设备类型都有其独特的…

三维目标检测|Iou3D 代码解读一

本文对OpenPCDet项目中的iou3d模块中的iou3d_nms_kernel.cu代码进行解读&#xff0c;本次解决的函数是box_overlap&#xff0c;它的输入是两个包围盒&#xff0c;输出是两个包围盒在bev下的重叠面积&#xff0c;计算流程是 确定box_a和box_b的四个角落坐标 从包围盒中提取坐标值…

探索实现C++ STL容器适配器:优先队列priority_queue

前引&#xff1a; 在算法竞赛中&#xff0c;选手们常常能在0.01秒内分出胜负&#xff1b;在实时交易系统中&#xff0c;毫秒级的延迟可能意味着数百万的盈亏&#xff1b;在高并发服务器中&#xff0c;每秒需要处理数万条不同优先级的请求——这些系统背后&#xff0c;都隐藏着同…

一、Dify 私有部署、本地安装教程(LInux-openeuler)

官网&#xff1a;Dify AI Plans and Pricing 1.找到下载的位置。 2.可以切换文档为中午文档。 3.本次安装使用Docker Compose 安装&#xff0c;可以大致看一下文档描述的配置信息要求。 4.各个版本信息&#xff0c;本次下载1.5.1版本&#xff0c;你也可以选择安装其他版本。 …

GASVM+PSOSVM+CNN+PSOBPNN+BPNN轴承故障诊断

一、各算法基本原理与技术特点 1. GASVM&#xff08;遗传算法优化支持向量机&#xff09; 原理&#xff1a; 利用遗传算法&#xff08;GA&#xff09;优化SVM的超参数&#xff08;如惩罚因子 C C C 和核函数参数 g g g&#xff09;。遗传算法通过模拟自然选择机制&#xff…

Python实例练习---魔法方法

&#xff08;主页有对应知识点^V^&#xff09; 【练习要求】 针对知识点Python面向对象的魔法方法安排的本实例。要求实现&#xff1a;用__init__魔法方法定义书的长&#xff0c;宽&#xff0c;高&#xff0c;最后用__str__输出返回值 【重要步骤提示】 定义class书类 2、使…

【从0-1的CSS】第3篇:盒子模型与弹性布局

文章目录 盒子模型内容区content内边距padding边框border外边距margin元素的宽度高度box-sizing属性content-box&#xff1a;设置的width和height就是内容区的width和heightborder-box:设置的width和height是context padding border的width和height 弹性布局Flex容器的属性fl…

设置LInux环境变量的方法和区别_Ubuntu/Centos

Linux环境变量可以通过export实现&#xff0c;也可以通过修改几个文件来实现 1 通过文件设置LInux环境变量 首先是设置全局环境变量&#xff0c;对所有用户都会生效 /etc/profile&#xff1a;该文件为系统的每个用户设置环境信息&#xff0c;当用户登录时&#xff0c;该文件…

python缓存装饰器实现方案

写python的时候突然想着能不能用注解于是就写了个这个 文章目录 原始版改进点 原始版 import os import pickle import hashlib import inspect import functoolsdef _generate_cache_filename(func, *args, **kwargs):"""生成缓存文件名的内部函数""…

使用 java -jar xxxx.jar 运行 jar 包报错: no main manifest attribute

1、问题描述 在Linux服务器上本想运行一下自己写的一个JAR&#xff0c;但是报错了&#xff01; no main manifest attribute, in first-real-server-1.0-SNAPSHOT.jar 2、解决办法 在自己的Spring项目的启动类&#xff08;xxx.xxx.xxx.XXXXApplication&#xff09;所在的Mo…

信号与槽的总结

信号与槽的总结 QT中的信号与Linux的信号对比 1&#xff09;信号源 2&#xff09;信号的类型 3&#xff09;信号的处理方式 QT信号与Linux信号的深度对比分析 一、信号源对比 QT信号 用户定义信号 &#xff1a;由开发者通过 signals:关键字在QObject派生类中显式声明 cl…

Python Mitmproxy详解:从入门到实战

一、Mitmproxy简介 Mitmproxy是一款开源的交互式HTTPS代理工具&#xff0c;支持拦截、修改和重放HTTP/HTTPS流量。其核心优势在于&#xff1a; 多平台支持&#xff1a;兼容Windows、macOS、Linux三端工具&#xff1a;提供命令行(mitmproxy)、Web界面(mitmweb)、数据流处理(mi…

刷题笔记--串联所有单词的子串

题目&#xff1a;1、我的写法&#xff08;超时&#xff09;从题面自然想到先用回溯算法把words的全排列先算出来&#xff0c;然后遍历字符串s一次将符合条件的位置加入结果全排列计算所有可能字符串算法写法&#xff1a;这是一个模板用于所有全排列算法的情况&#xff0c;本质思…

操作系统【1】【硬件结构】【操作系统结构】

一、CPU如何执行程序&#xff1f; 提纲 图灵机工作方式冯诺依曼模型线路位宽CPU位宽程序执行基本过程执行具体过程 1. 图灵机工作方式 图灵机可以视作“一台带规则的自动草稿机” 图灵机基本组成&#xff1a; 纸带&#xff08;内存&#xff09;&#xff1a;连续格子组成&…

SQLite与MySQL:嵌入式与客户端-服务器数据库的权衡

SQLite与MySQL&#xff1a;嵌入式与客户端-服务器数据库的权衡 在开发应用程序时&#xff0c;数据库选择是一个至关重要的决策&#xff0c;它会影响应用的性能、可扩展性、部署难度和维护成本。SQLite和MySQL是两种广泛使用的关系型数据库管理系统&#xff0c;它们各自针对不同…

CppCon 2018 学习:Smart References

“强类型别名”&#xff08;strong typedefs&#xff09; 的动机和实现&#xff0c;配合一个简单例子说明&#xff1a; 动机&#xff08;Motivation&#xff09; 用 using filename_t string; 和 using url_t string; 来区分不同的字符串类型&#xff08;比如文件名和网址&…

高性能高准确度的CPU电压与温度监测软件HWInfo

&#x1f5a5;️ 一、软件概述 Windows版&#xff1a;图形化界面&#xff0c;支持实时监控&#xff08;温度、电压、风扇转速等&#xff09;、基准测试及报告生成&#xff0c;兼容Windows XP至Windows 11系统。Linux版&#xff1a;命令行工具&#xff0c;由openSUSE社区维护&a…

H3C WA6322 AP版本升级

1、查看当前版本&#xff1a;R2444P01 2、官网下载升级文件&#xff1a; WA6300系列版本说明H3C WA6300系列(适用于WA6330、 WA6322、WA6320H、WA6320、 WTU630H、WTU630、WA6330-LI、WA6320-C、WA6320-D、WA6320H-LI、WA6338、WA6322H、WTU632H-IOT、WAP922E、WAP923、WA6320…

用 YOLOv8 + DeepSORT 实现目标检测、追踪与速度估算

【导读】 目标检测与追踪技术是计算机视觉领域最热门的应用之一&#xff0c;广泛应用于自动驾驶、交通监控、安全防护等场景。今天我们将带你一步步实现一个完整的项目&#xff0c;使用YOLOv8 DeepSORT实现目标检测、追踪与速度估算。>>更多资讯可加入CV技术群获取了解…

Python实例题:基于 Python 的简单聊天机器人

Python实例题 题目 基于 Python 的简单聊天机器人 要求&#xff1a; 使用 Python 构建一个聊天机器人&#xff0c;支持以下功能&#xff1a; 基于规则的简单问答系统关键词匹配和意图识别上下文记忆功能支持多轮对话可扩展的知识库 使用tkinter构建图形用户界面。实现至少 …