【Spring高级】Spring Boot启动过程

目录

    • SpringApplication new 分析
      • 源码分析
      • 步骤演示
        • primarySources和Sources
        • 应用类型webApplicationType
        • setInitializers设置容器初始化器
        • setListeners设置监听器
        • 主类推断
    • SpringApplication run 分析
      • 主要步骤
      • 步骤演示
        • 事件发布
        • 容器相关
        • 执行 runner
        • 准备Environment
        • EnvironmentPostProcessor后处理器
        • 环境参数与对象绑定

在我们 启动Spring Boot应用程序时,一般是下面这段代码:

SpringApplication.run(TestNewSpringApplication.class, args)

看下run方法,他是一个静态方法:

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

再进去看下里面的run方法,还是一个静态方法:

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

这个方法主要有两个步骤:

  1. 创建SpringApplication对象
  2. 调用SpringApplication对象的run方法

下面来分别分析下这两步。

SpringApplication new 分析

源码分析

下面来分析new 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));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

下面是主要步骤:

  1. this.resourceLoader = resourceLoader:resourceLoader是资源加载器,用于加载资源,如配置文件、类文件等。这里是null,ResourceLoader 的自动配置主要依赖于 Spring Boot 的自动配置机制。
  2. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)):源类this.primarySources通常是包含 Spring Boot 应用的 @SpringBootApplication 注解的类或者是定义了一些 @Configuration 的类。这些类用于定义 Spring 应用的上下文配置。primarySources 指定的类会被 Spring Boot 扫描,以查找这些BeanDefinition,也可以说是BeanDefinition源。
  3. this.webApplicationType = WebApplicationType.deduceFromClasspath()WebApplicationTypededuceFromClasspath静态方法,根据类路径推断应用的Web类型(如是否为Servlet应用、Reactive Web应用或None)。
  4. this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class)getSpringFactoriesInstances方法,从Spring的META-INF/spring.factories文件中加载所有BootstrapRegistryInitializer的实现类。BootstrapRegistryInitializer是一个用于初始化BootstrapRegistry的回调接口,在使用BootstrapRegistry之前调用它。BootstrapRegistry可以用来注册一些对象,这些对象可以在从Spring Boot启动到Spring容器初始化完成的过程中使用。简单来说,在没有Spring容器之前,可以利用BootstrapRegistry来共享一些对象。
  5. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)):从spring.factories文件中加载所有ApplicationContextInitializer的实现类,并将它们设置为ApplicationContext的初始化器。可以在创建ApplicationContextrefresh之间对ApplicationContext做一些扩展。
  6. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)):从spring.factories文件中加载所有ApplicationListener的实现类,并将它们设置为当前对象的事件监听器。
  7. this.mainApplicationClass = deduceMainApplicationClass():调用deduceMainApplicationClass方法推断主应用类,主应用类通常是包含main方法的类,用于启动Spring Boot应用。

注意,创建时,并没有去创建Spring容器,只有run时才会。

步骤演示

primarySources和Sources

这两个主要是定义了BeanDefinition源。primarySources是一开始new时,一般是启动类,主要的BeanDefinition源,也可以在Sources中自己添加。

首先看如下示例:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) {
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);

        
        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

运行报错如下:

Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.

这是因为程序根据pom文件的依赖,推断他是一个基于Servlet实现的web服务器,因此需要使用ServletWebServerApplicationContext,从而需要ServletWebServerFactory。

我们给手动加上:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) {
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);


        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

再次运行结果如下:

name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
name: org.springframework.context.event.internalEventListenerProcessor 来源:null
name: org.springframework.context.event.internalEventListenerFactory 来源:null
name: testNewSpringApplication 来源:null
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name: bean2 来源:com.cys.spring.chapter15.TestNewSpringApplication
name: servletWebServerFactory 来源:com.cys.spring.chapter15.TestNewSpringApplication

上面结果可以看到,一些内置的无法查看到来源,像bean2和servletWebServerFactory都是我们自己定义的,可以分别看到他们的来源。

也可以自己添加source,如下:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) {
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

运行结果如下:

在这里插入图片描述

应用类型webApplicationType

先直接看源码

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";


static WebApplicationType deduceFromClasspath() {
   // ClassUtils.isPresent用来判断类路径下是否含有某个类
   // 1. 如果包含WEBFLUX_INDICATOR_CLASS,并且不包含WEBMVC_INDICATOR_CLASS和JERSEY_INDICATOR_CLASS,则判断为REACTIVE类型
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
  
   // 2. 如果SERVLET_INDICATOR_CLASSES里的都不含有,则判断为NONE
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
   
   // 3.最后判断为SERVLET类型
   return WebApplicationType.SERVLET;
}

主要有以下三步:

  1. 如果包含了REACTIVE相关的包,不包含servlet相关的包,就判断为REACTIVE类型
  2. 如果还不包含javax.servlet.Servletorg.springframework.web.context.ConfigurableWebApplicationContext,则判断为NONE
  3. 否则,判断为SERVLET类型

代码演示:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("1. 演示获取 Bean Definition 源");
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});
        System.out.println("2. 演示推断应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为:"+deduceFromClasspath.invoke(null));

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

打印如下:

1. 演示获取 Bean Definition 源
2. 演示推断应用类型
	应用类型为:SERVLET
setInitializers设置容器初始化器

此步骤用来设置ApplicationContext的初始化器。ApplicationContext的初始化器主要用来在创建ApplicationContext后,refresh之前对ApplicationContext做一些扩展。

下面手动添加一个看下:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("1. 演示获取 Bean Definition 源");
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});
        System.out.println("2. 演示推断应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为:"+deduceFromClasspath.invoke(null));
        System.out.println("3. 演示 ApplicationContext 初始化器");
        spring.addInitializers(applicationContext -> {
            if (applicationContext instanceof GenericApplicationContext) {
                GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
                // 手动注册一个Bean3
                gac.registerBean("bean3", Bean3.class);
            }
        });

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

运行:

在这里插入图片描述

发现Bean3被注册进容器。

setListeners设置监听器

ApplicationListener,即事件监听器。用于监听容器中发布的事件。它是事件驱动模型开发的一部分,允许开发者在特定事件发生时执行相应的操作。

ApplicationListener监听ApplicationEvent及其子事件。通过实现ApplicationListener接口,你可以创建自定义的监听器来监听并响应特定的应用事件。例如,当使用ConfigurableApplicationContext接口中的refresh()、start()、stop()或close()方法时,会发布相应的事件(如ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent和ContextClosedEvent),你可以创建监听器来监听这些事件,并在事件发生时执行必要的操作,如初始化资源、启动服务、清理资源或关闭应用等。

示例:

package com.cys.spring.chapter15;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;


@Configuration
public class TestNewSpringApplication {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("1. 演示获取 Bean Definition 源");
        // 手动new一个SpringApplication,第一个参数就是初始的BeanDefinition源
        SpringApplication spring = new SpringApplication(TestNewSpringApplication.class);
        // 手动添加一个BeanDefinition源
        spring.setSources(new HashSet<String>() {{add("classpath:b01.xml");}});
        System.out.println("2. 演示推断应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        System.out.println("\t应用类型为:"+deduceFromClasspath.invoke(null));
        System.out.println("3. 演示 ApplicationContext 初始化器");
        spring.addInitializers(applicationContext -> {
            if (applicationContext instanceof GenericApplicationContext) {
                GenericApplicationContext gac = (GenericApplicationContext) applicationContext;
                // 手动注册一个Bean3
                gac.registerBean("bean3", Bean3.class);
            }
        });
        System.out.println("4. 演示监听器与事件");
        // 添加一个监听器监听所有事件
        spring.addListeners(event -> System.out.println("\t事件为:" + event.getClass()));

        // 调用run方法,返回一个容器
        ConfigurableApplicationContext context = spring.run(args);

        // 打印容器里的BeanDefinition
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
        }
        context.close();

    }

    static class Bean1 {

    }

    static class Bean2 {

    }

    static class Bean3 {

    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }

    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

结果打印出很多时间,如下:

1. 演示获取 Bean Definition 源
2. 演示推断应用类型
	应用类型为:SERVLET
3. 演示 ApplicationContext 初始化器
4. 演示监听器与事件
	事件为:class org.springframework.boot.context.event.ApplicationStartingEvent
	事件为:class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::               (v2.6.13)

	事件为:class org.springframework.boot.context.event.ApplicationContextInitializedEvent
2024-03-10 12:25:54.085  INFO 50655 --- [           main] c.c.s.c.TestNewSpringApplication         : Starting TestNewSpringApplication using Java 11.0.2 on testdeMBP with PID 50655 (/Users/fltech/Desktop/cys/学习笔记/sping高级-heima/sping-advanced/target/classes started by test in /Users/fltech/Desktop/cys/学习笔记/sping高级-heima/sping-advanced)
2024-03-10 12:25:54.092  INFO 50655 --- [           main] c.c.s.c.TestNewSpringApplication         : No active profile set, falling back to 1 default profile: "default"
	事件为:class org.springframework.boot.context.event.ApplicationPreparedEvent
2024-03-10 12:25:54.960  INFO 50655 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2024-03-10 12:25:54.973  INFO 50655 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-03-10 12:25:54.973  INFO 50655 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.68]
2024-03-10 12:25:55.226  INFO 50655 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-03-10 12:25:55.227  INFO 50655 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 811 ms
2024-03-10 12:25:55.313  INFO 50655 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
	事件为:class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
	事件为:class org.springframework.context.event.ContextRefreshedEvent
2024-03-10 12:25:55.331  INFO 50655 --- [           main] c.c.s.c.TestNewSpringApplication         : Started TestNewSpringApplication in 2.021 seconds (JVM running for 2.673)
	事件为:class org.springframework.boot.context.event.ApplicationStartedEvent
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
	事件为:class org.springframework.boot.context.event.ApplicationReadyEvent
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
name: org.springframework.context.event.internalEventListenerProcessor 来源:null
name: org.springframework.context.event.internalEventListenerFactory 来源:null
name: bean3 来源:null
name: testNewSpringApplication 来源:null
name: bean1 来源:class path resource [b01.xml]
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name: bean2 来源:com.cys.spring.chapter15.TestNewSpringApplication
name: servletWebServerFactory 来源:com.cys.spring.chapter15.TestNewSpringApplication
	事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
	事件为:class org.springframework.context.event.ContextClosedEvent
主类推断

主类推断即用来推断含有main方法的启动Spring Boot应用的主类。

可使用反射查看

Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
deduceMainApplicationClass.setAccessible(true);
System.out.println("\t主类是:"+deduceMainApplicationClass.invoke(spring))

SpringApplication run 分析

主要步骤

下面先看下主要步骤概览:

  1. 得到 SpringApplicationRunListeners,名字取得不好,实际是事件发布器

    • 发布 application starting 事件1️⃣
  2. 封装启动 args

  3. 准备 Environment 添加命令行参数(*)

  4. ConfigurationPropertySources 处理(*)

    • 发布 application environment 已准备事件2️⃣
  5. 通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)

    • application.properties,由 StandardConfigDataLocationResolver 解析
    • spring.application.json
  6. 绑定 spring.main 到 SpringApplication 对象(*)

  7. 打印 banner(*)

  8. 创建容器

  9. 准备容器

    • 发布 application context 已初始化事件3️⃣
  10. 加载 bean 定义

    • 发布 application prepared 事件4️⃣
  11. refresh 容器

    • 发布 application started 事件5️⃣
  12. 执行 runner

    • 发布 application ready 事件6️⃣

    • 这其中有异常,发布 application failed 事件7️⃣

带 * 的有独立的示例

下面逐一演示下步骤

步骤演示

事件发布

对应第1步

演示时,先获取到时间发布器,在Springboot中,自动配置过程中,在自动配置文件org/springframework/boot/spring-boot/2.6.13/spring-boot-2.6.13.jar!/META-INF/spring.factories中会定义实现类有哪些。

示例:

package com.cys.spring.chapter15;

import org.springframework.boot.DefaultBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;


public class TestSpringApplicationRun {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        SpringApplication app = new SpringApplication();

        /**
         * 1. 演示事件发布
         */
        // 添加时间监听器
        app.addListeners(e -> System.out.println(e.getClass()));
        // 获取事件发送器实现类名,实现类名在配置文件org/springframework/boot/spring-boot/2.6.13/spring-boot-2.6.13.jar!/META-INF/spring.factories中
        // 配置如下:org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener
        // 下面可以通过SpringFactoriesLoader.loadFactoryNames获取实现类的类名有哪些,这里就一个
        List<String> names = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, TestSpringApplicationRun.class.getClassLoader());
        for (String name : names) {
            System.out.println(name);
            // 获取类型
            Class<?> clazz = Class.forName(name);
            // 获取构造方法
            Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class);
            // 创建发布器
            SpringApplicationRunListener publisher = (SpringApplicationRunListener) constructor.newInstance(app, args);

            // 开始发布各种事件
            // 有些事件发布需要一个容器
            DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
            publisher.starting(bootstrapContext); // spring boot 开始启动
            publisher.environmentPrepared(bootstrapContext, new StandardEnvironment()); // 环境信息准备完毕
            GenericApplicationContext context = new GenericApplicationContext();
            publisher.contextPrepared(context); // 在 spring 容器创建,并调用初始化器之后,发送此事件
            publisher.contextLoaded(context); // 所有 bean definition 加载完毕
            context.refresh();
            publisher.started(context); // spring 容器初始化完成(refresh 方法调用完毕)
            publisher.running(context); // spring boot 启动完毕

            publisher.failed(context, new Exception("出错了")); // spring boot 启动出错
        }

    }
}
容器相关

对应8,9,10,11步

这里容器相关的步骤主要是:创建容器、容器增强、加载 BeanDefinition、refresh 容器。

代码如下:

package com.cys.spring.chapter15;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.*;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;

import java.util.Arrays;


public class TestSpringApplicationRun2 {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication();

        // 添加初始化器,模拟初始化增强,会在地8步后,容器创建好菜可以被调用
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("执行初始化器增强...");
            }
        });

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
        GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器");
        // 这里就模拟给容器做些增强
        for (ApplicationContextInitializer initializer : app.getInitializers()) {
            initializer.initialize(context);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
        // 先获取容器的BeanFactory
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        // 1. 创建根据注解获取BeanDefinition的读取器
        AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory);
        // 解析Config中的注解,获取BeanDefinition,并在到容器中
        reader1.register(Config.class);
        // 2. 创建根据xml获取BeanDefinition的读取器
        XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory);
        reader2.loadBeanDefinitions(new ClassPathResource("b03.xml"));
        // 23. 扫描获取BeanDefinition的读取器
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
        scanner.scan("com.cys.spring.chapter15.beans");


        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
        }
    }

    /**
     * 辅助函数:根据类型参数获取不同的容器类型
     *
     * @param type
     * @return
     */
    private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
        GenericApplicationContext context = null;
        switch (type) {
            case SERVLET : context = new AnnotationConfigServletWebServerApplicationContext();
            break;
            case REACTIVE : context = new AnnotationConfigReactiveWebServerApplicationContext();
            break;
            case NONE : context = new AnnotationConfigApplicationContext();
            break;
        }
        return context;
    }

    static class Bean4 {

    }

    static class Bean5 {

    }

    static class Bean6 {

    }

    @Configuration
    static class Config {
        @Bean
        public Bean5 bean5() {
            return new Bean5();
        }

        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }
    }
}

运行结果,打印如下:

>>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器
>>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器
执行初始化器增强...
>>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义
>>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器
name:org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
name:org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
name:org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
name:org.springframework.context.event.internalEventListenerProcessor 来源:null
name:org.springframework.context.event.internalEventListenerFactory 来源:null
name:testSpringApplicationRun2.Config 来源:null
name:bean4 来源:class path resource [b03.xml]
name:bean7 来源:file [.../classes/com/cys/spring/chapter15/beans/Bean7.class]
name:org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name:bean5 来源:com.cys.spring.chapter15.TestSpringApplicationRun2$Config
name:servletWebServerFactory 来源:com.cys.spring.chapter15.TestSpringApplicationRun2$Config
执行 runner

对应地2,12步

在Spring Boot中,CommandLineRunnerApplicationRunner是两个常用的接口,它们允许你在Spring Boot应用启动后立即执行一些自定义的代码。这些接口特别有用,当你需要在应用启动之后立即进行一些初始化工作,比如数据加载、缓存预热、检查配置等。

下面来看下如何主动调用,示例:

package com.cys.spring.chapter15;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.*;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;

import java.util.Arrays;


public class TestSpringApplicationRun2 {

    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication();

        // 添加初始化器,模拟初始化增强,会在地8步后,容器创建好菜可以被调用
        app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("执行初始化器增强...");
            }
        });

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
        // 执行runner使用
        DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
        GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器");
        // 这里就模拟给容器做些增强
        for (ApplicationContextInitializer initializer : app.getInitializers()) {
            initializer.initialize(context);
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义");
        // 先获取容器的BeanFactory
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        // 1. 创建根据注解获取BeanDefinition的读取器
        AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory);
        // 解析Config中的注解,获取BeanDefinition,并在到容器中
        reader1.register(Config.class);
        // 2. 创建根据xml获取BeanDefinition的读取器
        XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory);
        reader2.loadBeanDefinitions(new ClassPathResource("b03.xml"));
        // 23. 扫描获取BeanDefinition的读取器
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
        scanner.scan("com.cys.spring.chapter15.beans");


        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器");
        context.refresh();

        for (String name : context.getBeanDefinitionNames()) {
            System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
        }

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner");
        for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
            runner.run(args);
        }

        for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
            runner.run(arguments);
        }
    }

    /**
     * 辅助函数:根据类型参数获取不同的容器类型
     *
     * @param type
     * @return
     */
    private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
        GenericApplicationContext context = null;
        switch (type) {
            case SERVLET : context = new AnnotationConfigServletWebServerApplicationContext();
            break;
            case REACTIVE : context = new AnnotationConfigReactiveWebServerApplicationContext();
            break;
            case NONE : context = new AnnotationConfigApplicationContext();
            break;
        }
        return context;
    }

    static class Bean4 {

    }

    static class Bean5 {

    }

    static class Bean6 {

    }

    @Configuration
    static class Config {
        @Bean
        public Bean5 bean5() {
            return new Bean5();
        }

        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

        @Bean
        public CommandLineRunner commandLineRunner() {
            return new CommandLineRunner() {
                @Override
                public void run(String... args) throws Exception {
                    System.out.println("commandLineRunner()..." + Arrays.toString(args));
                }
            };
        }

        @Bean
        public ApplicationRunner applicationRunner() {
            return new ApplicationRunner() {
                @Override
                public void run(ApplicationArguments args) throws Exception {
                    // 可以拿到Application里的各种参数
                    System.out.println("applicationRunner()..." + Arrays.toString(args.getSourceArgs()));
                    System.out.println(args.getOptionNames());
                    System.out.println(args.getOptionValues("server.port"));
                    System.out.println(args.getNonOptionArgs());
                }
            };
        }
    }
}

运行打印结果如下:

>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner
commandLineRunner()...[]
applicationRunner()...[]
[]
null
[]
准备Environment

对应第3步

这里以Springboot中的ApplicationEnvironment为例。

ApplicationEnvironment是一个用于访问应用环境信息的接口。它通常用于读取应用的环境属性,例如配置文件中的属性、命令行参数、系统属性等。ApplicationEnvironment继承自Spring框架的Environment接口,并添加了Spring Boot特有的功能。

其类图如下:

在这里插入图片描述

ApplicationEnvironment的主要用途包括:

  1. 读取属性:通过getProperty方法读取属性值。这些属性可能来自application.propertiesapplication.yml配置文件、命令行参数、环境变量或系统属性等。
  2. 属性源管理ApplicationEnvironment管理着多个属性源(Property Sources),每个属性源都包含一组键值对。Spring Boot按照特定的顺序(例如,命令行参数优先级最高,然后是配置文件等)来解析属性值。
  3. 自定义属性源:你可以向ApplicationEnvironment添加自定义的属性源,以便在应用启动时加载特定的配置。
  4. 激活的配置文件:Spring Boot支持配置文件的激活,例如application-dev.properties用于开发环境,application-prod.properties用于生产环境。ApplicationEnvironment提供了方法来获取当前激活的配置文件。
  5. 环境检查:通过Environment接口的方法,你可以检查应用是否处于特定环境(如测试环境或生产环境),这对于条件性地配置应用行为非常有用。

在Spring Boot应用中,你通常不需要直接实例化ApplicationEnvironment,因为Spring Boot会自动配置并注入它。你可以在你的组件中通过依赖注入来获取并使用它。

由于这个类在org.springframework.boot中,并不对外开放,需要在内部测试:

package org.springframework.boot;

import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePropertySource;

import java.io.IOException;

public class Step3 {
    public static void main(String[] args) throws IOException {
        ApplicationEnvironment env = new ApplicationEnvironment(); // 系统环境变量, properties, yaml
        // 加一个配置源到最后一位
        env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step3.properties")));
        // 加油一个配置源到第一位
        env.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
        System.out.println(env.getProperty("JAVA_HOME"));

        System.out.println(env.getProperty("server.port"));
    }
}
EnvironmentPostProcessor后处理器

对应第5步。

EnvironmentPostProcessor主要功能是在Spring Boot环境加载后,动态加载其他配置文件中的一些信息到环境变量里面。通过这个接口,我们可以添加或修改环境配置,实现多个微服务共同配置的修改与维护。

当项目需要在不同环境(如开发、测试、生产等)中运行时,每套环境可能有专属的配置文件。EnvironmentPostProcessor允许我们在项目启动时读取环境变量或启动命令参数,从而获取特定环境下的配置信息(如服务地址、命名空间名称、分组名称等)。然后,根据这些配置参数或环境变量,我们可以读取不同的配置文件,实现不同环境使用不同的配置文件,而无需修改代码或本地配置文件。

使用EnvironmentPostProcessor时,我们需要实现该接口,并覆盖其postProcessEnvironment方法。在这个方法中,我们可以加载对应的配置文件到环境变量中。同时,还需要在spring.factories文件中进行相应配置,以便Spring Boot在启动时能够识别并加载我们的EnvironmentPostProcessor实现类。

在Springboot中通过ApplicationListener监听器EnvironmentPostProcessorApplicationListener,来监听并执行下面那些后处理器,如下图:

在这里插入图片描述

示例:

public class Step5 {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication();
        app.addListeners(new EnvironmentPostProcessorApplicationListener());

        /*List<String> names = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, Step5.class.getClassLoader());
        for (String name : names) {
            System.out.println(name);
        }*/

        EventPublishingRunListener publisher = new EventPublishingRunListener(app, args);
        ApplicationEnvironment env = new ApplicationEnvironment();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强前");
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }
        publisher.environmentPrepared(new DefaultBootstrapContext(), env);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> 增强后");
        for (PropertySource<?> ps : env.getPropertySources()) {
            System.out.println(ps);
        }

    }
}
环境参数与对象绑定

底层通过Binder来绑定:

示例:

package org.springframework.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePropertySource;

import java.io.IOException;

public class Step6 {
    // 绑定 spring.main 前缀的 key value 至 SpringApplication, 请通过 debug 查看
    public static void main(String[] args) throws IOException {
        SpringApplication application = new SpringApplication();
        ApplicationEnvironment env = new ApplicationEnvironment();
      	// 把step4.properties配置源加到环境
        env.getPropertySources().addLast(new ResourcePropertySource("step4", new ClassPathResource("step4.properties")));
        // 把step6.properties配置源加到环境
        env.getPropertySources().addLast(new ResourcePropertySource("step6", new ClassPathResource("step6.properties")));

//        User user = Binder.get(env).bind("user", User.class).get();
//        System.out.println(user);

//        User user = new User();
//        Binder.get(env).bind("user", Bindable.ofInstance(user));
//        System.out.println(user);

        System.out.println(application);
        Binder.get(env).bind("spring.main", Bindable.ofInstance(application));
        System.out.println(application);
    }

    static class User {
        private String firstName;
        private String middleName;
        private String lastName;
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getMiddleName() {
            return middleName;
        }
        public void setMiddleName(String middleName) {
            this.middleName = middleName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
        @Override
        public String toString() {
            return "User{" +
                   "firstName='" + firstName + '\'' +
                   ", middleName='" + middleName + '\'' +
                   ", lastName='" + lastName + '\'' +
                   '}';
        }
    }
}

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

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

相关文章

【MATLAB源码-第45期】基于matlab的16APSK调制解调仿真,使用卷积编码软判决。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 16APSK调制解调 16APSK (16-ary Amplitude Phase Shift Keying) 是一种相位调制技术&#xff0c;其基本思想是在恒定幅度的条件下&#xff0c;改变信号的相位&#xff0c;从而传送信息。 - 调制&#xff1a;在16APSK中&am…

Github 2024-04-12 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-04-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目6TypeScript项目2Cuda项目1C++项目1C项目1HTML项目1Jupyter Notebook项目1JavaScript项目1Python - 100天从新手到大师 创建周期:22…

书生·浦语大模型实战营 | 第3次学习笔记

前言 书生浦语大模型应用实战营 第二期正在开营&#xff0c;欢迎大家来学习。&#xff08;参与链接&#xff1a;https://mp.weixin.qq.com/s/YYSr3re6IduLJCAh-jgZqg 第三堂课的视频链接&#xff1a;https://www.bilibili.com/video/BV1QA4m1F7t4/ 本次笔记是学习完第三堂课…

视频秒播优化实践

本文字数&#xff1a;2259字 预计阅读时间&#xff1a;10分钟 视频起播时间&#xff0c;即首帧时间&#xff0c;是视频类应用的一个重要核心指标&#xff0c;也是影响用户观看体验的核心因素之一。如果视频要加载很久才能开始播放&#xff0c;用户放弃播放甚至离开 App 的概率都…

影响小程序SSL证书收费标准的因素有哪些?

在当今互联网时代&#xff0c;移动应用发展日新月异&#xff0c;小程序逐渐成为广大企业和个人开发者的心仪之选。然而&#xff0c;伴随小程序的广泛应用&#xff0c;安全问题和用户信任显得尤为关键。为了确保小程序的信息传输安全&#xff0c;SSL证书成为了一项基础配置。那么…

探索Web3的奇迹:数字时代的新前景

在数字化时代的潮流中&#xff0c;我们不可避免地迎来了一个全新的篇章——Web3时代的到来。在这个时代中&#xff0c;区块链技术作为数字化世界的核心&#xff0c;正在重塑着我们的生活方式、经济模式以及社会结构。在Web3时代&#xff0c;我们将目睹着一个以去中心化、透明化…

MS7336MA高清 HD/全高清 FHD 可选择视频运放与视频同轴线控解码

产品简述 MS7336MA 是一颗集成单通道视频放大器与视频同轴线控解 码为一体的芯片&#xff0c;它内部集成 6dB 增益轨到轨输出驱动器以及 10 阶滤波器&#xff0c;允许同一个输入信号在 -3dB 带宽 35MHz 和 55MHz 之间进行选择控制。视频同轴线控解码内部集成一颗高…

原型模式:复制对象的智能解决方案

在软件开发过程中&#xff0c;对象的创建可能是一个昂贵的操作&#xff0c;特别是当对象的初始化包括从数据库加载数据、进行IO操作或进行复杂计算时。原型模式是一种创建型设计模式&#xff0c;它通过复制现有的实例来创建新的对象实例&#xff0c;从而避免了类初始化时的高成…

QQ 邮箱使用 SMTP 发送邮件报错:550 The From header is missing or invalid

文章目录 场景描述问题排查根据提示查看原因查看封装的 message 个人简介 场景描述 QQ 邮箱使用 SMTP 发送邮件报错&#xff1a;550 The From header is missing or invalid&#xff1a; 失败原因&#xff1a;(550, bThe "From" header is missing or invalid. Ple…

6. Bean 的作用域和生命周期

Spring Framework的主要功能是用来存储和读取Bean&#xff0c;Bean于Spring框架的地位可见一斑。本节的目的就是从作用域和生命周期的角度更加深入了解一下Bean对象。 1. Bean的作用域 首先我们来学习一下Bean的作用域&#xff0c;源码位置&#xff1a;bean_scope 1.1 作用域…

iMazing如何备份手机资料 iPhone的资料可以传到iPad里吗 iphone备份到mac 苹果导入备份

在当今信息化快速发展的时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。随着资料的积累&#xff0c;备份手机数据成了一个重要的问题。本文将介绍iMazing如何备份手机资料&#xff0c;并为大家解答“iPhone的资料可以传到iPad里吗”这一问题。这不仅可以帮助你有效管…

mybaits(8)-缓存机制

缓存机制 1、mybatis缓存2、一级缓存2.1 开启一级缓存2.2 一级缓存失效 3、二级缓存3.1 开启二级缓存3.2 二级缓存什么时候失效3.3 二级缓存的相关配置 4、MyBatis集成EhCache 1、mybatis缓存 缓存&#xff1a;cache 缓存的作用&#xff1a;通过减少IO的方式&#xff0c;来提高…

最齐全,最简单的免费SSL证书获取方法——实现HTTPS访问

一&#xff1a;阿里云 优势&#xff1a;大平台&#xff0c;在站长中知名度最高&#xff0c;提供20张免费单域名SSL证书 缺点&#xff1a;数量有限&#xff0c;并且只有单域名证书&#xff0c;通配符以及多域名没有免费版本。并且提供的单域名证书只有三个月的期限。 二&#…

v1.9.2-httpsok快速申请免费SSL证书

v1.9.2-&#x1f525;httpsok快速申请免费SSL证书 介绍 httpsok 是一个便捷的 HTTPS 证书自动续签工具&#xff0c;专为 Nginx 、OpenResty 服务器设计。已服务众多中小企业&#xff0c;稳定、安全、可靠。 一行命令&#xff0c;一分钟轻松搞定SSL证书自动续期 更新日志 V1…

【vue】购物车案例优化

对 购物车案例 进行优化 用watch实现全选/取消全选用watch实现全选状态的检查用computed计算总价格 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-w…

Zookeeper的集群搭建和ZAB协议详解

Zookeeper的集群搭建 1&#xff09;zk集群中的角色 Zookeeper集群中的节点有三个角色&#xff1a; Leader&#xff1a;处理集群的所有事务请求&#xff0c;集群中只有一个LeaderFollwoer&#xff1a;只能处理读请求&#xff0c;参与Leader选举Observer&#xff1a;只能处理读…

HashMap部分底层源码解析

哈希表的物理结构 HashMap底层都是哈希表&#xff08;也称散列表&#xff09;&#xff0c;线程不安全&#xff0c;其中维护了一个长度为2的幂次方的Entry类型的数组table&#xff0c;数组的每一个索引位置被称为一个桶(bucket)&#xff0c;你添加的映射关系(key,value)最终都被…

clickhouse深入浅出

基础知识原理 极致压缩率 极速查询性能 列式数据库管理 &#xff0c;读请求多 大批次更新或无更新 读很多但用很少 大量的列 列的值小数值/短字符串 一致性要求低 DBMS&#xff1a;动态创建/修改/删除库 表 视图&#xff0c;动态查/增/修/删&#xff0c;用户粒度设库…

Go——Goroutine介绍

一. 并发介绍 进程和线程 进程是程序在操作系统中一次执行过程&#xff0c;系统进程资源分配和调度的一个独立单位。线程是进程执行的实体&#xff0c;是CPU调度和分派的基本单位&#xff0c;它是比进程更小的能独立运行的基本单位。一个进程可以创建和撤销多个线程&#xff0c…

sysbench MySQL性能测试

目录 1. QPS&&TPS 1.1 数据库启动到现在的运行时间(秒) 1.2 查询量 1.3 status命令直接显示出QPS 1.4 每秒输出数据库状态(累加) 2. sysbench 测试工具 3. OLTP MySQL测试 3.1 普通参数 3.2 支持的lua脚本 3.3 脚本参数 3.4 测试数据准备 3.5 进行测试 3.…