• 「SpringBoot」09 原理解析


    SpringBoot——原理解析

    笔记整理自【尚硅谷】雷神SpringBoot2视频教程

    Spring原理、SpringMVC原理、自动配置原理、SpringBoot原理。

    1. SpringBoot启动过程

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
       return new SpringApplication(primarySources).run(args);
    }             // ↑ 创建SpringApplication         ↑ 运行SpringApplication
    
    • 1
    • 2
    • 3

    组件用黄色的高光标识

    组件介入的各个时机的调用用青蓝色背景标识(只在 运行 SpringApplication中有)
    1️⃣ 创建 SpringApplication
    • 保存一些信息。

    • 判定当前应用的类型 => ClassUtils:Servlet

      WebApplicationType类

      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; // 返回SERVLET编程类型
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • bootstrappers初始启动引导器(List):去spring.factories文件中找org.springframework.boot.Bootstrapper

    • ApplicationContextInitializer(初始化器);去spring.factoriesApplicationContextInitializer

      List<ApplicationContextInitializer<?>> initializers
      
      • 1

      image-20220727211833347

    • ApplicationListener (应用监听器);去spring.factoriesApplicationListener

      List<ApplicationListener<?>> listeners
      
      • 1

      image-20220727212256954

    • 创建SpringApplication 完整代码

      // 构造方法
      public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
          this.resourceLoader = resourceLoader;
          Assert.notNull(primarySources, "PrimarySources must not be null");
          this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
          // WebApplicationType是枚举类 有NONE,SERVLET,REACTIVE 下行webApplicationType是SERVLET
          this.webApplicationType = WebApplicationType.deduceFromClasspath();
          // 初始启动引导器 去spring.factories文件中找org.springframework.boot.Bootstrapper 但我找不到实现Bootstrapper接口的类
          this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
          // 去spring.factories找 ApplicationContextInitializer
          setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
          // 去spring.factories找 ApplicationListener
          setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
          this.mainApplicationClass = deduceMainApplicationClass(); // 决定哪个类是我们的主程序
      }                               //   ↓
      private Class<?> deduceMainApplicationClass() {
      	try {
      		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
      		for (StackTraceElement stackTraceElement : stackTrace) {
                  // 哪个类有main方法 被找到的第1个类就是主程序类
      			if ("main".equals(stackTraceElement.getMethodName())) { 
      				return Class.forName(stackTraceElement.getClassName());
      			}
      		}		
          }
      	catch (ClassNotFoundException ex) {
      		// Swallow and continue
      	}
      	return null;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

    简单来说,应用创建的过程就是把一些关键的组件信息读取并保存到SpringApplication中,作为运行SpringApplication的前置工作。

    2️⃣ 运行 SpringApplication
    • StopWatch

    • 记录应用的启动时间

    • 创建引导上下文(Context环境)createBootstrapContext()

      ➢ 获取到所有之前的 bootstrappers 挨个执行 intitialize()方法 来完成对引导启动器上下文环境设置
      public interface Bootstrapper {
      
      	/**
      	 * Initialize the given {@link BootstrapRegistry} with any required registrations.
      	 * @param registry the registry to initialize
      	 */
      	void intitialize(BootstrapRegistry registry);
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 让当前应用进入headless模式(自力更生模式) java.awt.headless

    • 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】

      getSpringFactoriesInstances()spring.factoriesSpringApplicationRunListener

      image-20220728105312800

      image-20220728105552786

    • 遍历 SpringApplicationRunListener 调用 starting() 方法;

      相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。

    • 保存命令行参数:ApplicationArguments

    • 准备环境 prepareEnvironment()

      ➢ 返回或者创建基础环境信息对象:StandardServletEnvironment

      ➢ 配置环境信息对象。

      ​ ◽️ 读取所有的配置源的配置属性值。

      ➢ 绑定环境信息

      ➢ 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
    • 创建IOC容器(createApplicationContext())

      ➢ 根据项目类型(Servlet)创建容器

      ➢ 当前会创建 AnnotationConfigServletWebServerApplicationContext

    • 准备ApplicationContext IOC容器的基本信息 prepareContext()

      ➢ 保存环境信息

      ➢ IOC容器的后置处理流程

      ➢ 应用初始化器:applyInitializers

      ◽️ 遍历所有的 ApplicationContextInitializer,调用 initialize()。来对IOC容器进行初始化扩展功能

      image-20220728112147776

      ◽️ 遍历所有的 listener 调用 contextPrepared() => EventPublishRunListenr;通知所有的监听器 contextPrepared:IOC上下文环境已经准备好了

      image-20220728112657577

      ➢ 所有的监听器 调用 contextLoaded();通知所有的监听器 contextLoaded:IOC容器已经加载完成
    • 刷新IOC容器:refreshContext()

      ➢ 创建容器中的所有组件

      // Instantiate all remaining (non-lazy-init) singletons. 使用单例模式 实例化所有剩余的(非延迟初始化)组件。
      finishBeanFactoryInitialization(beanFactory);
      
      • 1
      • 2
    • 容器刷新完成后工作 afterRefresh()

    • 所有监听器 调用 listeners.started(context); 通知所有的监听器started
    • 调用所有runners;callRunners()

      获取容器中的 ApplicationRunner

      @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;
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      获取容器中的 CommandLineRunner

      @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;
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      合并所有runner并且按照@Order进行排序

      ➢ 遍历所有的runner,调用 run() 方法
    • 如果以上有异常,

      ➢ 调用Listener 的 failed() 方法
    • 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
    • running如果有问题,继续通知 failed,调用所有 Listener 的 failed;通知所有的监听器 failed
    • 最终返回IOC容器 return context

    • 运行SpringApplication 完整代码

      // run()方法
      public ConfigurableApplicationContext run(String... args) {
          StopWatch stopWatch = new StopWatch();//开始计时器
          stopWatch.start();//开始计时
      
          // 1.
          // 创建引导上下文(Context环境)createBootstrapContext()
          // 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
          DefaultBootstrapContext bootstrapContext = createBootstrapContext();
      
          // 2.到最后该方法会返回这context
          ConfigurableApplicationContext context = null;
      
          // 3.让当前应用进入headless模式
          configureHeadlessProperty();
      
          // 4.获取所有 RunListener(运行监听器)为了方便所有Listener进行事件感知
          SpringApplicationRunListeners listeners = getRunListeners(args);
      
          // 5. 遍历 SpringApplicationRunListener 调用starting方法
          // 相当于通知所有感兴趣系统正在启动过程的人 项目正在starting
          listeners.starting(bootstrapContext, this.mainApplicationClass);
          try {
              // 6.保存命令行参数 ApplicationArguments
              ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      
              // 7.准备环境
              ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
              configureIgnoreBeanInfo(environment);
      
              /*
              打印标志
                .   ____          _            __ _ _
               /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
              ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
               \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
                '  |____| .__|_| |_|_| |_\__, | / / / /
               =========|_|==============|___/=/_/_/_/
               :: Spring Boot ::                (v2.4.2)
              */
              Banner printedBanner = printBanner(environment);
      
              // 创建IOC容器(createApplicationContext())
              // 根据项目类型webApplicationType(NONE,SERVLET,REACTIVE)创建容器,
              // 当前会创建 AnnotationConfigServletWebServerApplicationContext
              context = createApplicationContext();
              context.setApplicationStartup(this.applicationStartup);
      
              // 8.准备ApplicationContext IOC容器的基本信息
              prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
              // 9.刷新IOC容器 创建容器中的所有组件 Spring框架的内容
              refreshContext(context);
              // 该方法没内容,大概为将来填入
              afterRefresh(context, applicationArguments);
              stopWatch.stop(); // 停止计时
              if (this.logStartupInfo) { // this.logStartupInfo默认是true
                  new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
              }
              // 10.
              listeners.started(context);
      
              // 11.调用所有runners
              callRunners(context, applicationArguments);
          }
          catch (Throwable ex) {
              // 13.
              handleRunFailure(context, ex, listeners);
              throw new IllegalStateException(ex);
          }
      
          try {
              // 12.
              listeners.running(context);
          }
          catch (Throwable ex) {
              // 13.
              handleRunFailure(context, ex, null);
              throw new IllegalStateException(ex);
          }
          return context;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      // 1. 
      private DefaultBootstrapContext createBootstrapContext() {
          DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
          this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext));
          return bootstrapContext;
      }
      
      // 3.
      private void configureHeadlessProperty() {
          // this.headless默认为true
          System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
                  System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
      }
      
      private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
      
      // 4.
      private SpringApplicationRunListeners getRunListeners(String[] args) {
          Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
          //getSpringFactoriesInstances 去 spring.factories 找 SpringApplicationRunListener
          return new SpringApplicationRunListeners(logger,
                  getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
                  this.applicationStartup);
      }
      
      // 7.准备环境
      private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                         DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
          // Create and configure the environment
          // 返回或者创建基础环境信息对象 如:StandardServletEnvironment, StandardReactiveWebEnvironment
          ConfigurableEnvironment environment = getOrCreateEnvironment();
          // 配置环境信息对象,读取所有的配置源的配置属性值。
          configureEnvironment(environment, applicationArguments.getSourceArgs());
          // 绑定环境信息
          ConfigurationPropertySources.attach(environment);
          // 7.1 通知所有的监听器当前环境准备完成
          listeners.environmentPrepared(bootstrapContext, environment);
          DefaultPropertiesPropertySource.moveToEnd(environment);
          configureAdditionalProfiles(environment);
          bindToSpringApplication(environment);
          if (!this.isCustomEnvironment) {
              environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                      deduceEnvironmentClass());
          }
          ConfigurationPropertySources.attach(environment);
          return environment;
      }
      
      // 8.
      private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
                                  ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                                  ApplicationArguments applicationArguments, Banner printedBanner) {
          // 保存环境信息
          context.setEnvironment(environment);
          // IOC容器的后置处理流程
          postProcessApplicationContext(context);
          // 应用初始化器
          applyInitializers(context);
          // 8.1 遍历所有的 listener 调用 contextPrepared
          // EventPublishRunListenr通知所有的监听器contextPrepared
          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) {
              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]));
          // 8.2
          listeners.contextLoaded(context);
      }
      
      // 11.调用所有runners
      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());
          // 合并所有runner并且按照@Order进行排序
          AnnotationAwareOrderComparator.sort(runners);
          // 遍历所有的runner 调用 run() 方法
          for (Object runner : new LinkedHashSet<>(runners)) {
              if (runner instanceof ApplicationRunner) {
                  callRunner((ApplicationRunner) runner, args);
              }
              if (runner instanceof CommandLineRunner) {
                  callRunner((CommandLineRunner) runner, args);
              }
          }
      }
      
      // 13.
      private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
                                    SpringApplicationRunListeners listeners) {
          try {
              try {
                  handleExitCode(context, exception);
                  if (listeners != null) {
                      //14.
                      listeners.failed(context, exception);
                  }
              }
              finally {
                  reportFailure(getExceptionReporters(context), exception);
                  if (context != null) {
                      context.close();
                  }
              }
          }
          catch (Exception ex) {
              logger.warn("Unable to close ApplicationContext", ex);
          }
          ReflectionUtils.rethrowRuntimeException(exception);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109
      • 110
      • 111
      • 112
      • 113
      • 114
      • 115
      • 116
      • 117
      • 118
      • 119
      • 120
      • 121
      • 122
      • 123
      • 124
      • 125
      • 126
      • 127
      • 128
      • 129
      • 130
      • 131

    Application Events and Listeners

    ApplicationRunner 与CommandLineRunner

    2. 自定义事件监听组件

    自定义上方高光的五个组件

    • MyApplicationContextInitializer

      import org.springframework.context.ApplicationContextInitializer;
      import org.springframework.context.ConfigurableApplicationContext;
      
      public class MyApplicationContextInitializer implements ApplicationContextInitializer {
          @Override
          public void initialize(ConfigurableApplicationContext applicationContext) {
              System.out.println("MyApplicationContextInitializer ....initialize.... ");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • MyApplicationListener

      import org.springframework.context.ApplicationEvent;
      import org.springframework.context.ApplicationListener;
      
      public class MyApplicationListener implements ApplicationListener {
          @Override
          public void onApplicationEvent(ApplicationEvent event) {
              System.out.println("MyApplicationListener.....onApplicationEvent...");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • MySpringApplicationRunListener

      import org.springframework.boot.ConfigurableBootstrapContext;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.SpringApplicationRunListener;
      import org.springframework.context.ConfigurableApplicationContext;
      import org.springframework.core.env.ConfigurableEnvironment;
      
      public class MySpringApplicationRunListener implements SpringApplicationRunListener {
      
          private SpringApplication application;
          
          public MySpringApplicationRunListener(SpringApplication application, String[] args){
              this.application = application;
          }
      
          @Override
          public void starting(ConfigurableBootstrapContext bootstrapContext) {
              System.out.println("MySpringApplicationRunListener....starting...."); 
          }
      
          @Override
          public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
              System.out.println("MySpringApplicationRunListener....environmentPrepared....");
          }
      
          @Override
          public void contextPrepared(ConfigurableApplicationContext context) {
              System.out.println("MySpringApplicationRunListener....contextPrepared....");
          }
      
          @Override
          public void contextLoaded(ConfigurableApplicationContext context) {
              System.out.println("MySpringApplicationRunListener....contextLoaded....");
          }
      
          @Override
          public void started(ConfigurableApplicationContext context) {
              System.out.println("MySpringApplicationRunListener....started....");
          }
      
          @Override
          public void running(ConfigurableApplicationContext context) {
              System.out.println("MySpringApplicationRunListener....running....");
          }
      
          @Override
          public void failed(ConfigurableApplicationContext context, Throwable exception) {
              System.out.println("MySpringApplicationRunListener....failed....");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
    • MyApplicationRunner

      import org.springframework.boot.ApplicationArguments;
      import org.springframework.boot.ApplicationRunner;
      import org.springframework.core.annotation.Order;
      import org.springframework.stereotype.Component;
      
      @Order(1) // 按照Order排序 数字越大优先级越高
      @Component // 放入容器
      public class MyApplicationRunner implements ApplicationRunner {
          @Override
          public void run(ApplicationArguments args) throws Exception {
              System.out.println("MyApplicationRunner...run...");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • MyCommandLineRunner

      import org.springframework.boot.CommandLineRunner;
      import org.springframework.core.annotation.Order;
      import org.springframework.stereotype.Component;
      /**
       * 应用启动做一个一次性事情
       */
      @Order(2) // 按照Order排序
      @Component // 放入容器
      public class MyCommandLineRunner implements CommandLineRunner {
          @Override
          public void run(String... args) throws Exception {
              System.out.println("MyCommandLineRunner....run....");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 注册MyApplicationContextInitializerMyApplicationListenerMySpringApplicationRunListener

      resources/META-INF/spring.factories

      org.springframework.context.ApplicationContextInitializer=\
        com.atguigu.boot.listener.MyApplicationContextInitializer
      
      org.springframework.context.ApplicationListener=\
        com.atguigu.boot.listener.MyApplicationListener
      
      org.springframework.boot.SpringApplicationRunListener=\
        com.atguigu.boot.listener.MySpringApplicationRunListener
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      image-20220728191459394

    完结撒花✿✿ヽ(°▽°)ノ✿ 感谢雷神,后会有期!

  • 相关阅读:
    浅聊一下Nginx
    golang笔记 mutex,抢占式调度,semaphore
    使用sql profile 稳定执行计划的案例
    数据结构学习笔记
    Hystrix熔断器整合 - 服务熔断和服务降级
    餐厅订位短信解决方案
    C++实现排序 - 01 冒泡、选择、插入和希尔排序
    SpringBoot整合MyBatis从零开始
    FastAPI 学习之路(十三)Cookie 参数,Header参数
    Java集合框架【一泛型(Generics)】
  • 原文地址:https://blog.csdn.net/weixin_53407527/article/details/126042145