SpringBoot启动过程(生命周期及事件)概述

总结:

SpringBoot启动过程(生命周期及事件)概述

========================

详见正文:SpringBoot生命周期事件——BAT的乌托邦

正文

本文将以 SpringApplication的启动流程/生命周期各时期发出的Event事件为主线,结合每个生命周期内 完成的大事记介绍,真正实现一文让你总览Spring Boot的全貌,这对你 深入理解Spring Boot,以及整合进Spring Cloud都将非常重要

为表诚意,本文一开始便把 SpringApplication生命周期事件流程图附上,然后再精细化讲解各个事件的详情。

话外音:赶时间的小伙伴可以拿图走人?,但不建议白嫖哟

生命周期事件流程图

版本说明:

由于不同版本、类路径下存在不同包时结果会存在差异,不指明版本的文章都是不够负责任的。因此对导包/版本情况作出如下说明:

  • Spring Boot:2.2.2.RELEASE。有且仅导入 spring-boot-starter-webspring-boot-starter-actuator
  • Spring Cloud:Hoxton.SR1。有且仅导入 spring-cloud-context(注意:并非spring-cloud-starter,并不含有spring-cloud-commons哦)

总的来说:本例导包是非常非常”干净”的,这样在流程上才更有说服力嘛~

SpringApplicationEvent

它是和 SpringApplication生命周期有关的 所有事件的父类,@since 1.0.0。

public abstract class SpringApplicationEvent extends ApplicationEvent {
    private final String[] args;

    public SpringApplicationEvent(SpringApplication application, String[] args) {
        super(application);
        this.args = args;
    }
    public SpringApplication getSpringApplication() {
        return (SpringApplication) getSource();
    }
    public final String[] getArgs() {
        return this.args;
    }
}

它是抽象类,扩展自Spring Framwork的 ApplicationEvent,确保了事件和应用实体 SpringApplication产生关联(当然还有 String[] args)。它有如下实现子类(7个):

每个事件都代表着SpringApplication不同生命周期所处的位置,下面分别进行讲解。

ApplicationStartingEvent:开始启动中

@since 1.5.0,并非1.0.0就有的哦。不过现在几乎没有人用1.5以下的版本了,所以可当它是标准事件。

完成的大事记

  • SpringApplication实例已实例化: new SpringApplication(primarySources)
  • 它在实例化阶段完成了如下几件”大”事:
    • 推断出应用类型 webApplicationType、main方法所在类
    • 给字段initializers赋值:拿到SPI方式配置的 ApplicationContextInitializer上下文初始化器
    • 给字段listeners赋值:拿到SPI方式配置的 ApplicationListener应用监听器
  • 注意:在此阶段(早期阶段)不要过多地使用它的内部状态,因为它可能在生命周期的后期被修改(话外音:使用时需谨慎)
  • 此时, SpringApplicationRunListener已实例化:它通过SPI方式指定 org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener
  • 若你有自己的运行时应用监听器,使用相同方式配置上即可,均会生效
  • 由于 EventPublishingRunListener已经实例化了,因此在后续的事件发送中,均能够触发对应的监听器的执行
  • 发送ApplicationStartingEvent事件,触发对应的监听器的执行

监听此事件的监听器们

默认情况下,有4个监听器监听 ApplicationStartingEvent事件:

  1. LoggingApplicationListener:@since 2.0.0。对日志系统抽象 LoggingSystem执行实例化以及初始化之前的操作,默认使用的是基于Logback的 LogbackLoggingSystem
  2. BackgroundPreinitializer:启动一个后台进行对一些类进行 预热。如 ValidationInitializer、JacksonInitializer...,因为这些组件有第一次惩罚的特点(并且首次初始化均还比较耗时),所以使用后台线程先预热效果更佳
  3. DelegatingApplicationListener:它监听的是 ApplicationEvent,而实际上只会 ApplicationEnvironmentPreparedEvent到达时生效,所以此处忽略
  4. LiquibaseServiceLocatorApplicationListener:略

总结:此事件节点结束时, SpringApplication完成了一些实例化相关的动作:本实例实例化、本实例属性赋值、日志系统实例化等。

ApplicationEnvironmentPreparedEvent:环境已准备好

@since 1.0.0。该事件节点是最为重要的一个节点之一,因为对于Spring应用来说,环境抽象 Enviroment简直太重要了,它是最为基础的 元数据,决定着程序的构建和走向,所以构建的时机是比较早的。

完成的大事记

  • 封装命令行参数(main方法的args)到 ApplicationArguments里面
  • 创建出一个环境抽象实例 ConfigurableEnvironment的实现类,并且填入值:Profiles配置和Properties属性,默认内容如下(注意,这只是初始状态,后面还会改变、添加属性源,实际见最后的截图):

  • 发送ApplicationEnvironmentPreparedEvent事件,触发对应的监听器的执行

  • 对环境抽象Enviroment的 填值,均是由监听此事件的监听器去完成,见下面的监听器详解
  • bindToSpringApplication(environment):把环境属性中 spring.main.xxx = xxx绑定到当前的SpringApplication实例属性上,如常用的 spring.main.allow-bean-definition-overriding=true会被绑定到当前 SpringApplication实例的对应属性上

监听此事件的监听器们

默认情况下,有9个监听器监听 ApplicationEnvironmentPreparedEvent事件:

  1. BootstrapApplicationListener:来自SC。优先级最高,用于启动/创建Spring Cloud的应用上下文。需要注意的是:到此时SB的上下文 ApplicationContext还并没有创建哦。这个流程”嵌套”特别像Bean初始化流程:初始化Bean A时,遇到了Bean B,就需要先去完成Bean B的初始化, 再回头来继续完成Bean A的步骤。
  2. 说明:在创建SC的应用的时候,使用的也是 SpringApplication#run()完成的(非web),因此也会走下一整套SpringApplication的生命周期逻辑,所以请你务必区分。
    • 特别是这种case会让”绝大多数”初始化器、监听器等执行多次,若你有那种只需要执行一次的需求(比如只想让SB容器生命周期内执行,SC生命周期不执行), 请务必自行处理,否则会被执行多次而带来不可预知的结果
  3. SC应用上下文读取的外部化配置文件名默认是: bootstrap,使用的也是 ConfigFileApplicationListener完成的加载/解析
  4. LoggingSystemShutdownListener:来自SC。对 LogbackLoggingSystem先清理,再重新初始化一次,效果同上个事件,相当于重新来了一次,毕竟现在有Enviroment环境里嘛
  5. ConfigFileApplicationListener:@since 1.0.0。 它也许是最重要的一个监听器。做了如下事情:
  6. 加载SPI配置的所有的 EnvironmentPostProcessor实例,并且排好序。需要注意的是: ConfigFileApplicationListener也是个 EnvironmentPostProcessor,会参与排序哦
  7. 排好序后,分别一个个的执行 EnvironmentPostProcessor(@since 1.3.0,并非一开始就有),介绍如下:
    • SystemEnvironmentPropertySourceEnvironmentPostProcessor:@since 2.0.0。把 SystemEnvironmentPropertySource替换为其子类 OriginAwareSystemEnvironmentPropertySource(属性值带有Origin来源),仅此而已
    • SpringApplicationJsonEnvironmentPostProcessor:@since 1.3.0。把环境中 spring.application.json=xxx值解析成为一个 MapPropertySource属性源,然后放进环境里面去(属性源的位置是做了处理的,一般不用太关心)
    • 可以看到,SB是直接支持JSON串配置的哦。Json解析参见: JsonParser
    • CloudFoundryVcapEnvironmentPostProcessor:@since 1.3.0。略
    • ConfigFileApplicationListener:@since 1.0.0(它比EnvironmentPostProcessor先出现的哦)。加载 application.properties/yaml等外部化配置,解析好后放进环境里(这应该是最为重要的)。
    • 外部化配置默认的优先级为: "classpath:/,classpath:/config/,file:./,file:./config/"。当前工程下的config目录里的application.properties优先级最高,当前工程类路径下的application.properties优先级最低
    • 值得强调的是:bootstrap.xxx也是由它负责加载的,处理规则一样
    • DebugAgentEnvironmentPostProcessor:@since 2.2.0。处理和 reactor测试相关,略
  8. AnsiOutputApplicationListener:@since 1.2.0。让你的终端(可以是控制台、可以是日志文件)支持Ansi彩色输出,使其更具可读性。当然前提是你的终端支持ANSI显示。参考类: AnsiOutput。你可通过 spring.output.ansi.enabled = xxx配置,可选值是: DETECT/ALWAYS/NEVER,一般不动即可。另外,针对控制台可以单独配置: spring.output.ansi.console-available = true/false
  9. LoggingApplicationListener:@since 2.0.0。根据Enviroment环境完成 initialize()初始化动作:日志等级、日志格式模版等
  10. 值得注意的是:它这步相当于在ApplicationStartingEvent事件基础上进一步完成了初始化(上一步只是实例化)
  11. ClasspathLoggingApplicationListener:@since 2.0.0。用于把classpath路径以 log.debug()输出,略
  12. 值得注意的是:classpath类路径是有N多个的 Arrays.toString(((URLClassLoader) classLoader).getURLs()),也就是说每个.jar里都属于classpath的范畴
  13. 当然喽,你需要注意Spring在处理类路径时: classpath和classpath*的区别~,这属于基础知识
  14. BackgroundPreinitializer:本事件达到时无动作
  15. DelegatingApplicationListener:执行通过外部化配置 context.listener.classes = xxx,xxx的监听器们,然后把该事件广播给他们,关心此事件的监听器执行
  16. 这麽做的好处:可以通过属性文件外部化配置监听器,而不一定必须写在 spring.factories里,更具弹性
  17. 外部化配置的执行优先级,还是相对较低的,到这里才给与执行嘛
  18. FileEncodingApplicationListener:检测当前系统环境的file.encoding和spring.mandatory-file-encoding设置的值是否一样,如果不一样则抛出异常如果不配置spring.mandatory-file-encoding则不检查

总结:此事件节点结束时,Spring Boot的环境抽象Enviroment已经准备完毕,但此时其上下文 ApplicationContext没有创建,但是Spring Cloud的应用上下文(引导上下文)已经 全部初始化完毕哦,所以SC管理的外部化配置也应该都进入到了SB里面。如下图所示(这是基本上算是Enviroment的最终态了):

小提示:SC配置的优先级是高于SB管理的外部化配置的。例如针对 spring.application.name这个属性,若bootstrap里已配置了值,再在application.yaml里配置其实就无效了,因此生产上建议不要写两处。

ApplicationContextInitializedEvent:上下文已实例化

@since 2.1.0,非常新的一个事件。当SpringApplication的上下文 ApplicationContext准备好 ,对单例Bean们实例化之 ,发送此事件。所以此事件又可称为: contextPrepared事件。

完成的大事记

  • printBanner(environment):打印Banner图,默认打印的是Spring Boot字样
  • spring.main.banner-mode = xxx来控制Banner的输出,可选值为 CONSOLE/LOG/OFF,一般默认就好
  • 默认在类路径下放置一个 banner.txt文件,可实现自定义Banner。关于更多自定义方式,如使用图片、gif等,本处不做过多介绍
    • 小建议:别花里胡哨搞个佛祖在那。让它能自动打印输出 当前应用名,这样才是最为实用,最高级的(但需要你定制化开发,并且支持可配置,最好对使用者无感,属于一个common组件)
  • 根据是否是web环境、是否是REACTIVE等,用 空构造器创建出一个 ConfigurableApplicationContext上下文实例(因为使用的是空构造器,所以不会立马”启动”上下文)
  • SERVLET -> AnnotationConfigServletWebServerApplicationContext
  • REACTIVE -> AnnotationConfigReactiveWebServerApplicationContext
  • 非web环境 -> AnnotationConfigApplicationContext(SC应用的容器就是使用的它)
  • 既然上下文实例已经有了,那么就开始对它进行一些参数的设置:
  • 首先 最重要的便是把已经准备好的环境Enviroment环境设置给它
  • 设置些beanNameGenerator、resourceLoader、ConversionService等组件
  • 实例化所有的 ApplicationContextInitializer上下文初始化器,并且排序好后挨个执行它(这个很重要),默认有如下截图这些初始化器此时要执行:下面对这些初始化器分别做出简单介绍:
  • BootstrapApplicationListener.AncestorInitializer:来自SC。用于把SC容器设置为SB容器的父容器。当然实际操作委托给了此方法: new ParentContextApplicationContextInitializer(this.parent).initialize(context)去完成
  • BootstrapApplicationListener.DelegatingEnvironmentDecryptApplicationInitializer:来自SC。代理了下面会提到的 EnvironmentDecryptApplicationInitializer,也就是说在此处就会先执行,用于提前解密Enviroment环境里面的属性,如相关URL等
  • PropertySourceBootstrapConfiguration:来自SC。重要,和配置中心相关, 若想自定义配置中心必须了解它。主要作用是 PropertySourceLocator属性源定位器,我会把它放在配置中心章节详解
  • EnvironmentDecryptApplicationInitializer:来自SC。属性源头通过上面加载回来了,通过它来实现解密
    • 值得注意的是:它被执行了两次哦~
  • DelegatingApplicationContextInitializer:和上面的 DelegatingApplicationListener功能类似,支持外部化配置 context.initializer.classes = xxx,xxx
  • SharedMetadataReaderFactoryContextInitializer:略
  • ContextIdApplicationContextInitializer:@since 1.0.0。设置应用ID -> applicationContext.setId()。默认取值为 spring.application.name,再为application,再为自动生成
  • ConfigurationWarningsApplicationContextInitializer:@since 1.2.0。对错误的配置进行 警告(不会终止程序),以warn()日志输出在控制台。默认内置的只有对包名的检查:若你扫包含有 "org.springframework"/"org"这种包名就警告

    • 若你想自定义检查规则,请实现 Check接口,然后…
  • RSocketPortInfoApplicationContextInitializer:@since 2.2.0。暂略

  • ServerPortInfoApplicationContextInitializer:@since 2.0.0。将自己作为一个监听器注册到上下文 ConfigurableApplicationContext里,专门用于监听 WebServerInitializedEvent事件(非SpringApplication的生命周期事件)
    • 该事件有两个实现类: ServletWebServerInitializedEventReactiveWebServerInitializedEvent。发送此事件的时机是 WebServer已启动完成,所以已经有了监听的端口号
    • 该监听器做的事有两个:
    • "local." + getName(context.getServerNamespace()) + ".port"作为key(默认值是 local.server.port),value是端口值。这样可以通过@Value来获取到本机端口了(但貌似端口写0的时候,SB在显示上有个小bug)
    • 作为一个属性源 MapPropertySource放进环境里,属性源名称为: server.ports(因为一个server是可以监听多个端口的,所以这里用复数)
  • ConditionEvaluationReportLoggingListener:将 ConditionEvaluationReport报告(自动配置中哪些匹配了,哪些没匹配上)写入日志,当然只有 LogLevel#DEBUG时才会输出(注意:这不是日志级别哦,应该叫报告级别)。如你配置 debug=true就开启了此自动配置类报告
    • 槽点:它明明是个初始化器,为毛命名为Listener?
  • 发送ApplicationContextInitializedEvent事件,触发对应的监听器的执行

监听此事件的监听器们

默认情况下,有2个监听器监听 ApplicationContextInitializedEvent事件:

  1. BackgroundPreinitializer:本事件达到时无动作
  2. DelegatingApplicationListener:本事件达到时无动作

总结:此事件节点结束时,完成了应用上下文ApplicationContext的准备工作, 并且执行所有注册的上下文初始化器 ApplicationContextInitializer。但是此时,单例Bean是仍旧还没有初始化,并且WebServer也还没有启动

ApplicationPreparedEvent:上下文已准备好

@since 1.0.0。截止到上个事件 ApplicationContextInitializedEvent,应用上下文ApplicationContext充其量叫 实例化好了,但是还剩下很重要的事没做,这便是本周期的内容。

完成的大事记

  • 把applicationArguments、printedBanner等都作为一个Bean放进Bean工厂里(因此你就可以@Autowired注入的哦)
  • 比如:有了Banner这个Bean,你可以在你任何想要输出的地方输出一个Banner,而不仅仅是启动时只会输出一次了
  • lazyInitialization = true延迟初始化,那就向Bean工厂放一个: new LazyInitializationBeanFactoryPostProcessor()
  • 该处理器@since 2.2.0。该处理器的作用是:对所有的Bean(通过 LazyInitializationExcludeFilter接口指定的排除在外)全部 .setLazyInit(true);延迟初始化
  • 根据primarySources和allSources,交给 BeanDefinitionLoader(SB提供的实现)实现加载 Bean的定义信息,它支持4种加载方式(4种源):
  • AnnotatedBeanDefinitionReader -> 基于注解
  • XmlBeanDefinitionReader -> 基于xml配置
  • GroovyBeanDefinitionReader -> Groovy文件
  • ClassPathBeanDefinitionScanner -> classpath中加载
  • (不同的源使用了不同的load加载方式)
  • 发送ApplicationPreparedEvent事件,触发对应的监听器的执行

监听此事件的监听器们

默认情况下,有6个监听器监听 ApplicationContextInitializedEvent事件:

  1. CloudFoundryVcapEnvironmentPostProcessor:略
  2. ConfigFileApplicationListener:向上下文注册一个 new PropertySourceOrderingPostProcessor(context)。它的作用是:Bean工厂结束后对环境里的属性源进行重排序 -> 把名字叫 defaultProperties的属性源放在最末位
  3. 该属性源是通过 SpringApplication#setDefaultProperties API方式放进来的,一般不会使用到,留个印象即可
  4. LoggingApplicationListener:因为这时已经有Bean工厂了嘛,所以它做的事是:向工厂内放入Bean
  5. “springBootLoggingSystem” -> loggingSystem
  6. “springBootLogFile” -> logFile
  7. “springBootLoggerGroups” -> loggerGroups
  8. BackgroundPreinitializer:本事件达到时无动作
  9. RestartListener:SC提供。把当前最新的上下文缓存起来而已,目前并未发现有实质性作用,可忽略
  10. DelegatingApplicationListener:本事件达到时无动作

总结:此事件节点结束时,应用上下文ApplicationContext 初始化完成,该赋值的赋值了,Bean定义信息也已全部加载完成。但是,单例Bean还没有被实例化,web容器依旧还没启动。

ApplicationStartedEvent:应用成功启动

@since 2.0.0。截止到此,应用已经 准备就绪,并且通过监听器、初始化器等完成了非常多的工作了,但仍旧剩下 被认为最为重要的初始化单例Bean动作还没做、web容器(如Tomcat)还没启动,这便是这个周期所要做的事。

完成的大事记

  • 启动Spring容器: AbstractApplicationContext#refresh(),这个步骤会做很多事,比如会 实例化单例Bean
  • 该步骤属于Spring Framework的核心内容范畴,做了很多事,请参考Spring核心技术内容章节
  • 在Spring容器refresh()启动完成后, WebServer也随之完成启动, 成功监听到对应端口(们)
  • 输出启动成功的日志: Started Application in xxx seconds (JVM running for xxx)
  • 发送ApplicationStartedEvent事件,触发对应的监听器的执行
  • callRunners():依次执行容器内配置的 ApplicationRunner/CommandLineRunner的Bean实现类,支持sort排序
  • ApplicationRunner:@since 1.3.0,入参是 ApplicationArguments,先执行(推荐使用)
  • CommandLineRunner:@since 1.0.0,入参是 String... args,后执行(不推荐使用)

监听此事件的监听器们

默认情况下,有3个监听器监听 ApplicationStartedEvent事件:

  1. 前两个不用再解释了吧:本事件达到时无动作
  2. TomcatMetricsBinder:@since 2.1.0。和监控相关:将你的tomcat指标信息 TomcatMetrics绑定到 MeterRegistry,从而就能收集到相关指标了

总结:此事件节点结束时, SpringApplication的生命周期到这一步, 正常的启动流程就全部完成了。也就说Spring Boot应用可以正常对对外提供服务了。

ApplicationReadyEvent:应用已准备好

@since 1.3.0。该事件所处的生命周期可认为基本同 ApplicationStartedEvent,仅是在其后执行而已,两者中间并无其它特别的动作,但是监听此事件的监听器们还是 蛮重要的

完成的大事记

同上。

监听此事件的监听器们

默认情况下,有4个监听器监听 ApplicationStartedEvent事件:

  1. SpringApplicationAdminMXBeanRegistrar:当此事件到达时,告诉Admin Spring应用已经ready,可以使用啦。
  2. 中间这两个不用再解释了吧:本事件达到时无动作
  3. RefreshEventListener:当此事件到达时,告诉Spring应用已经ready了,接下来便可以执行 ContextRefresher.refresh()

总结:此事件节点结束时,应用已经完完全全的准备好了,并且也已经完成了相关组件的周知工作。

异常情况

SpringApplication是有可能在启动的时候失败(如端口号已被占用),当然任何一步骤遇到异常时交给 SpringApplication#handleRunFailure()方法来处理,这时候也会有对应的事件发出。

ApplicationFailedEvent:应用启动失败

SpringApplication在启动时抛出异常:可能是端口绑定、也可能是你自定义的监听器你写了个bug等,就会”可能”发送此事件。

完成的大事记
  • 得到异常的退出码ExitCode,然后发送 ExitCodeEvent事件(非生命周期事件)
  • 发送 ApplicationFailedEvent事件,触发对应的监听器的执行
监听此事件的监听器们

默认情况下,有6个监听器监听 ApplicationStartedEvent事件:

  1. LoggingApplicationListener:执行 loggingSystem.cleanUp()清理资源
  2. ClasspathLoggingApplicationListener:输出一句debug日志: Application failed to start with classpath: ...
  3. 中间这两个不用再解释了吧:本事件达到时无动作
  4. ConditionEvaluationReportLoggingListener:自动配置输出报告,输出错误日志呗:特别方便你查看和错误定位
  5. 不得不夸:SB对错误定位这块才真叫智能,比Spring Framework好用太多了
  6. BootstrapApplicationListener.CloseContextOnFailureApplicationListener:执行context.close()

总结:此事件节点结束时,会做一些释放资源的操作。一般情况下:我们并不需要监听到此事件

总结

关于 SpringApplication的生命周期体系的介绍就到这了,相信通过此”万字长文”你能体会到A哥的用心。翻了翻市面上的相关文章,本文Almost可以保证是总结得最到位的,让你通过一文便可从大的方面基本掌握Spring Boot,这不管是你使用SB,还是后续自行扩展、精雕细琢SB,以及去深入了解Spring Cloud均由非常重要的意义,希望对你有帮助,谢谢你的三连。

Original: https://www.cnblogs.com/z-sm/p/15120013.html
Author: March On
Title: SpringBoot启动过程(生命周期及事件)概述

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/541156/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 第一个mybatis程序

    2、第一个mybatis程序 思路:搭建环境—>导入mybatis—>编写代码—>测试! 2.1、搭建环境 搭建数据库 CRE…

    Java 2023年6月13日
    082
  • java内存模型学习

    根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。 虚拟机的内存模型分为两部分:一部分是线程共享的,包括 Java 堆和方法区;另一部分是…

    Java 2023年5月29日
    079
  • 子串次数——类似KMP

    老规矩 先来看问题 字符串a 在A中出现了多少次 求次数 。 当然有很多种算法 最简单的 一个一个找呗 不过这种太麻烦 我们不讲这种 我在写这个算法的时候 用到了KMP算法的部分内…

    Java 2023年6月5日
    058
  • Java中类成员访问权限修饰符(public、protected、default、private)

    1.public(公共): 任何类都可以进行访问(最不严格)。 2.protected(受保护): 同一包内的类以及其子类可以进行访问。 3.default(默认): 类中不加任何…

    Java 2023年6月13日
    064
  • Java学习 (18) Java数组篇(02)数组特点&数组边界

    数组的特点 其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。 其元素必须是相同类型,不允许出现混合类型。 数组中的元素可以是任何数据类型,包括基本类型和引用类型。 *数组…

    Java 2023年6月8日
    058
  • Java基础 | Stream流原理与用法总结

    Stream简化元素计算; 一、接口设计 从Java1.8开始提出了Stream流的概念,侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式;依旧先看核心接口的设计: B…

    Java 2023年6月15日
    082
  • java中的定时任务

    java中的定时任务, 使用java实现有3种方式: 1, 使用普通thread实现 2, 使用timer实现: 可控制启动或取消任务, 可指定第一次执行的延迟 线程安全, 但只会…

    Java 2023年5月29日
    065
  • 电脑常见快捷键

    一、键盘功能键: Tab键:切换菜单和空四个格子; Shift键:组合键;(可以与F1,F2,或者1,2,3等等组合打出不一样的符号) Ctrl键:控制键;(eg:ctrl+shi…

    Java 2023年6月15日
    080
  • 常见电子书获取资源途经

    比起以往,22年后对于电子书的获取渠道有了更多了解,在此记录。 先来点以前获取电子书的途经。 直接百度,能搜索得到多数电子资源,质量通常难以保证,精力、时间成本高。 各种微信公众号…

    Java 2023年6月7日
    076
  • 3.门面Slf4j+logback

    1.导入pomyil org.slf4j slf4j-api 1.7.27 ch.qos.logback logback-classic 1.2.3 2.默认使用logback.x…

    Java 2023年6月13日
    076
  • Nginx作为负载均衡服务

    一、Nginx负载均衡 1、示意图 2、好处 均摊请求,让服务端整体吞吐率提高,满足不断增长的业务需求。 相比于单点服务,当其中一台服务器挂掉后,还可以继续让剩下的服务器继续服务,…

    Java 2023年5月30日
    091
  • Docker安装mysql

    [root@VM_177_101_centos xieshuang]# docker pull mysql:5.7.18 5.7.18: Pulling from library/…

    Java 2023年6月5日
    053
  • java Builder模式

    Builder 模式也叫建造者模式,builder模式的作用将一个复杂对象的构建与他的表示分离,一步一步创建一个复杂对象的创建型模式。在不知道内部建造细节的情况下,可以更精细的控制…

    Java 2023年6月16日
    085
  • 「免费开源」基于Vue和Quasar的前端SPA项目crudapi后台管理系统实战之联合索引(十一)

    基于Vue和Quasar的前端SPA项目实战之联合索引(十一) 回顾 通过之前文章基于Vue和Quasar的前端SPA项目实战之动态表单(五)的介绍,关于表单元数据配置相关内容已经…

    Java 2023年6月6日
    072
  • 琐碎的想法(一)代码“优雅”的含义

    优雅的含义 代码优雅曾是翻译而来的,优雅这一个词语源于单词elegant。 elegant有三种含义,优美的(形容举止),精美的(形容物品),简明的。 形容代码上,应该包含了后两种…

    Java 2023年6月8日
    066
  • 在OpenCloudOS 上安装.NET 6

    开源操作系统社区 OpenCloudOS 由腾讯与合作伙伴共同倡议发起,是完全中立、全面开放、安全稳定、高性能的操作系统及生态。OpenCloudOS 沉淀了多家厂商在软件和开源生…

    Java 2023年6月5日
    075
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球