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);
            //  打印banner
            Banner 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);
         // 初始化context
        this.applyInitializers(context);
         // 通知监听器上下文准备阶段
        listeners.contextPrepared(context);
         // 打印 profile
        if (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方法执行完毕。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前端知识一

&#xff08;ref函数&#xff09;1.为什么vue3中使用ref来创建响应式数据&#xff0c;而不是直接声明一个变量 import { ref } from "vue";const count ref(0); // 创建一个响应式的计数器&#xff0c;初始值为0function increment() {count.value; // 增加计数器的…

STM32---FreeRTOS中断管理试验

一、实验 实验目的&#xff1a;学会使用FreeRTOS的中断管理 创建两个定时器&#xff0c;一个优先级为4&#xff0c;另一个优先级为6&#xff1b;注意&#xff1a;系统所管理的优先级范围 &#xff1a;5~15 现象&#xff1a;两个定时器每1s&#xff0c;打印一段字符串&#x…

数据结构知识学习小结

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

swift4-汇编分析枚举内存布局

一、枚举的内存原理 1.1 常规case enum TestEnum { case test1, test2, test3 } var t TestEnum.test1 t .test2 t .test3枚举是常规的case的情况-是采取一个字节来存枚举变量通过拿到枚举的内存地址&#xff0c;看地址里面存的枚举值情况窥探枚举内存存储情况 var t Te…

Anolis服务器Arm64架构服务器配置(其他版本服务器解决方式思路一质)

Anolis服务器Arm64架构服务器配置 1.nginx配置1.1.尝试安装nginx1.2.资源准备1.2.1.查看服务器系统版本:1.2.2.下载依赖包:1.3.正式安装nginx1.3.1.下载nginx并上传服务器1.3.2.开始安装nginx1.4.防火墙配置1.4.1.直接关闭防火墙:不推荐,但省事1.4.2.命令介绍1.4.3.配置开启…

threejs:着色器onBeforeCompile给导入的模型添加光带扫描效果

模型材质属性丢失 上一篇博客我们学习了用着色器给模型添加光带扫描效果&#xff0c;今天来学习给导入的模型添加光带扫描效果&#xff0c;目标是给如下图的立筒仓加光带扫描。 首先我们试试原来的方法还是否有效。 import * as THREE from three;// 引入gltf模型加载库GLTFL…

MySQL零基础教程16—表连接进阶

复习表别名 之前已经学习过&#xff0c;查询的时候可以使用as来对检索的列进行重命名&#xff0c;这样可以让sql更加简介&#xff0c;增强易读性&#xff08;as可以省略&#xff09; 此外&#xff0c;使用表别名还可以支持在一条select语句中&#xff0c;一个表是被多次使用 …

K8s控制器Deployment详解

回顾 ReplicaSet 控制器,该控制器是用来维护集群中运行的 Pod 数量的&#xff0c;但是往往在实际操作的时候&#xff0c;我们反而不会去直接使用 RS&#xff0c;而是会使用更上层的控制器&#xff0c;比如说 Deployment。 Deployment 一个非常重要的功能就是实现了 Pod 的滚动…

rabbitmq-amqp事务消息+消费失败重试机制+prefetch限流

1. 安装和配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <arti…

探秘基带算法:从原理到5G时代的通信变革【八】QAM 调制 / 解调

文章目录 2.7 QAM 调制 / 解调2.7.1 概述2.7.2 星座图星座图的结构与性能发射端的信息编码与接收端的解码差分编码的分类与实现差分编码的模4格雷加法器公式16QAM星座图与映射关系 2.7.3 信号表达式正交振幅调制的基本原理与系统分析相位误差对QAM性能的影响多电平正交振幅调制…

idea生成自定义Maven原型(archetype)项目工程模板

一、什么是Maven原型&#xff08;Maven archetype&#xff09; 引自官网的介绍如下&#xff1a; Maven原型插件官网地址 这里采用DeepSeek助手翻译如下&#xff1a; Maven 原型 什么是原型&#xff1f; 简而言之&#xff0c;原型是一个 Maven 项目模板工具包。原型被定义为一…

决策树(Decision Tree)基础知识

目录 一、回忆1、*机器学习的三要素&#xff1a;1&#xff09;*函数族2&#xff09;*目标函数2.1&#xff09;*模型的其他复杂度参数 3&#xff09;*优化算法 2、*前处理/后处理1&#xff09;前处理&#xff1a;特征工程2&#xff09;后处理&#xff1a;模型选择和模型评估 3、…

Python学习(十四)pandas库入门手册

目录 一、安装与导入二、核心数据结构2.1 Series 类型&#xff08;一维数组&#xff09;2.2 DataFrame 类型&#xff08;二维数组&#xff09; 三、数据读取与写入3.1 读取 CSV 和 Excel 文件3.2 写入数据 四、数据清洗与处理4.1 处理缺失值4.2 数据筛选4.3 数据排序 五、数据分…

通过计费集成和警报监控 Elasticsearch Service 成本

作者&#xff1a;来自 Elastic Alexis Charveriat 使用 Elasticsearch 服务计费集成来跟踪、定制和提醒 Elasticsearch 服务费用。 监控和管理你的Elasticsearch服务&#xff08;ESS&#xff09;使用情况和成本对高效运营至关重要。 Elasticsearch服务计费集成提供了一种简化的…

cmake、CMakeLists.txt、make、ninja

文章目录 一、概念0.cmake官网1.什么是cmake2.为什么使用cmake3.CMakeLists.txt 二、CMakeLists.txt语法&#xff1a;如何编写CMakeLists.txt&#xff0c;语法详解(0)语法基本原则(1)project关键字(2)set关键字(3)message关键字(4)add_executable关键字(5)add_subdirectory关键…

DeepSeek本地接口调用(Ollama)

前言 上篇博文&#xff0c;我们通过Ollama搭建了本地的DeepSeek模型&#xff0c;本文主要是方便开发人员&#xff0c;如何通过代码或工具&#xff0c;通过API接口调用本地deepSeek模型 前文&#xff1a;DeepSeek-R1本地搭建_deepseek 本地部署-CSDN博客 注&#xff1a;本文不仅…

前端基础之浏览器本地存储

如我们在一些网站中&#xff0c;去进行数据搜索&#xff0c;在浏览器中是有一个对于的存储的&#xff0c;并且我们可以去手动进行value的增删操作 LocalStroage的使用 并且将浏览器关闭之后&#xff0c;数据也会保存&#xff0c;除非用户手动清理数据或是清空缓存 <!DOCTYPE…

2025 聚合易支付完整版PHP网站源码

源码介绍 2025 聚合易支付完整版PHP网站源码 PHP版本&#xff1a;PHP74 源码上传服务器&#xff0c;解压访问域名即可安装 安装完成后一定要设置伪静态 源码里面nginx.txt 就是伪静态 然后复制粘贴到伪静态里面保存即可 部分截图 源码获取 2025 聚合易支付完整版PHP网站源码…

Spring Boot 3 整合 MinIO 实现分布式文件存储

引言 文件存储已成为一个做任何应用都不可回避的需求。传统的单机文件存储方案在面对大规模数据和高并发访问时往往力不从心&#xff0c;而分布式文件存储系统则提供了更好的解决方案。本篇文章我将基于Spring Boot 3 为大家讲解如何基于MinIO来实现分布式文件存储。 分布式存…

easyExcel使用案例有代码

easyExcel 入门,完成web的excel文件创建和导出 easyExcel官网 EasyExcel 的主要特点如下&#xff1a; 1、高性能&#xff1a;EasyExcel 采用了异步导入导出的方式&#xff0c;并且底层使用 NIO 技术实现&#xff0c;使得其在导入导出大数据量时的性能非常高效。 2、易于使…