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

run方法执行过程分析

文章目录

  • run方法核心流程
  • SpringApplicationRunListener监听器
    • 监听器的配置与加载
    • SpringApplicationRunListener源码解析
    • 实现类EventPublishingRunListener
  • 初始化ApplicationArguments
  • 初始化ConfigurableEnvironment
      • 获取或创建环境
      • 配置环境
  • 打印Banner
  • Spring应用上下文的创建
  • Spring应用上下文的准备
  • Spring应用上下文的刷新
  • 调用ApplicationRunner和CommandLineRunner

run方法核心流程

在分析和学习整个run方法的源代码及操作之前,我们先通过下图所示的流程图来看一下SpringApplication调用的run方法处理的核心操作都包含哪些。

在这里插入图片描述

上面的流程图可以看出,SpringApplication在run方法中重点做了以下操作。

  • 获取监听器和参数配置。
  • 打印Banner信息。
  • 创建并初始化容器。
  • 监听器发送通知。

当然,除了核心操作,run方法运行过程中还涉及启动时长统计、异常报告、启动日志、异常处理等辅助操作。

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();this.configureHeadlessProperty();SpringApplicationRunListeners listeners = this.getRunListeners(args);listeners.starting();Collection exceptionReporters;try {// 创建 ApplicationArguments 对象ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//  加载属性配置ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);//  打印bannerBanner printedBanner = this.printBanner(environment);// 创建容器context = this.createApplicationContext();//  异常报告器exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);//  准备容器this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 初始化容器this.refreshContext(context);//  初始化容器后this.afterRefresh(context, applicationArguments);// 停止时长统计stopWatch.stop();//  打印日志if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}//  通知监听器容器启动完成listeners.started(context);this.callRunners(context, applicationArguments);} catch (Throwable var10) {//  异常处理this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}try {//  通知监听器:容器正在运行listeners.running(context);return context;} catch (Throwable var9) {// 异常处理this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}
}

SpringApplicationRunListener监听器

监听器的配置与加载

SpringApplicationRunListeners 可以理解为一个SpringApplicationRunListener的容器,它将SpringApplicationRunListener的集合以构造方法传入,并赋值给其listeners成员变量,然后提供了针对listeners 成员变量的各种遍历操作方法,比如,遍历集合并调用对应的starting、started、running等方法。

SpringApplicationRunListeners 的构建很简单,SpringApplication中getRunListeners方法代码如下:


private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class[]{SpringApplication.class, String[].class};return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListeners构造方法的第二个参数便是SpringApplicationRunListener的集合,SpringApplication中调用构造方法时该参数是通过getSpringFactoriesInstances方法获取(都是一样的套路)的,代码如下:


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {// 获取类加载器ClassLoader classLoader = this.getClassLoader();//  加载监听器,并放入set中Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 实例化监听器List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);//  排序AnnotationAwareOrderComparator.sort(instances);return instances;
}

通过方法名便可得知,getSpringFactoriesInstances是用来获取factories配置文件中的注册类,并进行实例化操作。

关于通过SpringFactoriesLoader获取META-INF/spring.factories中对应的配置,构造方法章节已经多次提到,这里不再赞述。SpringApplicationRunListener的注册配置位于spring-boot项目中的spring.factories文件内,SpringBoot默认仅有一个监听器进行了注册,关于其功能后面会专门讲到。


private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {List<T> instances = new ArrayList(names.size());Iterator var7 = names.iterator();while(var7.hasNext()) {String name = (String)var7.next();try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);T instance = BeanUtils.instantiateClass(constructor, args);instances.add(instance);} catch (Throwable var12) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);}}return instances;
}

在上面的代码中,实例化监听器时需要有一个默认的构造方法,且构造方法的参数为Class<?>[]parameterTypes。我们向上追踪该参数的来源,会发现该参数的值为Class数组,数组的内容依次为SpringApplication.class和String[].class。也就是说,SpringApplicationRunListener的实现类必须有默认的构造方法,且构造方法的参数必须依次为SpringApplication和String[]类型。

SpringApplicationRunListener源码解析

接口SpringApplicationRunListener是SpringApplication的run方法监听器。上节提到了 SpringApplicationRunListener通过SpringFactoriesLoader加载,并且必须声明一个公共构造函数,该函数接收SpringApplication实例和String[]的参数,而且每次运行都会创建一个新的实例。

SpringApplicationRunListener提供了一系列的方法,用户可以通过回调这些方法,在启动各个流程时加入指定的逻辑处理。


class SpringApplicationRunListeners {private final Log log;private final List<SpringApplicationRunListener> listeners;SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {this.log = log;this.listeners = new ArrayList(listeners);}public void starting() {Iterator var1 = this.listeners.iterator();while(var1.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var1.next();listener.starting();}}public void environmentPrepared(ConfigurableEnvironment environment) {Iterator var2 = this.listeners.iterator();while(var2.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();listener.environmentPrepared(environment);}}public void contextPrepared(ConfigurableApplicationContext context) {Iterator var2 = this.listeners.iterator();while(var2.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();listener.contextPrepared(context);}}public void contextLoaded(ConfigurableApplicationContext context) {Iterator var2 = this.listeners.iterator();while(var2.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();listener.contextLoaded(context);}}public void started(ConfigurableApplicationContext context) {Iterator var2 = this.listeners.iterator();while(var2.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();listener.started(context);}}public void running(ConfigurableApplicationContext context) {Iterator var2 = this.listeners.iterator();while(var2.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();listener.running(context);}}public void failed(ConfigurableApplicationContext context, Throwable exception) {Iterator var3 = this.listeners.iterator();while(var3.hasNext()) {SpringApplicationRunListener listener = (SpringApplicationRunListener)var3.next();this.callFailedListener(listener, context, exception);}}private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context, Throwable exception) {try {listener.failed(context, exception);} catch (Throwable var6) {if (exception == null) {ReflectionUtils.rethrowRuntimeException(var6);}if (this.log.isDebugEnabled()) {this.log.error("Error handling failed", var6);} else {String message = var6.getMessage();message = message != null ? message : "no error message";this.log.warn("Error handling failed (" + message + ")");}}}
}

我们通过源代码可以看出,SpringApplicationRunListener为run方法提供了各个运行阶段的监听事件处理功能。

下图展示了在整个run方法的生命周期中SpringApplicationRunListener的所有方法所处的位置,该图可以帮助我们更好地学习run方法的运行流程。在前面run方法的代码中已经看到相关监听方法被调用,后续的源代码中也将涉及对应方法的调用,我们可参考此图以便理解和加深记忆。

在这里插入图片描述

实现类EventPublishingRunListener

EventPublishingRunListener是SpringBoot中针对SpringApplicationRunListener接口的唯一内建实现。EventPublishingRunListener使用内置的SimpleApplicationEventMulticaster来广播在上下文刷新之前触发的事件。

默认情况下,SpringBoot在初始化过程中触发的事件也是交由EventPublishingRunListener来代理实现的。

SpringBoot完成基本的初始化之后,会遍历SpringApplication的所有ApplicationListener实例,并将它们与SimpleApplicationEventMulticaster进行关联,方便SimpleApplicationEventMulticaster后续将事件传递给所有的监听器。

EventPublishingRunListener针对不同的事件提供了不同的处理方法,但它们的处理流程基本相同。


public void contextLoaded(ConfigurableApplicationContext context) {ApplicationListener listener;for(Iterator var2 = this.application.getListeners().iterator(); var2.hasNext(); context.addApplicationListener(listener)) {listener = (ApplicationListener)var2.next();if (listener instanceof ApplicationContextAware) {((ApplicationContextAware)listener).setApplicationContext(context);}}this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

contextLoaded方法在发布事件之前做了两件事:第一,遍历application的所有监听器实现类,如果该实现类还实现了ApplicationContextAware接口,则将上下文信息设置到该监听器内;第二,将application中的监听器实现类全部添加到上下文中。最后一步才是调用事件广播。

也正是这个方法形成了不同事件广播形式的分水岭,在此方法之前执行的事件广播都是通过multicastEvent来进行的,而该方法之后的方法则均采用publishEvent来执行。这是因为只有到了contextLoaded方法之后,上下文才算初始化完成,才可通过它的publishEvent方法来进行事件的发布。

初始化ApplicationArguments

监听器启动之后,紧接着便是执行ApplicationArguments对象的初始化,ApplicationArguments是用于提供访问运行SpringApplication时的参数。

ApplicationArguments的初始化过程非常简单,只是调用了它的实现类DefaultApplicationArguments并传入main方法中的args参数。

在DefaultApplicationArguments中将参数args封装为Source对象,Source对象是基于Spring框架的SimpleCommandLinePropertySource来实现的。

初始化ConfigurableEnvironment

完成ApplicationArguments参数的准备之后,便开始通过prepareEnvironment方法对ConfigurableEnvironment对象进行初始化操作。

ConfigurableEnvironment接口继承自Environment接口和ConfigurablePropertyResolver,最终都继承自接口PropertyResolverConfigurableEnvironment接口的主要作用是提供当前运行环境的公开接口,比如配置文件profiles各类系统属性和变量的设置、添加、读取、合并等功能。

通过ConfigurableEnvironment接口中方法定义,可以更清楚地了解它的功能,代码如下:


public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {void setActiveProfiles(String... var1);void addActiveProfile(String var1);void setDefaultProfiles(String... var1);MutablePropertySources getPropertySources();Map<String, Object> getSystemProperties();Map<String, Object> getSystemEnvironment();void merge(ConfigurableEnvironment var1);
}

通过接口提供的方法,我们可以看出ConfigurableEnvironment就是围绕着这个“环境”来提供相应的功能,这也是为什么我们也将它称作“环境”。

了解了ConfigurableEnvironment的功能及方法,我们回归到SpringApplication的流程看相关源代码。run方法中调用prepareEnvironment方法相关代码如下:


private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {ConfigurableEnvironment environment = this.getOrCreateEnvironment();this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());listeners.environmentPrepared((ConfigurableEnvironment)environment);this.bindToSpringApplication((ConfigurableEnvironment)environment);if (!this.isCustomEnvironment) {environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());}ConfigurationPropertySources.attach((Environment)environment);return (ConfigurableEnvironment)environment;
}

通过以上代码可知,prepareEnvironment进行了以下的操作:

  • 获取或创建环境。
  • 配置环境。
  • ConfigurationPropertySources附加到指定环境:将ConfigurationPropertySources附加到指定环境中的第一位,并动态跟踪环境的添加或删除(当前版本新增了该行代码,与最后一步操作相同)。
  • 设置listener监听事件:前面章节已经讲过,此处主要针对准备环境的监听。
  • 绑定环境到SpringApplication:将环境绑定到name为“spring.main”的目标上。
  • 转换环境:判断是否是定制的环境,如果不是定制的,则将环境转换为StandardEnvironment。此时判断条件isCustomEnvironment默认为false,在后面的操作中会将其设置为true,如果为true则不再会进行此转换操作。
  • ConfigurationPropertySources附加到指定环境:将ConfigurationPropertySources附加到指定环境中的第一位,并动态跟踪环境的添加或删除操作。

获取或创建环境

SpringApplication类中通过getOrCreateEnvironment方法来获取或创建环境。在该方法中首先判断环境是否为null,如果不为null则直接返回;如果为null,则根据前面推断出来的WebApplicationType类型来创建指定的环境。

配置环境

在获得环境变量对象之后,开始对环境变量和参数进行相应的设置,主要包括转换服务的设置、PropertySources的设置和activeProfiles的设置。

打印Banner

完成环境的基本处理之后,下面就是控制台Banner的打印了。SpringBoot的Banner打印是一个比较酷炫的功能,但又显得有些华而不实,特别是打印图片时启动速度会变慢。

private Banner printBanner(ConfigurableEnvironment environment) {if (this.bannerMode == Mode.OFF) {return null;} else {ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);}
}

上面的代码中展示了Banner的开启及打印位置的设置。程序通过Banner.Mode枚举值来判断是否开启Banner打印,此项参数可以在SpringBoot入口nmain方法中通过setBannerMode方法来设置,也可以通过application.properties中的spring.main.banner-mode进行设置。

SpringApplicationBannerPrinter类承载了Banner初始化及打印的核心功能,比如默认如何获取Banner信息、如何根据约定优于配置来默认获得Banner的内容、Banner支持的文件格式等。

而具体打印的信息是由Banner接口的实现类来完成的,比如默认情况下使用SpringBootBanner来打印SpringBoot的版本信息及简单的图形。当然还有通过资源文件打印的ResourceBanner,通过图片打印的ImageBanner等方法。

Spring应用上下文的创建

SpringBoot创建Spring的应用上下文时,如果未指定要创建的类,则会根据之前推断出的类型来进行默认上下文类的创建。

在SpringBoot中通过SpringApplication类中的createApplicationContext来进行应用上下文的创建,代码如下:


protected ConfigurableApplicationContext createApplicationContext() {//  获取容器的类变量Class<?> contextClass = this.applicationContextClass;//  如果为null,则根据Web应用类型按照默认类进行创建if (contextClass == null) {try {switch(this.webApplicationType) {case SERVLET:contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");break;case REACTIVE:contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");break;default:contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");}} catch (ClassNotFoundException var3) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);}}return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

Spring应用上下文的准备

我们在上一节完成了应用上下文的创建工作,SpringApplication继续通过prepareContext方法来进行应用上下文的准备工作。


private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 设置应用上下文环境context.setEnvironment(environment);// 设置应用上下文后置处理this.postProcessApplicationContext(context);// 初始化contextthis.applyInitializers(context);// 通知监听器上下文准备阶段listeners.contextPrepared(context);// 打印 profileif (this.logStartupInfo) {this.logStartupInfo(context.getParent() == null);this.logStartupProfileInfo(context);}//  获取 ConfigurableListableBeanFactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}//  获取全部配置源Set<Object> sources = this.getAllSources();Assert.notEmpty(sources, "Sources must not be empty");//  将获取到的配置源加载到context中this.load(context, sources.toArray(new Object[0]));//  通知监听器 context 加载完成listeners.contextLoaded(context);
}

Spring应用上下文的刷新

Spring应用上下文的刷新,是通过调用SpringApplication中的refreshContext方法来完成的。SpringApplication中refreshContext方法相关代码如下:


private void refreshContext(ConfigurableApplicationContext context) {this.refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();} catch (AccessControlException var3) {}}}protected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);((AbstractApplicationContext)applicationContext).refresh();
}

其中refresh方法调用的是AbstractApplicationContext中的refresh方法,该类属于spring-context包。AbstractApplicationContext的refresh方法更多的是Spring相关的内容。


public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {this.prepareRefresh();ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);this.initMessageSource();this.initApplicationEventMulticaster();this.onRefresh();this.registerListeners();this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (BeansException var9) {if (this.logger.isWarnEnabled()) {this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);}this.destroyBeans();this.cancelRefresh(var9);throw var9;} finally {this.resetCommonCaches();}}
}

在上面的代码中,调用finishRefresh方法初始化容器的生命周期处理器并发布容器的生命周期事件之后,Spring应用上下文正式开启,SpringBoot核心特性也随之启动。完成refreshContext方法操作之后,调用afterRefresh方法。

完成以上操作之后,调用SpringApplicationRunListeners的started方法,通知监听器容器启动完成,并调用ApplicationRunner和CommandLineRunner的运行方法。

调用ApplicationRunner和CommandLineRunner

ApplicationRunner和CommandLineRunner是通过 SpringApplication类的callRunners方法来完成的,具体代码如下:


private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList();runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);Iterator var4 = (new LinkedHashSet(runners)).iterator();while(var4.hasNext()) {Object runner = var4.next();if (runner instanceof ApplicationRunner) {this.callRunner((ApplicationRunner)runner, args);}if (runner instanceof CommandLineRunner) {this.callRunner((CommandLineRunner)runner, args);}}}

以上代码,首先从context中获得类型为ApplicationRunner和CommandLineRunner的Bean,将它们放入List列表中并进行排序。然后再遍历调用其run方法,并将ApplicationArguments参数传入。

SpringBoot提供这两个接口的目的,是为了我们在开发的过程中,通过它们来实现在容器启动时执行一些操作,如果有多个实现类,可通过@Order注解或实现Ordered接口来控制执行顺序。

这两个接口都提供了一个run方法,但不同之处在于:ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组。

以上方法执行完成后,会通过SpringApplicationRunListeners的running方法通知监听器:容器此刻已处于运行状态。至此,SpringApplication的run方法执行完毕。

相关文章:

run方法执行过程分析

文章目录 run方法核心流程SpringApplicationRunListener监听器监听器的配置与加载SpringApplicationRunListener源码解析实现类EventPublishingRunListener 初始化ApplicationArguments初始化ConfigurableEnvironment获取或创建环境配置环境 打印BannerSpring应用上下文的创建S…...

关联封号率降70%!2025最新IP隔离方案实操手册

高效运营安全防护&#xff0c;跨境卖家必看的风险规避指南 跨境账号管理的核心挑战&#xff1a;关联封号风险激增 2024年&#xff0c;随着全球电商平台对账号合规的审查日益严苛&#xff0c;“关联封号”已成为跨境卖家最头疼的问题之一。无论是同一IP登录多账号、员工操作失误…...

LeetCode 解题思路 10(Hot 100)

解题思路&#xff1a; 上边&#xff1a; 从左到右遍历顶行&#xff0c;完成后上边界下移&#xff08;top&#xff09;。右边&#xff1a; 从上到下遍历右列&#xff0c;完成后右边界左移&#xff08;right–&#xff09;。下边&#xff1a; 从右到左遍历底行&#xff0c;完成后…...

ASP.NET Core JWT认证与授权

1.JWT结构 JSON Web Token&#xff08;JWT&#xff09;是一种用于在网络应用之间安全传输声明的开放标准&#xff08;RFC 7519&#xff09;。它通常由三部分组成&#xff0c;以紧凑的字符串形式表示&#xff0c;在身份验证、信息交换等场景中广泛应用。 2.JWT权限认证 2.1添…...

城市地质安全专题连载⑧ | 强化工程地质安全保障力度,为工程项目全栈护航

作者 | 徐海洋、孙美琴 在城市化进程日益加速的今天&#xff0c;城市地质安全问题日益凸显&#xff0c;成为制约城市可持续发展的关键因素之一。从隧道掘进中的突发灾害&#xff0c;到高层建筑地基的稳定性挑战&#xff0c;再到城市地下空间的开发利用风险&#xff0c;地质安全…...

50.xilinx fir滤波器系数重加载如何控制

&#xff0c; 注意:matlab量化后的滤波器系数为有符号数&#xff0c;它是以补码形式存储的&#xff0c;手动计算验证时注意转换为负数对应数值进行计算。...

低代码平台的后端架构设计与核心技术解析

引言&#xff1a;低代码如何颠覆传统后端开发&#xff1f; 在传统开发模式下&#xff0c;一个简单用户管理系统的后端开发需要&#xff1a; 3天数据库设计5天REST API开发2天权限模块对接50个易出错的代码文件 而现代低代码平台通过可视化建模自动化生成&#xff0c;可将开发…...

QT实现单个控制点在曲线上的贝塞尔曲线

最终效果: 一共三个文件 main.cpp #include <QApplication> #include "SplineBoard.h" int main(int argc,char** argv) {QApplication a(argc, argv);SplineBoard b;b.setWindowTitle("标准的贝塞尔曲线");b.show();SplineBoard b2(0.0001);b2.sh…...

svn 通过127.0.01能访问 但通过公网IP不能访问,这是什么原因?

连接失败的提示如下 1、SVN的启动方法 方法一&#xff1a; svnserve -d -r /mnt/svn 方法二&#xff1a; svnserve -d --listen-port 3690 -r /mnt/svn 方法三&#xff1a; svnserve -d -r /mnt/svn --listen-host 0.0.0.0 2、首先检查svn服务器是否启动 方法一&#x…...

‌学习DeepSeek V3 与 R1 核心区别(按功能维度分类)

‌一、定位与架构‌ ‌V3&#xff08;通用型模型&#xff09;‌ 定位&#xff1a;多模态通用大模型&#xff0c;擅长文本生成、多语言翻译、智能客服等多样化任务‌12。架构&#xff1a;混合专家&#xff08;MoE&#xff09;架构&#xff0c;总参数 ‌6710 亿‌&#xff0c;每次…...

C++中的 互斥量

1.概念&#xff1a; 为什么&#xff1a;线程的异步性&#xff0c;不是按照时间来的&#xff01;&#xff01;&#xff01; C并发以及多线程的秘密-CSDN博客 目的 多线程编程中&#xff0c;当多个线程可能同时访问和修改共享资源时&#xff0c;会导致数据不一致或程序错误。…...

直接法估计相机位姿

引入 在前面的文章&#xff1a;运动跟踪——Lucas-Kanade光流中&#xff0c;我们了解到特征点法存在一些缺陷&#xff0c;并且用光流法追踪像素点的运动来替代特征点法进行特征点匹配的过程来解决这些缺陷。而这篇文章要介绍的直接法则是通过计算特征点在下一时刻图像中的位置…...

PHP动态网站建设

如何配置虚拟主机 1. 学习提纲 本地发布与互联网发布&#xff1a;介绍了如何通过本地IP地址和互联网域名发布网站。 虚拟主机配置与访问&#xff1a;讲解了如何配置虚拟主机&#xff0c;并通过自定义域名访问不同的站点目录。 Web服务器配置&#xff1a;详细说明了如何配置A…...

【gRPC】Java高性能远程调用之gRPC详解

gRPC详解 一、什么是gRPC&#xff1f;二、用proto生成代码2.1、前期准备2.2、protobuf插件安装 三、简单 RPC3.1、开发gRPC服务端3.2、开发gRPC客户端3.3、验证gRPC服务 四、服务器端流式 RPC4.1、开发一个gRPC服务&#xff0c;类型是服务端流4.2、开发一个客户端&#xff0c;调…...

数据结构知识学习小结

一、动态内存分配基本步骤 1、内存分配简单示例&#xff1a; 个人对于示例的理解&#xff1a; 定义一个整型的指针变量p&#xff08;着重认为它是一个“变量”我觉得可能会更好理解&#xff09;&#xff0c;这个变量用来存地址的&#xff0c;而不是“值”&#xff0c;malloc函…...

分布式锁—2.Redisson的可重入锁一

大纲 1.Redisson可重入锁RedissonLock概述 2.可重入锁源码之创建RedissonClient实例 3.可重入锁源码之lua脚本加锁逻辑 4.可重入锁源码之WatchDog维持加锁逻辑 5.可重入锁源码之可重入加锁逻辑 6.可重入锁源码之锁的互斥阻塞逻辑 7.可重入锁源码之释放锁逻辑 8.可重入锁…...

计算机毕业设计SpringBoot+Vue.js球队训练信息管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

FFmpeg入门:最简单的音视频播放器

FFmpeg入门&#xff1a;最简单的音视频播放器 前两章&#xff0c;我们已经了解了分别如何构建一个简单和音频播放器和视频播放器。 FFmpeg入门&#xff1a;最简单的音频播放器 FFmpeg入门&#xff1a;最简单的视频播放器 本章我们将结合上述两章的知识&#xff0c;看看如何融…...

java 查找两个集合的交集部分数据

利用了Java 8的Stream API&#xff0c;代码简洁且效率高 import java.util.stream.Collectors; import java.util.List; import java.util.HashSet; import java.util.Set;public class ListIntersection {public static List<Long> findIntersection(List<Long> …...

【系统架构设计师】以数据为中心的体系结构风格

目录 1. 说明2. 仓库体系结构风格3. 黑板体系结构风格 1. 说明 1.以数据为中心的体系结构风格主要包括仓库体系结构风格和黑板体系结构风格。 2. 仓库体系结构风格 1.仓库&#xff08;Repository&#xff09;是存储和维护数据的中心场所。2.在仓库风格中&#xff0c;有两种不…...

通过HTML有序列表(ol/li)实现自动递增编号的完整解决方案

以下是通过HTML有序列表(ol/li)实现自动递增编号的完整解决方案&#xff1a; <!DOCTYPE html> <html> <head> <style> /* 基础样式 */ ol {margin: 1em 0;padding-left: 2em; }/* 方案1&#xff1a;默认数字编号 */ ol.default {list-style-type: dec…...

【Python 数据结构 4.单向链表】

目录 一、单向链表的基本概念 1.单向链表的概念 2.单向链表的元素插入 元素插入的步骤 3.单向链表的元素删除 元素删除的步骤 4.单向链表的元素查找 元素查找的步骤 5.单向链表的元素索引 元素索引的步骤 6.单向链表的元素修改 元素修改的步骤 二、Python中的单向链表 ​编辑 三…...

基于 vLLM 部署 LSTM 时序预测模型的“下饭”(智能告警预测与根因分析部署)指南

Alright,各位看官老爷们,准备好迎接史上最爆笑、最通俗易懂的 “基于 vLLM 部署 LSTM 时序预测模型的智能告警预测与根因分析部署指南” 吗? 保证让你笑出猪叫,看完直接变身技术大咖!🚀😂 咱们今天的主题,就像是要打造一个“智能运维小管家”! 这个小管家,不仅能提…...

Java多线程与高并发专题——ConcurrentHashMap 在 Java7 和 8 有何不同?

引入 上一篇我们提到HashMap 是线程不安全的&#xff0c;并推荐使用线程安全同时性能比较好的 ConcurrentHashMap。 而在 Java 8 中&#xff0c;对于 ConcurrentHashMap 这个常用的工具类进行了很大的升级&#xff0c;对比之前 Java 7 版本在诸多方面都进行了调整和变化。不过…...

NL2SQL-基于Dify+阿里通义千问大模型,实现自然语音自动生产SQL语句

本文基于Dify阿里通义千问大模型&#xff0c;实现自然语音自动生产SQL语句功能&#xff0c;话不多说直接上效果图 我们可以试着问他几个问题 查询每个部门的员工数量SELECT d.dept_name, COUNT(e.emp_no) AS employee_count FROM employees e JOIN dept_emp de ON e.emp_no d…...

LeetCode 1328.破坏回文串:贪心

【LetMeFly】1328.破坏回文串&#xff1a;贪心 力扣题目链接&#xff1a;https://leetcode.cn/problems/break-a-palindrome/ 给你一个由小写英文字母组成的回文字符串 palindrome &#xff0c;请你将其中 一个 字符用任意小写英文字母替换&#xff0c;使得结果字符串的 字典…...

计算机视觉|ViT详解:打破视觉与语言界限

一、ViT 的诞生背景 在计算机视觉领域的发展中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一直占据重要地位。自 2012 年 AlexNet 在 ImageNet 大赛中取得优异成绩后&#xff0c;CNN 在图像分类任务中显示出强大能力。随后&#xff0c;VGG、ResNet 等深度网络架构不…...

//定义一个方法,把int数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,并在控制台输出结果

import java.util.Scanner; public class cha{ public static void main(String[] args){//定义一个方法&#xff0c;把int数组中的数据按照指定的格式拼接成一个字符串返回&#xff0c;调用该方法&#xff0c;并在控制台输出结果//eg&#xff1a; 数组为&#xff1a;int[] arr…...

Python快捷手册

Python快捷手册 后续会陆续更新Python对应的依赖或者工具使用方法 文章目录 Python快捷手册[toc]1-依赖1-词云小工具2-图片添加文字3-BeautifulSoup网络爬虫4-Tkinter界面绘制5-PDF转Word 2-开发1-多线程和队列 3-运维1-Requirement依赖2-波尔实验室3-Anaconda3使用教程4-CentO…...

QT5 GPU使用

一、问题1 1、现象 2、原因分析 出现上图错误&#xff0c;无法创建EGL表面&#xff0c;错误&#xff1d;0x300b。申请不上native window有可能是缺少libqeglfs-mali-integration.so 这个库 3、解决方法 需要将其adb push 到小机端的/usr/lib/qt5/plugins/egldeviceintegrat…...