深入了解Springboot框架的启动流程

news2025/5/23 19:51:21

目录

1、介绍

2、执行流程

1、运行run方法

2、初始化SpringApplication对象

1、确定容器类型

3、加载所有的初始化器

4、加载Spring上下文监听器

5、设置程序运行的主类

3、进入run方法

1、开启计时器

2、Headless模式配置

3、获取并启用监听器

4、准备环境

1、设置应用程序参数

2、准备环境变量

3、忽略bean信息

4、打印 banner 信息

5、创建上下文

1、应用程序类型的上下文

2、实例化异常报告器

6、准备上下文环境

1、实例化单例的beanName生成器

2、执行初始化方法

3、将启动参数注册到容器中

7、刷新上下文

1、自动装配和容器启动

2、刷新上下文后置处理

8、结束计时器

9、发布上下文

1、准备就绪事件

2、执行自定义的run方法


1、框架介绍

更多了解Springboot的自动装配可参考:谈谈对Springboot框架的理解-CSDN博客

1、约定大于配置

        Springboot是依赖于spring的,除了拥有spring的全部功能以外,Springboot无需繁琐的xml配置(约定大于配置),有application.properties/yml配置文件,还有static静态配置目录,这取决于它自身强大的自动装配功能;

2、开箱即用

        并且自身已嵌入Tomcat、Jetty等web容器,集成了springmvc,使得springboot可以直接运行,不需要额外的容器。

         关于springboot的jar包可以启动,为什么普通classes/lib/jar包不能运行的,可参考:Springboot项目jar包启动原理及类型_spring jar包-CSDN博客

3、便于集成

        依赖于强大的@EnableAutoConfiguration注解,通过SpringFactoriesLoader机制(SPI机制)去读取META-INF/spring.factories文件。里面存放了key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的实现。有mq、redis、servlet、jdbc、log4j等实现类。在自动装配的过程中通过@ConditionalOnClass等注解来进行过滤。

4、指标检测actuator

        提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等。

5、简化了maven配置

通过spring-boot-starter-parent这个基础pom,里面封装了常用的依赖。


2、执行流程

如下图为简化版的所示:

1、运行run方法

通过源码可参考:

package *;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
 
@SpringBootApplication
public class App  {
 
    public static void main(String[] args) {
        // 启动springboot
        ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
    }
 
}

        通过run方法,会 new 一个SpringApplication 对象,创建这个对象的构造函数做了一些准备工作。

如下所示:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
}

        在这里可以看到返回值为ConfigurableApplicationContext(extends ApplicationContext, Lifecycle, Closeable)。

这个ApplicationContext可以参考:Spring的核心模块原理介绍_spring 原理-CSDN博客

1、primarySource

        而primarySource,在 Spring Boot 启动时,指定应用的主配置类,(通常是带有 @SpringBootApplication 的类),也可以去用于 自定义启动逻辑 或 模块化项目 中。

如下所示:

public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication();
        // 设置主配置类
        app.setPrimarySources(Collections.singletonList(MyApplication.class));
        app.run(args);
    }
}

2、@Primary

是spring的注解。多数据源配置中的 primary 属性。

在配置数据源时,@Primary 注解常用于标记主数据源。

例如:

@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

因为两者比较相似,因此可以参考区别:

2、初始化SpringApplication对象

以下就是在new SpringApplication对象时进行的一系列操作。

1、确定容器类型

        在构造方法内,首先会通过 WebApplicationType.deduceFromClasspath(); 方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式编程)。

如下图所示:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //加载应用程序启动的容器的类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
}


static WebApplicationType deduceFromClasspath() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
}

这块关于容器的类型区别为:

3、加载所有的初始化器

如下所示:

        在设置初始化器的时候,传参为SpringFactoriesLoader.loadFactoryNames()。此处就是之前实现自动装配过程中读取/META-INF/spring.factories文件的配置类。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

        不过此时的class<T> type传入的类型为ApplicationContextInitializer.class,因此需要去spring.factories里面找到key为org.springframework.context.ApplicationContextInitializer的属性。

如下所示:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

1、自定义的初始化器

        只需要实现 ApplicationContextInitializer接口既可MyApplicationContextInitializer.java。

如下:

package com.spring.application;
 
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/**
 * 自定义的初始化器
 */
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("我是初始化的 MyApplicationContextInitializer...");
    }
}

在工程的resources目录下添加 META-INF/spring.factories 配置文件。

内容如下,将自定义的初始化器注册进去:

org.springframework.context.ApplicationContextInitializer=\
com.spring.application.MyApplicationContextInitializer
     启动服务后,就可以在控制台看到打印的信息,在这里我们可以很直观的看到它的执行顺序,是在打印banner的后面执行的;

4、加载Spring上下文监听器

作用:监听 Spring 应用上下文的 通用事件(如应用启动完成、关闭等)。

        回到上面SpringApplication的构造函数里面来,完成初始化器后,进行监听器的加载,同样的使用spi机制。    

在spring.factories文件里面找到ApplicationListener类的实现。

⚠️注意:

        在springboot项目里面,可以发现关于SpringFactoriesLoader.LoadFactoryName这个机制的地方较多,因此可以重点掌握下。

举例:

public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("应用上下文刷新完成");
    }
}

5、设置程序运行的主类

继续回到刚才构造函数里面:

        deduceMainApplicationClass(); 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备。在这里可以看到一个getStackTrace()方法。

如下:

  public StackTraceElement[] getStackTrace() {
        return getOurStackTrace().clone();
  }

    private synchronized StackTraceElement[] getOurStackTrace() {
        // Initialize stack trace field with information from
        // backtrace if this is the first call to this method
        if (stackTrace == UNASSIGNED_STACK ||
            (stackTrace == null && backtrace != null) /* Out of protocol state */)                     {
            int depth = getStackTraceDepth();
            stackTrace = new StackTraceElement[depth];
            for (int i=0; i < depth; i++)
                stackTrace[i] = getStackTraceElement(i);
        } else if (stackTrace == null) {
            return UNASSIGNED_STACK;
        }
        return stackTrace;
    }
  • 如果开发者显式调用 SpringApplication.run(MyApplication.class, args),Spring Boot 可以直接使用传入的 MyApplication.class 作为主类。
  • 但如果开发者省略了显式传参(例如在 IDE 中直接运行),Spring Boot 需要自动找到主类。此时,getStackTrace() 成为一种可靠的手段。

下面就是getStackTrace方法的作用:

到达这一步的话,关于new SpringApplication对象就完成了。

3、进入run方法

接下来就是调用run方法。

1、开启计时器

计算Springboot启动需要花费多长时间。

2、Headless模式配置

当开启计时器后,下一步会看到 configureHeadlessProperty()方法,通过进一步查看:

private boolean headless = true;

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";


private void configureHeadlessProperty() {
		System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
				System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}


        是一个用于 配置图形环境(Headless 模式) 的方法。它的核心作用是确保 Java 应用程序在 无图形界面(Headless)环境(如服务器、命令行工具等)中运行时,能够正确处理与图形界面相关的操作(如 Swing、AWT 等),避免因缺少图形环境导致的异常。

具体细节如下所示:

3、获取并启用监听器

定义:用于监听 Spring Boot 应用的启动过程中的关键事件,启动阶段专用监听器集合。

作用:需要在 Spring Boot 启动的 特定阶段 插入逻辑(如初始化日志、记录启动耗时等)。

如下图所示:

这一步 通过监听器来实现初始化的的基本操作。

这一步做了2件事情

1、创建所有 SpringBoot 运行监听器并发布应用启动事件

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

2、启用监听器  

      这一步的监听器和之前的new SpringApplication对象的时候setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 有什么区别?

如下图所示:

举例:

public class MySpringApplicationRunListener implements SpringApplicationRunListener {

    public MySpringApplicationRunListener(SpringApplication application, String[] args) {
        // 构造函数
    }

    @Override
    public void starting() {
        System.out.println("Spring Boot 启动开始");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        System.out.println("环境对象准备完成");
    }

    // 其他方法...
}

4、准备环境

1、设置应用程序参数

将执行run方法时传入的参数封装成一个对象。

2、准备环境变量

包含系统属性和用户配置的属性,执行的代码块在 prepareEnvironment 方法内。

并且将Springboot的监听器和applicationArgs对象传入。

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

打了断点之后可以看到,它将maven和系统的环境变量都加载进来了。

3、忽略bean信息

//忽略bean信息
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
		if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
			Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
			System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
		}
	}

        这个方法configureIgnoreBeanInfo() 是将 spring.beaninfo.ignore 的默认值值设为true,意思是跳过beanInfo的搜索,其设置默认值的原理和headless模式一样;

也可以在配置文件中添加以下配置来设为false

spring.beaninfo.ignore=false
4、打印 banner 信息

继续跟随代码往下看:

显而易见,这个流程就是用来打印控制台那个很大的spring的banner的.

在 SpringBootBanner.java 里面打印的,这个类实现了Banner 接口,

而且banner信息是直接在代码里面固定的;

1、自定义banner

需要在resources目录下添加一个 banner.txt 的文件即可,txt文件内容如下:

                 _           _
                (_)         | |
 _   _  _____  ___ _ __   __| | ___  _ __   __ _
| | | |/ _ \ \/ / | '_ \ / _` |/ _ \| '_ \ / _` |
| |_| |  __/>  <| | | | | (_| | (_) | | | | (_| |
 \__, |\___/_/\_\_|_| |_|\__,_|\___/|_| |_|\__, |
  __/ |                                     __/ |
 |___/                                     |___/
:: spring::

只需要加一个文件即可,其他什么都不用做,然后直接启动springboot,就可以看到效果了。

5、创建上下文

1、应用程序类型的上下文

继续追随代码:

可以看到createApplicationContext的实现:

protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
2、实例化异常报告器

        异常报告器是用来捕捉全局异常使用的,SpringBootExceptionReporter.class。

当springboot应用程序在发生异常时,异常报告器会将其捕捉并做相应处理,在spring.factories 文件里配置了默认的异常报告器。

        这个异常报告器只会捕获启动过程抛出的异常,如果是在启动完成后,在用户请求时报错,异常报告器不会捕获请求中出现的异常。

1、自定义异常报告器

MyExceptionReporter.java 继承 SpringBootExceptionReporter 接口

package com.spring.application;
 
import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;
 
public class MyExceptionReporter implements SpringBootExceptionReporter {
 
 
    private ConfigurableApplicationContext context;
    // 必须要有一个有参的构造函数,否则启动会报错
    MyExceptionReporter(ConfigurableApplicationContext context) {
        this.context = context;
    }
 
    @Override
    public boolean reportException(Throwable failure) {
        System.out.println("进入异常报告器");
        failure.printStackTrace();
        // 返回false会打印详细springboot错误信息,返回true则只打印异常信息 
        return false;
    }
}

在 spring.factories 文件中注册异常报告器

# Error Reporters 异常报告器
org.springframework.boot.SpringBootExceptionReporter=\
com.spring.application.MyExceptionReporter

接着我们在application.yml 中 把端口号设置为一个很大的值,这样肯定会报错,

server:
  port: 80828888

6、准备上下文环境

这里准备的上下文环境是为了下一步刷新做准备的,里面还做了一些额外的事情;
 

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
        //1、实例化单例beanName的生成器
		postProcessApplicationContext(context);
        //2、执行初始化方法
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //3、将启动参数注入到容器中
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}
1、实例化单例的beanName生成器

        在 postProcessApplicationContext(context); 方法里面。使用单例模式创建 了BeanNameGenerator 对象,其实就是beanName生成器,用来生成bean对象的名称。

如下图所示:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
		if (this.addConversionService) {
			context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
		}
	}
2、执行初始化方法

       拿到加载出来的所有初始化器(实现了ApplicationContextInitializer 接口的类),在这里回进行统一的解析。

protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
					ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}
3、将启动参数注册到容器中

        将启动参数以单例的模式注册到容器中,参数的beanName 为 :springApplicationArguments。

ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

7、刷新上下文

1、自动装配和容器启动

        刷新上下文已经是spring的范畴了,自动装配和启动 tomcat就是在这个方法里面完成的。

2、刷新上下文后置处理

afterRefresh 方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的,

代码示例:

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
	}

8、结束计时器

到这一步,springboot其实就已经完成了,计时器会打印启动springboot的时长。

在控制台看到启动还是挺快的,不到2秒就启动完成了;
 

9、发布上下文

1、准备就绪事件

告诉应用程序,已经准备好了,可以开始工作了。

2、执行自定义的run方法

        这是一个扩展功能,callRunners(context, applicationArguments) 可以在启动完成后执行自定义的run方法;有2中方式可以实现:

  1. 实现 ApplicationRunner 接口
  2. 实现 CommandLineRunner 接口
package com.spring.init;
 
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
 
/**
 * 自定义run方法的2种方式
 */
@Component
public class MyRunner implements ApplicationRunner, CommandLineRunner {
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(" 我是自定义的run方法1,实现 ApplicationRunner 接口既可运行"        );
    }
 
    @Override
    public void run(String... args) throws Exception {
        System.out.println(" 我是自定义的run方法2,实现 CommandLineRunner 接口既可运行"        );
    }
}

如下:

3、流程汇总

经过上面的各种的细节拆分,这边进行一个总结:

可以看到由7步组成。


参考文章:

1、9千字长文带你了解SpringBoot启动过程--史上最详细 SpringBoot启动流程-图文并茂-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LLaMA-Factory微调LLM-Research/Llama-3.2-3B-Instruct模型

1、GPU环境 nvidia-smi 2、pyhton环境安装 git clone https://github.com/hiyouga/LLaMA-Factory.git conda create -n llama_factory python3.10 conda activate llama_factory cd LLaMA-Factory pip install -e .[torch,metrics] 3、微调模型下载&#xff08;LLM-Research/…

3.8.1 利用RDD实现词频统计

在本次实战中&#xff0c;我们通过Spark的RDD实现了词频统计功能。首先&#xff0c;准备了包含单词的文件并上传至HDFS。接着&#xff0c;采用交互式方式逐步完成词频统计&#xff0c;包括创建RDD、单词拆分、映射为二元组、按键归约以及排序等操作。此外&#xff0c;还通过创建…

Spring Ioc和Aop,Aop的原理和实现案例,JoinPoint,@Aspect,@Before,@AfterReturning

DAY25.2 Java核心基础 Spring两大核心&#xff1a;Ioc和Aop IOC Ioc容器&#xff1a;装载bean的容器&#xff0c;自动创建bean 三种方式&#xff1a; 1、基于xml配置&#xff1a;通过在xml里面配置bean&#xff0c;然后通过反射机制创建bean&#xff0c;存入进Ioc容器中 …

[解决conda创建新的虚拟环境没用python的问题]

问题复现 使用conda create -n env的时候&#xff0c;在对应的虚拟环境的文件里面找不到对应的python文件 为什么 首先&#xff0c;我们来看一下创建环境时的触发链路&#xff1a; 这表明当前环境中找不到Python可执行文件。 解决方法 所以很明显&#xff0c;我们需要指定…

【C++】控制台小游戏

移动&#xff1a;W向上&#xff0c;S上下&#xff0c;A向左&#xff0c;D向右 程序代码&#xff1a; #include <iostream> #include <conio.h> #include <windows.h> using namespace std;bool gameOver; const int width 20; const int height 17; int …

配合本专栏前端文章对应的后端文章——从模拟到展示:一步步搭建传感器数据交互系统

对应文章&#xff1a;进一步完善前端框架搭建及vue-konva依赖的使用&#xff08;Vscode&#xff09;-CSDN博客 目录 一、后端开发 1.模拟传感器数据 2.前端页面呈现数据后端互通 2.1更新模拟传感器数据程序&#xff08;多次请求&#xff09; 2.2&#x1f9e9; 功能目标 …

springboot IOC

springboot IOC IoC Inversion of Control Inversion 反转 依赖注入 DI &#xff08;dependency injection &#xff09; dependency 依赖 injection 注入 Qualifier 预选赛 一文带你快速理解JavaWeb中分层解耦的思想及其实现&#xff0c;理解 IOC和 DI https://zhuanlan.…

Ajax01-基础

一、AJAX 1.AJAX概念 使浏览器的XMLHttpRequest对象与服务器通信 浏览器网页中&#xff0c;使用 AJAX技术&#xff08;XHR对象&#xff09;发起获取省份列表数据的请求&#xff0c;服务器代码响应准备好的省份列表数据给前端&#xff0c;前端拿到数据数组以后&#xff0c;展…

生成树协议(STP)配置详解:避免网络环路的最佳实践

生成树协议&#xff08;STP&#xff09;配置详解&#xff1a;避免网络环路的最佳实践 生成树协议&#xff08;STP&#xff09;配置详解&#xff1a;避免网络环路的最佳实践一、STP基本原理二、STP 配置示例&#xff08;华为交换机&#xff09;1. 启用生成树协议2. 配置根桥3. 查…

面向 C 语言项目的系统化重构实战指南

摘要: 在实际开发中,C 语言项目往往随着功能演进逐渐变得混乱:目录不清、宏滥用、冗余代码、耦合高、测试少……面对这样的“技术债积累”,盲目大刀阔斧只会带来更多混乱。本文结合 C 语言的特点,从项目评估、目录规划、宏与内联、接口封装、冗余剔除、测试与 CI、迭代重构…

Python Pandas库简介及常见用法

Python Pandas库简介及常见用法 一、 Pandas简介1. 简介2. 主要特点&#xff08;一&#xff09;强大的数据结构&#xff08;二&#xff09;灵活的数据操作&#xff08;三&#xff09;时间序列分析支持&#xff08;四&#xff09;与其他库的兼容性 3.应用场景&#xff08;一&…

第十六届蓝桥杯复盘

文章目录 1.数位倍数2.IPv63.变换数组4.最大数字5.小说6.01串7.甘蔗8.原料采购 省赛过去一段时间了&#xff0c;现在复盘下&#xff0c;省赛报完名后一直没准备所以没打算参赛&#xff0c;直到比赛前两天才决定参加&#xff0c;赛前两天匆匆忙忙下载安装了比赛要用的编译器ecli…

【已解决】HBuilder X编辑器在外接显示器或者4K显示器怎么界面变的好小问题

触发方式&#xff1a;主要涉及DPI缩放问题&#xff0c;可能在电脑息屏有概率触发 修复方式&#xff1a; 1.先关掉软件直接更改屏幕缩放&#xff0c;然后打开软件&#xff0c;再关掉软件恢复原来的缩放&#xff0c;再打开软件就好了 2.(不推荐&#xff09;右键HBuilder在属性里…

直线型绝对值位移传感器:精准测量的科技利刃

在科技飞速发展的今天&#xff0c;精确测量成为了众多领域不可或缺的关键环节。无论是工业自动化生产线上的精细操作&#xff0c;还是航空航天领域中对零部件位移的严苛把控&#xff0c;亦或是科研实验中对微小位移变化的精准捕捉&#xff0c;都离不开一款高性能的测量设备——…

Ansible模块——管理100台Linux的最佳实践

使用 Ansible 管理 100 台 Linux 服务器时&#xff0c;推荐遵循以下 最佳实践&#xff0c;以提升可维护性、可扩展性和安全性。以下内容结合实战经验进行总结&#xff0c;适用于中大型环境&#xff08;如 100 台服务器&#xff09;&#xff1a; 一、基础架构设计 1. 分组与分层…

从0开始学习大模型--Day09--langchain初步使用实战

众所周知&#xff0c;一味地学习知识&#xff0c;所学的东西和概念都是空中楼阁&#xff0c;大部分情况下&#xff0c;实战都是很有必要的&#xff0c;今天就通过微调langchain来更深刻地理解它。 中间如何进入到langchain界面请参考结尾视频链接。 首先&#xff0c;进入界面…

C++中的菱形继承问题

假设有一个问题&#xff0c;类似于鸭子这样的动物有很多种&#xff0c;如企鹅和鱿鱼&#xff0c;它们也可能会有一些共同的特性。例如&#xff0c;我们可以有一个叫做 AquaticBird &#xff08;涉禽&#xff0c;水鸟的一类&#xff09;的类&#xff0c;它又继承自 Animal 和 Sw…

网络-MOXA设备基本操作

修改本机IP和网络设备同网段&#xff0c;输入设备IP地址进入登录界面&#xff0c;交换机没有密码&#xff0c;路由器密码为moxa 修改设备IP地址 交换机 路由器 环网 启用Turbo Ring协议&#xff1a;在设备的网络管理界面中&#xff0c;找到环网配置选项&#xff0c;启用Turb…

飞桨paddle import fluid报错【已解决】

跟着飞桨的安装指南安装了paddle之后 pip install paddlepaddle有一个验证&#xff1a; import paddle.fluid as fluid fluid.install check.run check()报错情况如下&#xff0c;但是我在pip list中&#xff0c;确实看到了paddle安装上了 我import paddle别的包&#xff0c…

测试工程师要如何开展单元测试

单元测试是软件开发过程中至关重要的环节&#xff0c;它通过验证代码的最小可测试单元(如函数、方法或类)是否按预期工作&#xff0c;帮助开发团队在早期发现和修复缺陷&#xff0c;提升代码质量和可维护性。以下是测试工程师开展单元测试的详细步骤和方法&#xff1a; 一、理…