Springboot启动过程分析

news2024/9/21 4:39:36

Springboot启动过程分析

SpringBoot的版本是v3.0.2,下面进行详细的分析。

一、SpringBoot启动流程的主干

示例程序入口如下所示:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudySpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudySpringBootApplication.class, args); // ref-1
    }

}

ref-1处代码最终走到的核心代码如下所示:

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    // 记录开始时间
    long startTime = System.nanoTime(); // ref-2
    // 创建启动上下文
    DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // ref-6
    ConfigurableApplicationContext context = null;
    // 配置无头属性
    configureHeadlessProperty(); // ref-7
    // 获取监听器
    SpringApplicationRunListeners listeners = getRunListeners(args); //ref-8
    // 告知监听器:应用程序已经启动
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 准备环境变量信息
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // ref-9
        // 打印sping的标题栏
        Banner printedBanner = printBanner(environment);
        // 创建应用上下文
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        // 准备应用上下文
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // ref-11
        refreshContext(context); // ref-11
        afterRefresh(context, applicationArguments); // ref-15
        // 计算应用启动时间
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        // 通知监听器:应用已经启动了
        listeners.started(context, timeTakenToStartup);
        callRunners(context, applicationArguments); // ref-16
    }
    catch (Throwable ex) {
        if (ex instanceof AbandonedRunException) {
            throw ex;
        }
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        if (context.isRunning()) {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            listeners.ready(context, timeTakenToReady);
        }
    }
    catch (Throwable ex) {
        if (ex instanceof AbandonedRunException) {
            throw ex;
        }
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

接下来我们看看ref-6处的createBootstrapContext()方法,下面是该方法的内容:

// SpringApplication.java文件
private DefaultBootstrapContext createBootstrapContext() {
    // 创建默认的上下文
    DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
    // 使用初始化器对默认的上下文对象进行初始化
    this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
    return bootstrapContext;
}

DefaultBootstrapContext是一个启动上下文,我们来看看它的类层次关系:
在这里插入图片描述

ConfigurableBootstrapContext是一个组合性接口,BootstrapRegistry用来注册对象,BootstrapContext用来访问注册的单例对象。

接下来我们继续看ref-7处的configureHeadlessProperty(),详细代码如下:

// SpringApplication.java文件
private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
                       System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

简单讲,这段代码就是在确保属性java.awt.headless的值为true。这个属性叫作无头属性,是用来告知Java图形类库是否有显示器,当它设置为true时,可以保证在命令行情况下部分Java图形控件还是可以正常使用的。

我们再来看ref-8处的代码,如下所示:

// SpringApplication.java文件
public ConfigurableApplicationContext run(String... args) {
	...... // 省略其他代码
    SpringApplicationRunListeners listeners = getRunListeners(args); // ref-8
	listeners.starting(bootstrapContext, this.mainApplicationClass); 
    ...... // 省略其他代码
}

ref-8处的代码会获取监听器对象,然后会通知监听器对象应用已经启动了。

我们接着看ref-处9的prepareEnvironment(...)函数内容,代码如下所示:

// SpringApplication.java文件
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
    DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment(); // 创建一个环境信息对象
    // 配置属性源,并且配置被激活的profile
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 挂载属性源到环境信息对象
    ConfigurationPropertySources.attach(environment);
    // 通知监听器:环境信息已经准备好了
    listeners.environmentPrepared(bootstrapContext, environment);
    // 将默认的属性源移动到属性源列表的末尾
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
                 "Environment prefix cannot be set via properties.");
    // 绑定"spring.main"属性到环境信息对象
    bindToSpringApplication(environment);
    // 如果不是定制化的环境信息对象,而且环境信息对象的类型不是指定的类型,那么就要做转换
    if (!this.isCustomEnvironment) {
        EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
        // 转换就是把属性源从environment拷贝到指定类型的环境变量对象中。
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    // 将环境信息对象中的"configurationProperties"拿出来,然后放入到属性源列表的最后
    ConfigurationPropertySources.attach(environment);
    // 将准备好的环境信息对象返回
    return environment;
}

接下来我们继续看ref-10处准备应用上下文的代码:

// SpringApplication.java文件
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
     ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, Banner printedBanner) {
   	// 为应用上下文设置环境信息
    context.setEnvironment(environment);
    // 为应用上下文设置bean名称生成器、资源加载器、类加载器和转换服务。
    postProcessApplicationContext(context);
    // 添加特殊的初始化器 ApplicationContextInitializer 
    addAotGeneratedInitializerIfNecessary(this.initializers);
    // 调用初始化器来初始化这个应用上下文
    applyInitializers(context);
    // 通知监听器:应用上下文已经准备好
    listeners.contextPrepared(context);
    // 当应用上下文已经准备好并且启动上下文已经关闭的时候,广播该事件
    bootstrapContext.close(context);
    if (this.logStartupInfo) {
        // 通过日志输出启动信息
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans  设置启动阶段特殊的单例对象
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    // 注册应用参数单例对象
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        // 注册springBootBanner单例对象
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
        // 设置是否允许循环依赖
        autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
        if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
           // 设置是否允许bean定义重载 
        listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    }
    // 设置懒初始化
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // 设置Bean工厂的后置处理器,这个应用上下文刷新的时候,这个后置处理器就会应用到内部的bean工厂,
    // 然后bean对象的定义就会被执行。
    context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
    // 预编译技术(AOT)相关
    if (!AotDetector.useGeneratedArtifacts()) {
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        // 加载beans到应用上下文中
        load(context, sources.toArray(new Object[0]));
    }
    // 通知监听器:应用上下文已经加载完成
    listeners.contextLoaded(context);
}

接下来我们就分析ref-11处的应用上下文刷新refreshContext(...)的内容,函数代码如下所示:

// SpringApplication.java文件
private void refreshContext(ConfigurableApplicationContext context) {
    // 注册应用关闭时的钩子
    if (this.registerShutdownHook) {
        shutdownHook.registerApplicationContext(context);
    }
    // 刷新应用上下文
    refresh(context); // ref-12
}

我们继续看ref-12处的应用上下文刷新代码:

// SpringApplication.java文件
/**
* Refresh the underlying {@link ApplicationContext}.
* @param applicationContext the application context to refresh
*/
protected void refresh(ConfigurableApplicationjavaContext applicationContext) {
	applicationContext.refresh(); // ref-13
}

ref-13处的代码实际执行代码段如下所示:

// org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.java文件
@Override
public final void refresh() throws BeansException, IllegalStateException {
    try {
        super.refresh(); // ref-14
    }
    catch (RuntimeException ex) {
        WebServer webServer = this.webServer;
        if (webServer != null) {
            webServer.stop();
        }
        throw ex;
    }
}

ref-14处的代码实际执行代码段如下所示:

// org.springframework.context.support.AbstractApplicationContext.java文件
@Override
public void refresh() throws BeansException, IllegalStateException {
    // 启动和关闭时都要加锁
    synchronized (this.startupShutdownMonitor) {
        // 创建代表步骤刷新的StartupStep
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // Prepare this context for refreshing. 准备刷新该应用上下文
        prepareRefresh(); // 设置启动时间、激活标志,以及执行属性源的初始化

        // Tell the subclass to refresh the internal bean factory.
       	// 告诉子类去刷新内部的bean工厂
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        // 配置bean工厂需要地特征
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // bean工厂加载了所有bean的定义时,这儿方便子类对bean工厂进行后置处理
            postProcessBeanFactory(beanFactory);
			
            // 创建代表后置处理步骤的StartupStep
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // Invoke factory processors registered as beans in the context.
            // 调用bean工厂后置处理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 注册拦截bean创建的bean后置处理器
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end(); // 后置处理步骤结束

            // Initialize message source for this context. 
            // 初始化消息源,其实就是在获取注册到该应用上下文中的messageSource对象
            initMessageSource();

            // Initialize event multicaster for this context.
            // 初始化事件广播器,其实就是在获取注册到该应用上下文中的applicationEventMulticaster对象
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            // 初始化子类中的其他特殊bean
            onRefresh();

            // Check for listener beans and register them.
            // 添加实现了ApplicationListener接口的bean成为监听器
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 完成bean工厂的初始化,初始化所有剩余的对象
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            // 清除资源缓存、初始化这个应用上下文的生命周期处理器、发布应用上下文刷新的事件
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            // 应用上下文步骤结束
            contextRefresh.end();
        }
    }
}

接下来我们继续看应用刷新后的处理操作,也就是ref-15处的代码:

// org.springframework.boot.SpringApplication.java文件
/**
 * Called after the context has been refreshed.
 * @param context the application context
 * @param args the application arguments
 */
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

可以看到这个方法是一个空的实现,子类可以重写该方法完成刷新应用上下文之后的操作。

ref-16处的代码会调用ApplicationRunnerCommandLineRunner,函数细节如下所示:

// org.springframework.boot.SpringApplication.java文件
private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    // 获取所有的ApplicationRunner类型的实例
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 获取所有的CommandLineRunner类型的实例
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 依据注解上的优先级对runners进行排序
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        // 执行ApplicationRunner实现类的run(...)方法
        if (runner instanceof ApplicationRunner applicationRunner) {
            callRunner(applicationRunner, args);
        }
        // 执行CommandLineRunner实现类的run(...)方法
        if (runner instanceof CommandLineRunner commandLineRunner) {
            callRunner(commandLineRunner, args);
        }
    }
}

到这里,基本已经分析完了SpringBoot启动流程的主干路径。

二、ApplicationRunner和CommandLineRunner分析

在SpringBoot启动流程的主干路径的最后调用ApplicationRunnerCommandLineRunner两个接口的实现类。

ApplicationRunner接口定义如下:

// org.springframework.boot.ApplicationRunner.java文件
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see CommandLineRunner
 */
@FunctionalInterface
public interface ApplicationRunner {

    /**
	 * Callback used to run the bean.
	 * @param args incoming application arguments
	 * @throws Exception on error
	 */
    void run(ApplicationArguments args) throws Exception;

}

可以看到就是将启动类的参数传递给run(...)方法。

CommandLineRunner接口定义如下所示:

// org.springframework.boot.CommandLineRunner.java文件
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * <p>
 * If you need access to {@link ApplicationArguments} instead of the raw String array
 * consider using {@link ApplicationRunner}.
 *
 * @author Dave Syer
 * @since 1.0.0
 * @see ApplicationRunner
 */
@FunctionalInterface
public interface CommandLineRunner {

	/**
	 * Callback used to run the bean.
	 * @param args incoming main method arguments
	 * @throws Exception on error
	 */
	void run(String... args) throws Exception;

}

可以看到,基本上和ApplicationRunner差不多。

三、SpringApplicationRunListeners的获取与通知

我们先回顾一下主干流程中关于SpringApplicationRunListeners的部分,代码如下所示:

// SpringApplication.java文件
public ConfigurableApplicationContext run(String... args) {
	...... // 省略其他代码
    SpringApplicationRunListeners listeners = getRunListeners(args); // ref-8
	listeners.starting(bootstrapContext, this.mainApplicationClass); 
    ...... // 省略其他代码
}

ref-8代码处的函数如下所示:

// org.springframework.boot.SpringApplication.java文件
private SpringApplicationRunListeners getRunListeners(String[] args) {
    // 创建参数解析器,这个解析器会用来解析工厂类的构造器
    ArgumentResolver argumentResolver = ArgumentResolver.of(SpringApplication.class, this);
    // 追加可以解析的类型
    argumentResolver = argumentResolver.and(String[].class, args);
    // 获取监听器
    List<SpringApplicationRunListener> listeners = getSpringFactoriesInstances(SpringApplicationRunListener.class, argumentResolver); // ref-17
    SpringApplicationHook hook = applicationHook.get();
    SpringApplicationRunListener hookListener = (hook != null) ? hook.getRunListener(this) : null;
    // 将钩子监听器也添加到监听器列表中
    if (hookListener != null) {
        listeners = new ArrayList<>(listeners);
        listeners.add(hookListener);
    }
    return new SpringApplicationRunListeners(logger, listeners, this.applicationStartup);
}

我们接着看ref-17处的函数是如何获取工厂实例的:

// org.springframework.boot.SpringApplication.java文件
private <T> List<T> getSpringFactoriesInstances(Class<T> type, ArgumentResolver argumentResolver) {
    return SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver); // ref-18
}

ref-18处代码中的load(...)方法就是具体获取工厂实例的逻辑。下面是load(...)方法的代码细节:

// org.springframework.core.io.support.SpringFactoriesLoader.java文件
/**
* Load and instantiate the factory implementations of the given type from
* {@value #FACTORIES_RESOURCE_LOCATION}, using the configured class loader
* and the given argument resolver.
* <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
* <p>As of Spring Framework 5.3, if duplicate implementation class names are
* discovered for a given factory type, only one instance of the duplicated
* implementation type will be instantiated.
* @param factoryType the interface or abstract class representing the factory
* @param argumentResolver strategy used to resolve constructor arguments by their type
* @throws IllegalArgumentException if any factory implementation class cannot
* be loaded or if an error occurs while instantiating any factory
* @since 6.0
*/
public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver) {
    return load(factoryType, argumentResolver, null); // ref-19
}

这个load(...)方法的大概意思是通过指定的工厂类型和构造器参数解析器会找到对应的工厂类实现类,具体的工厂类实现类注册在META-INF/spring.factories文件中,下面是该文件的截图:
在这里插入图片描述

我们接下来看看ref-19处代码里面具体是怎么加载工厂类实例的:

// org.springframework.core.io.support.SpringFactoriesLoader.java文件
/**
 * Load and instantiate the factory implementations of the given type from
 * {@value #FACTORIES_RESOURCE_LOCATION}, using the configured class loader,
 * the given argument resolver, and custom failure handling provided by the given
 * failure handler.
 * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
 * <p>As of Spring Framework 5.3, if duplicate implementation class names are
 * discovered for a given factory type, only one instance of the duplicated
 * implementation type will be instantiated.
 * <p>For any factory implementation class that cannot be loaded or error that
 * occurs while instantiating it, the given failure handler is called.
 * @param factoryType the interface or abstract class representing the factory
 * @param argumentResolver strategy used to resolve constructor arguments by their type
 * @param failureHandler strategy used to handle factory instantiation failures
 * @since 6.0
 */
public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver,
                        @Nullable FailureHandler failureHandler) {

    Assert.notNull(factoryType, "'factoryType' must not be null");
    // 加载指定类型的工厂实例的名称
    List<String> implementationNames = loadFactoryNames(factoryType);
    logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames));
    List<T> result = new ArrayList<>(implementationNames.size());
    FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
    for (String implementationName : implementationNames) 
        // 使用反射工具创建工厂类的实例
        T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
        if (factory != null) {
            result.add(factory);
        }
    }
	// 对实例列表进行排序
    AnnotationAwareOrderComparator.sort(result);
    return result;
}

注释里面的大概意思是,会从META-INF/spring.factories中依据指定类型、参数解析器、失败处理器加载工厂实现类。加载完成后还会进行排序。如果有重复工厂实现类定义的话,只会加载一个。

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

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

相关文章

【网络知识】TCP和UDP详解

TCP和UDP 文章目录UDP协议概述TCP协议概述TCP报文段TCP连接的建立两天内完成下面的参考博客&#x1f60a;点此到文末惊喜↩︎ UDP协议 概述 TCP协议 概述 定义 传输控制协议&#xff08;TCP&#xff0c;Transmission Control Protocol&#xff09;是一种传输层通信协议&…

Python 之 Pandas DataFrame 数据类型的简介、创建的列操作

文章目录一、DataFrame 结构简介二、DataFrame 对象创建1. 使用普通列表创建2. 使用嵌套列表创建3 指定数值元素的数据类型为 float4. 字典嵌套列表创建5. 添加自定义的行标签6. 列表嵌套字典创建 DataFrame 对象7. Series 创建 DataFrame 对象三、DataFrame 列操作1. 选取数据…

【LeetCode】剑指 Offer(5)

目录 写在前面&#xff1a; 题目&#xff1a; 题目的接口&#xff1a; 解题思路1&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 解题思路2&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a;…

臻和科技再冲刺港交所上市:近三年亏损14亿元,有股东提前退出

近日&#xff0c;臻和科技集团有限公司&#xff08;下称“臻和科技”&#xff09;再次递交招股书&#xff0c;准备在港交所主板上市。据贝多财经了解&#xff0c;这已经是臻和科技第二次冲刺港交所上市。在此之前&#xff0c;臻和科技曾于2022年9月26日递表&#xff0c;后选择了…

hadoop02【尚硅谷】

HDFS 大数据学习笔记 一、HDFS产出背景及定义 HDFS产生背景 随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系统管理的磁盘中&#xff0c;但是不方便管理和维护&#xff0c;迫切需要一种系统来管理多台机器上的文件&#x…

python基于vue的酒店预约管理平台系统

当用户在上一步中的房间展示界面中点击了房间的图片或者名称之后系统会根据房间的ID自动的跳转到房间的详情页面中来&#xff0c;在房间的详情页面中可以看到房间的图片房间的价格房间的详细介绍房间的类型等内容&#xff0c;当用户登录之后还可以根据需要进行对房间进行预定&a…

Vulnhub靶场之PYLINGTON: 1

1.信息收集 1.输入arp-scan 192.168.239.0/24&#xff0c;探索存活主机&#xff0c;发现主机192.168.239.172存活。 2.对存活主机进行端口扫描&#xff0c;发现22(SSH)、80(Web)端口。 3.访问80端口&#xff0c;在浏览器上输出&#xff1a;http://192.168.239.172。 4.查看…

工具篇3.5世界热力图

一、定义 世界热力图是一种地图形式&#xff0c;它使用颜色的变化来显示世界各个地区的某种指标&#xff08;如 GDP、人口、气候等&#xff09;的分布和密度。通常&#xff0c;世界热力图会使用不同的颜色来表示数据的变化&#xff0c;例如使用蓝色表示低值&#xff0c;红色表…

算法leetcode|37. 解数独(rust重拳出击)

文章目录37. 解数独&#xff1a;样例 1&#xff1a;提示&#xff1a;分析&#xff1a;题解&#xff1a;rustgoccpythonjava37. 解数独&#xff1a; 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现…

如何配置git,使其支持多用户

如何配置git&#xff0c;使其支持多用户&#xff1f; 在多数时候&#xff0c; 我们使用git进行操作时&#xff0c;只需要在本地配置一个用户的ssh key&#xff0c;就可以完成基本的pull/push操作。如果现在我有两个github的账号&#xff0c;并需要在一台电脑中操作其中的repo&…

项目管理工具dhtmlxGantt甘特图入门教程(十):服务器端数据集成(下)

这篇文章给大家讲解如何利用dhtmlxGantt在服务器端集成数据。 dhtmlxGantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表&#xff0c;可满足应用程序的所有需求&#xff0c;是完善的甘特图图表库 DhtmlxGantt正版试用下载&#xff08;qun 764149912&#xff09;http…

LVGL WIN32模拟器环境搭建

LVGL WIN32模拟器环境搭建LVGL简介环境搭建IDE 选择模拟器代码下载PC模拟器搭建其他配置项说明LVGL简介 LVGL是一个跨平台、轻量级、易于移植的图形库。因其支持大量特性和其易于裁剪&#xff0c;配置开关众多&#xff0c;且版本升级较快&#xff0c;不同版本之间存在一定的差…

基于springboot+vue的医院信息管理系统

基于springbootvue的医院信息管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介…

2、登录界面开发

【任务描述】本任务要求使用线性布局以及TextView、EditText、Button等常见控件完成智慧园区登录界面的开发。1、线性布局&#xff08;LinearLayout&#xff09;1.1、LinearLayout概述线性布局&#xff08;LinearLayout&#xff09;主要以水平或垂直方式来排列界面中的控件。并…

【C++修炼之路】20.手撕红黑树

每一个不曾起舞的日子都是对生命的辜负 红黑树实现:RBTree 前言一.红黑树的概念及性质1.1 红黑树的概念1.2 红黑树的性质二.红黑树的结构2.1 红黑树节点的定义2.2 红黑树类的封装三.红黑树的插入情况1&#xff1a;只变色情况2&#xff1a;变色单旋情况3&#xff1a;双旋插入的代…

Docker入门和安装教程

一、Docker入门简介 Docker 是一个基于GO语言开发的开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱机制&#xff0c;相互之间不会…

关于如何在 Grafana 绘制 Apache Hudi Metrics 仪表盘的教程

文章目录前言下载 Prometheus下载 PushgatewayPrometheus 集成 PushgatewayPushgateway 后台执行Prometheus 后台执行在Prometheus中配置PushgatewayApache Hudi Metrics中开启 Pushgateway 推送Grafana 安装和启动Grafana 新增 Apache Hudi Metrics 仪表盘添加 Prometheus 数据…

批处理删除指定文件或文件夹

声明&#xff1a;1-2节参考了 https://blog.csdn.net/weixin_43960383/article/details/1243673841. DEL1.1 DEL 的命令参数使用 del 命令能指定文件&#xff0c;Del (erase)[Drive:][Path]FileName指删除指定文件。指定要删除的文件或文件集的位置和名称。语法格式如下&#x…

XML学习

文章目录XML什么是XML?XML的作用&#xff1f;XML语法文档声明XML注释元素&#xff08;标签&#xff09;xml属性语法规则5.4、xml解析技术介绍dom4j 解析技术&#xff08;重点&#xff09;dom4j 编程步骤&#xff1a;使用遍历标签 获取所有标签中的内容&#xff08;重点&#x…

第十三届蓝桥杯国赛 C++ C 组 Java A 组 C 组 Python C 组 E 题——斐波那契数组(三语言代码AC)

目录1.斐波那契数组1.题目描述2.输入格式3.输出格式4.样例输入5.样例输出6.数据范围7.原题链接2.解题思路3.Ac_code1.Java2.C3.Python1.斐波那契数组 1.题目描述 如果数组 A(a0,a1,⋯.an−1)A(a_0,a_1,⋯.a_{n-1})A(a0​,a1​,⋯.an−1​)满足以下条件, 就说它是一个斐波那契…