Spring Boot——Spring Boot启动原理

news2024/11/16 15:39:00

系列文章目录


Spring Boot启动原理

  • 系列文章目录
  • 一、Spring Boot启动的宏观流程图
  • 二、Spring Boot启动流程
    • 2.1 初始化new SpringApplication
      • 2.1.1Spring Boot入口
      • 2.1.2初始化SpringApplication
        • 2.1.2.1判断当前应用程序类型
        • 2.1.2.2设置应用程序的所有初始化器(initializers)
        • 2.1.2.3设置应用程序的所有监听器(listeners)
        • 2.1.2.4设置应用程序运行主类
    • 2.2执行run方法
      • 2.2.1开启计时器
      • 2.2.2初始化应用上下文和初始化异常报告
      • 2.2.3设置Headless属性
      • 2.2.4获取所有注册的listeners并发布ApplicationStartingEvent事件。
      • 2.2.5获取并解析应用程序在命令行上传入的参数
      • 2.2.6读取环境配置信息
      • 2.2.7设置忽略BeanInfo
      • 2.2.8.打印Banner
      • 2.2.9创建上下文createApplicationContext
      • 2.2.10.获取所有异常报告器
      • 2.2.11准备上下文prepareContext
      • 2.2.12.刷新上下文refreshContext
      • 2.2.13上下文后置处理
      • 2.2.14停止计时器
      • 2.2.15.打印应用程序启动信息
      • 2.2.16 通知监听器,应用程序正在运行
      • 2.2.17执行所有Runner
      • 2.2.18.返回上下文对象context


一、Spring Boot启动的宏观流程图

在这里插入图片描述

二、Spring Boot启动流程

2.1 初始化new SpringApplication

2.1.1Spring Boot入口

@SpringBootApplication
public class SampleWebJspApplication extends SpringBootServletInitializer {
	public static void main(String[] args) {
		SpringApplication.run(SampleWebJspApplication.class, args);
	}
}

2.1.2初始化SpringApplication

准备阶段,在程序运行之前初始化一些属性,用于在后序启动应用程序过程中。

//创建 SpringApplication 实例时,初始化各种重要的属性,包括应用程序的主要来源、Web 应用类型、初始化器和监听器等
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//根据类路径来推断 Spring Boot 应用程序的 Web 应用类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//获取所有实现了 ApplicationContextInitializer 接口的实例,并将它们设置为应用程序上下文的初始化器(initializers)。
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		//获取所有实现了 ApplicationListener 接口的实例,并将它们设置为应用程序的监听器(listeners)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//4.设置应用程序运行主类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

2.1.2.1判断当前应用程序类型

//根据类路径来推断 Spring Boot 应用程序的 Web 应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();

//根据类路径中存在的特定类来推断 Spring Boot 应用程序的 Web 应用类型
static WebApplicationType deduceFromClasspath() {
		//检查类路径中是否存在 WEBFLUX_INDICATOR_CLASS 并且不存在 WEBMVC_INDICATOR_CLASS 和 JERSEY_INDICATOR_CLASS。
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			//即响应式 Web 应用程序
			return WebApplicationType.REACTIVE;
		}
		//检查类路径中是否存在当前遍历的类名。
		for (String className : SERVLET_INDICATOR_CLASSES) {
			if (!ClassUtils.isPresent(className, null)) {
				//表示非 Web 应用程序。
				return WebApplicationType.NONE;
			}
		}
		return WebApplicationType.SERVLET;
	}

2.1.2.2设置应用程序的所有初始化器(initializers)

//获取所有实现了 ApplicationContextInitializer 接口的实例,并将它们设置为应用程序上下文的初始化器(initializers)。
setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

//通过加载 "spring.factories" 配置文件中指定类型的工厂名称,并创建对应的工厂实例,然后根据工厂实例的排序顺序返回一个排序后的工厂实例的集合。
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		// 从 "spring.factories" 配置文件中加载指定类型的工厂名称,并将它们放入一个有序的集合中
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//通过工厂名称创建指定类型的工厂实例,并将它们放入一个列表中。
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		//对工厂实例列表进行排序。
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}			

上面这段代码主要是通过加载 “spring.factories” 配置文件中指定类型的工厂名称,并创建对应的工厂实例,然后根据工厂实例的排序顺序返回一个排序后的工厂实例的集合。关键方法是SpringFactoriesLoader.loadFactoryNames()方法。

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        //只会返回key为org.springframework.context.ApplicationContextInitializer的value
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                MultiValueMap<String, String> result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

loadFactoryNames()该方法会从类路径的 META-INF/spring.factories 读取相应配置文件,读取配置文件中key为org.springframework.context.ApplicationContextInitializer对应的value。例如,spring-boot这个jar包它的 META-INF/spring.factories 部分定义为:
在这里插入图片描述

获取到全类名之后,之后会进行实例化,并返回实例。如下代码:

//通过工厂名称创建指定类型的工厂实例,并将它们放入一个列表中。
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);


private <T> List<T> createSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
			Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass
						.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException(
						"Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

2.1.2.3设置应用程序的所有监听器(listeners)

这只应用程序的listeners与设置初始化器(initializers)的方式相同,会从类路径的 META-INF/spring.factories 读取相应配置文件,读取配置文件中key为org.springframework.context.ApplicationListener对应的value。再根据拿到的全类名进行实例化,最后完成设置应用程序的所有监听器。

2.1.2.4设置应用程序运行主类

//4.设置应用程序运行主类
		this.mainApplicationClass = deduceMainApplicationClass();

private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}


到这里SpringApplication实例的初始化和一些准备工作就结束了。

2.2执行run方法

宏观上run方法都做了哪些工作:

public ConfigurableApplicationContext run(String... args) {
		//1.这里首先创建了一个 StopWatch 对象用于计时监控整个应用程序的启动时间。
		StopWatch stopWatch = new StopWatch();
		//启动计时监控
		stopWatch.start();
		//2.初始化应用上下文
		ConfigurableApplicationContext context = null;
		//初始化异常报告
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		//3.配置 Headless 属性:Headless 模式用于在没有显示器、键盘或鼠标的环境中运行应用程序。
		// 这里设置了 Headless 属性,以便在没有 GUI 的环境中正确启动应用程序。
		configureHeadlessProperty();
		//4.获取 SpringApplicationRunListeners:这一步是为了获取所有注册的 SpringApplicationRunListener 监听器。
		// 这些监听器在应用程序的不同生命周期阶段会被通知,从而能够对应用程序进行扩展和自定义处理。
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//通知监听者,开始启动
		// 这里通过调用 listeners.starting() 来通知所有的 SpringApplicationRunListener 监听器,应用程序即将开始启动。
		listeners.starting();
		try {
			//5.获取并解析应用程序在命令行上传入的参数。
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			/*
			6.根据SpringApplicationRunListeners以及参数来准备环境:Spring 环境包含了应用程序的配置信息,
			它由多个属性源组成,包括默认属性、命令行参数、系统属性、配置文件等。
			 */
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			//7.配置忽略 BeanInfo:这是一个特定的处理,用于在某些环境中忽略对 JavaBean 的分析。
			configureIgnoreBeanInfo(environment);
			//8.打印 Banner:Banner 是在应用程序启动时打印的一个 ASCII 艺术字或自定义标志。
			Banner printedBanner = printBanner(environment);
			//9.创建Spring上下文,其中包含了所有的 Spring Bean 和配置。
			context = createApplicationContext();
			//10.获取所有注册的 SpringBootExceptionReporter 实现类的实例。
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//11.Spring上下文前置处理:包括设置环境、添加 BeanPostProcessor、注册 BeanDefinition 等。
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//12.Spring上下文刷新:这将触发 Spring Bean 的实例化和依赖注入等过程。
			refreshContext(context);
			//13.spring上下文后置处理:可以在这里执行额外的初始化操作。
			afterRefresh(context, applicationArguments);
			//14.停止计时器
			stopWatch.stop();
			//15.如果设置了 logStartupInfo,则会记录应用程序启动信息,包括启动时间和主类等。
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			//16.调用 listeners.running(context) 来通知所有监听者,应用程序正在运行。
			listeners.started(context);
			//17.执行所有注册的 ApplicationRunner 和 CommandLineRunner,这些是在应用程序启动完成后自动执行的任务。
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		//18.返回上下文对象
		return context;
	}

下面将会详细的介绍各个环节:

2.2.1开启计时器

//1.这里首先创建了一个 StopWatch 对象用于计时监控整个应用程序的启动时间。
		StopWatch stopWatch = new StopWatch();
		//启动计时监控
		stopWatch.start();

//start 方法时,它会开始记录给定任务的运行时间,确保不会重复计时同一个任务。如果你尝试在计时器运行时再次调用 
//start 方法,它将会抛出异常,防止重复启动计时器。
public void start(String taskName) throws IllegalStateException {
    if (this.currentTaskName != null) {
        throw new IllegalStateException("Can't start StopWatch: it's already running");
    } else {
        this.currentTaskName = taskName;
        this.startTimeMillis = System.currentTimeMillis();
    }
}

2.2.2初始化应用上下文和初始化异常报告

//2.初始化应用上下文
		ConfigurableApplicationContext context = null;
		//初始化异常报告
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

2.2.3设置Headless属性

//配置 Headless 属性:Headless 模式用于在没有显示器、键盘或鼠标的环境中运行应用程序。
		// 这里设置了 Headless 属性,以便在没有 GUI 的环境中正确启动应用程序。
		configureHeadlessProperty();

/*
 * @description:这段代码用于配置 Java AWT 的 Headless 属性。Headless 模式是在没有显示器、键盘或鼠标的环境中运行 Java 应用程序,
 * 通常在服务器上或无界面的操作系统中使用。当应用程序在 Headless 模式下运行时,Java AWT 将不会初始化图形界面,以减少资源消耗。
 * @author: wangwei
 * @date: 2023/7/26 19:39
 * @param: []
 * @return: void
 **/
private void configureHeadlessProperty() {
   System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
         System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

2.2.4获取所有注册的listeners并发布ApplicationStartingEvent事件。

这里依然是使用了getSpringFactoriesInstances()方法来获取实例。
还是从 META-INF/spring.factories 中读取Key为 org.springframework.boot.SpringApplicationRunListener 的Values,最后根据全类名进行实例化。

//获取 SpringApplicationRunListeners:这一步是为了获取所有注册的 SpringApplicationRunListener 监听器。
		// 这些监听器在应用程序的不同生命周期阶段会被通知,从而能够对应用程序进行扩展和自定义处理。
		SpringApplicationRunListeners listeners = getRunListeners(args);


private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	//通过加载 "spring.factories" 配置文件中指定类型的工厂名称,并创建对应的工厂实例,然后根据工厂实例的排序顺序返回一个排序后的工厂实例的集合。
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		// 从 "spring.factories" 配置文件中加载指定类型的工厂名称,并将它们放入一个有序的集合中
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		//通过工厂名称创建指定类型的工厂实例,并将它们放入一个列表中。
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		//对工厂实例列表进行排序。
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}


最后回到SpringApplication中的run方法中
-------------------------------------------------

		//通知监听者,开始启动
		// 这里通过调用 listeners.starting() 来通知所有的 SpringApplicationRunListener 监听器,应用程序即将开始启动。
		listeners.starting();

public void starting() {
   for (SpringApplicationRunListener listener : this.listeners) {
      listener.starting();
   }
}
@Override
public void starting() {
   this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
//事件传播机制的实现。它用于广播(multicast)一个应用程序事件给所有对该事件感兴趣的监听器,并支持在多线程环境中异步执行监听器的处理逻辑。
public void multicastEvent(ApplicationEvent event) {
    this.multicastEvent(event, this.resolveDefaultEventType(event));
}

public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
    Executor executor = this.getTaskExecutor();
    Iterator var5 = this.getApplicationListeners(event, type).iterator();

    while(var5.hasNext()) {
        ApplicationListener<?> listener = (ApplicationListener)var5.next();
        if (executor != null) {
            executor.execute(() -> {
                this.invokeListener(listener, event);
            });
        } else {
            this.invokeListener(listener, event);
        }
    }

}


2.2.5获取并解析应用程序在命令行上传入的参数

//获取并解析应用程序在命令行上传入的参数。
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

public DefaultApplicationArguments(String[] args) {
   Assert.notNull(args, "Args must not be null");
   this.source = new Source(args);
   this.args = args;
}

//个命令行参数解析器的实现,用于将传入的命令行参数数组 args 解析成一个 
//CommandLineArgs 对象。
//CommandLineArgs 是一个自定义类,用于存储解析后的命令行参数信息。
public CommandLineArgs parse(String... args) {
    CommandLineArgs commandLineArgs = new CommandLineArgs();
    String[] var3 = args;
    int var4 = args.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String arg = var3[var5];
        if (arg.startsWith("--")) {
            String optionText = arg.substring(2, arg.length());
            String optionValue = null;
            String optionName;
            if (optionText.contains("=")) {
                optionName = optionText.substring(0, optionText.indexOf(61));
                optionValue = optionText.substring(optionText.indexOf(61) + 1, optionText.length());
            } else {
                optionName = optionText;
            }

            if (optionName.isEmpty() || optionValue != null && optionValue.isEmpty()) {
                throw new IllegalArgumentException("Invalid argument syntax: " + arg);
            }

            commandLineArgs.addOptionArg(optionName, optionValue);
        } else {
            commandLineArgs.addNonOptionArg(arg);
        }
    }

    return commandLineArgs;

2.2.6读取环境配置信息

/*
根据SpringApplicationRunListeners以及参数来准备环境:Spring 环境包含了应用程序的配置信息,
它由多个属性源组成,包括默认属性、命令行参数、系统属性、配置文件等。
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

/*
 * @description:为应用程序创建并准备一个配置环境(ConfigurableEnvironment)。它将应用程序的命令行参数、配置属性等信息设置到环境中,
 * 并通知相关的监听器。通过这个准备环境的过程,应用程序可以在后续的启动过程中正确加载和配置所需的属性和配置信息。
 * @author: wangwei
 * @date: 2023/7/26 20:14
 * @param: [listeners, applicationArguments]
 * @return: org.springframework.core.env.ConfigurableEnvironment
 **/
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
   //获取或创建应用程序的环境对象。环境是一个配置容器,用于保存应用程序的配置信息、属性和其他环境相关的内容。
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   //配置环境,将应用程序的命令行参数(applicationArguments)设置到环境中。
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   //将配置属性源(ConfigurationPropertySource)附加到环境中,用于加载配置属性。
   ConfigurationPropertySources.attach(environment);
   //通知监听器,应用程序的环境已准备好。这是应用程序启动过程中通知监听器的一个生命周期事件。
   listeners.environmentPrepared(environment);
   //将环境对象与当前的 Spring 应用程序实例进行绑定,以便可以在应用程序中访问该环境。
   bindToSpringApplication(environment);
   //如果当前环境不是自定义环境,则执行下面的逻辑。
   if (!this.isCustomEnvironment) {
      /*
      创建一个 EnvironmentConverter 对象,并将现有的环境对象 environment 作为参数传入。
      调用 convertEnvironmentIfNecessary 方法,将当前环境转换成指定类型的环境。这通常是为了适应不同的环境配置。
       */
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   //再次将配置属性源(ConfigurationPropertySource)附加到环境中,用于加载配置属性。
   ConfigurationPropertySources.attach(environment);
   return environment;
}



2.2.7设置忽略BeanInfo

//配置忽略 BeanInfo:这是一个特定的处理,用于在某些环境中忽略对 JavaBean 的分析。
			configureIgnoreBeanInfo(environment);

2.2.8.打印Banner

//打印 Banner:Banner 是在应用程序启动时打印的一个 ASCII 艺术字或自定义标志。
			Banner printedBanner = printBanner(environment);

2.2.9创建上下文createApplicationContext

//创建Spring上下文,其中包含了所有的 Spring Bean 和配置。
			context = createApplicationContext();
protected ConfigurableApplicationContext createApplicationContext() {
		//获取应用程序上下文的类类型。这个类类型可以在应用程序配置中指定,表示要使用的自定义上下文类。
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
					//如果 this.webApplicationType 为 SERVLET,则使用默认的 Servlet Web 上下文类
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					//如果 this.webApplicationType 为 SERVLET,则使用默认的 Servlet Web 上下文类
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					//对于其他情况,默认使用普通的上下文类类型 DEFAULT_CONTEXT_CLASS。
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
						ex);
			}
		}
		//实例化 contextClass 对象,得到一个应用程序上下文的实例。
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

2.2.10.获取所有异常报告器

//获取所有注册的 SpringBootExceptionReporter 实现类的实例。
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);
      
      
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // Use names and ensure unique to protect against duplicates
   //加载指定 type 类型的工厂类名称。
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
   //根据工厂类名称创建实例对象。
   //这个方法将通过反射实例化工厂类,并调用其对应的静态工厂方法或构造函数。
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

2.2.11准备上下文prepareContext

/*
 * @description:准备应用程序的上下文。它将配置环境设置到上下文中,调用初始化器进行上下文初始化,注册一些特定的单例 Bean,加载应用程序的资源,
 * 并通知监听器应用程序的上下文已准备好和已加载完成。
 * 这个过程是 Spring Boot 应用程序启动过程中的重要一环,确保应用程序上下文正确配置和初始化。
 * @author: wangwei
 * @date: 2023/7/26 20:55
 * @param: 
 * @return: 
 **/

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   //将应用程序的配置环境 environment 设置到上下文 context 中。
   context.setEnvironment(environment);
   //对应用程序的上下文进行后置处理。这是 Spring 的扩展点,可以在应用程序上下文创建后,对其进行额外的定制。
   postProcessApplicationContext(context);
   //调用应用程序的初始化器(ApplicationContextInitializer)对上下文进行初始化。初始化器是一种在 Spring Boot 应用程序启动时自动执行的扩展机制。
   applyInitializers(context);
   //通知监听器,应用程序的上下文已准备好。这是应用程序启动过程中通知监听器的一个生命周期事件。
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      //如果应用程序的上下文没有父级上下文,则记录启动信息。
      logStartupInfo(context.getParent() == null);
      //记录应用程序的启动配置信息和配置文件。
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   //向上下文注册特定的单例 Bean:
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   //注册 applicationArguments,应用程序的命令行参数对象。
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      //注册 printedBanner,打印的横幅对象。
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      //设置是否允许覆盖 Bean 定义,即是否允许多次注册同一名称的 Bean。
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // Load the sources
   //设置是否允许覆盖 Bean 定义,即是否允许多次注册同一名称的 Bean。
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   //设置是否允许覆盖 Bean 定义,即是否允许多次注册同一名称的 Bean。
   load(context, sources.toArray(new Object[0]));
   //设置是否允许覆盖 Bean 定义,即是否允许多次注册同一名称的 Bean。
   listeners.contextLoaded(context);
}

2.2.12.刷新上下文refreshContext

//Spring上下文刷新:这将触发 Spring Bean 的实例化和依赖注入等过程。
			refreshContext(context);

private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}
	
//Spring 应用程序上下文刷新的核心方法,负责进行初始化、配置和处理各种 Bean,使得应用程序上下文准备好运行,可以在后续的运行中使用和管理 Bean。
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      //准备应用程序上下文进行刷新操作。
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      //准备应用程序上下文进行刷新操作。
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      //准备 Bean 工厂供在上下文中使用
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
          //允许上下文子类后处理 Bean 工厂,做一些额外的配置。
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         //调用注册在上下文中的 Bean 工厂后置处理器,这些后置处理器用于在 Bean 工厂实例化 Bean 之前修改、扩展或配置 Bean 工厂。
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
        //注册 Bean 后置处理器(BeanPostProcessor)到 Bean 工厂中。Bean 后置处理器在 Bean 的实例化、初始化以及销毁的过程中,可以对 Bean 进行拦截和处理。
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         //初始化消息资源,用于国际化和本地化消息处理。
         initMessageSource();

         // Initialize event multicaster for this context.
         //初始化应用程序事件广播器,用于发布和处理应用程序事件。
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         //在特定上下文子类中,进行其他特殊 Bean 的初始化和配置。
         onRefresh();

         // Check for listener beans and register them.
         //检查并注册事件监听器
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         //实例化所有剩余的(非懒加载)单例 Bean。
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         //完成刷新操作,发布 Spring 容器刷新事件。
         finishRefresh();
      }

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

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

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

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         //清空 Spring 核心中的常见的元数据缓存,以便在运行时不再需要单例 Bean 的元数据信息。
         resetCommonCaches();
      }
   }
}

2.2.13上下文后置处理

//在默认情况下不执行任何操作,留给子类去扩展和实现。
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

2.2.14停止计时器

//停止 
StopWatch 的计时,并记录上一次计时任务的执行时间。
public void stop() throws IllegalStateException {
    if (this.currentTaskName == null) {
        throw new IllegalStateException("Can't stop StopWatch: it's not running");
    } else {
        long lastTime = System.currentTimeMillis() - this.startTimeMillis;
        this.totalTimeMillis += lastTime;
        this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
        if (this.keepTaskList) {
            this.taskList.add(this.lastTaskInfo);
        }

        ++this.taskCount;
        this.currentTaskName = null;
    }
}

2.2.15.打印应用程序启动信息

//如果设置了 logStartupInfo,则会记录应用程序启动信息,包括启动时间和主类等。
if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}

2.2.16 通知监听器,应用程序正在运行

//是通知所有注册的 SpringApplicationRunListener 实现类,应用程序上下文已经启动完成。
public void started(ConfigurableApplicationContext context) {
   for (SpringApplicationRunListener listener : this.listeners) {
      listener.started(context);
   }
}

2.2.17执行所有Runner

//它在应用程序上下文创建完成后,调用所有注册的 ApplicationRunner 和 CommandLineRunner bean 的 run() 方法。
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);
   for (Object runner : new LinkedHashSet<>(runners)) {
      if (runner instanceof ApplicationRunner) {
         callRunner((ApplicationRunner) runner, args);
      }
      if (runner instanceof CommandLineRunner) {
         callRunner((CommandLineRunner) runner, args);
      }
   }
}

2.2.18.返回上下文对象context

return context;

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

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

相关文章

内置 NMOS 单路 PWM 控制的高调光比 LED 降压恒流控制器

概述 OC5401M 是一款内置调光 NMOS 的单路 PWM 控制的高调光比降压恒流驱动控制器&#xff0c;PWM 调光比最高可达 10000&#xff1a;1。 OC5401M 支持 16-60V 输入电压范围。 OC5401M 采用电流滞环控制方式&#xff0c;无需环路补偿。 OC5401M 可通过外接电阻设置 LED输出电流…

Python(四十九)获取列表指定元素的索引

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

Improving Cross-Modal Retrieval with Set of Diverse Embeddings

框架图&#xff1a; Using Triplet Loss: Smooth-Chamfer similarity Using Log-Sum-Exp,

matplotlib从起点出发(5)_Tutorial_5_styleSheets

1 使用样式表和rcParams自定义matplotlib 以下是自定义matplotlib的属性和默认样式的提示。 有三种方式来自定义matplotlib: 在运行时设置rcParams&#xff1b;使用样式表&#xff1b;改写matplotlibrc文件。 在运行时设置rcParams优先于样式表&#xff0c;样式表优先于matp…

[JavaWeb]SQL介绍-DDL语句

SQL介绍-DDL语句 一.SQL简介1.简介2.SQL通用语法3.SQL语言的分类 二.DDL-操作数据库与表1.DDL操作数据库2.DDL操作表①.查询表(Retrieve)②.创建表(Create)③.修改表(Update)④.删除表(Delete) 一.SQL简介 1.简介 SQL: Structured Query Language–结构化查询语言用来操作关系…

如图,△ABC中,AD是角平分线,E、F分别为AC、AB上的点,且∠AED+∠AFD=180°.试问:DE与DF有何关系,并说明理由.

Question&#xff1a; 如图&#xff0c;△ABC中&#xff0c;AD是角平分线&#xff0c;E、F分别为AC、AB上的点&#xff0c;且∠AED∠AFD180&#xff0e;试问&#xff1a;DE与DF有何关系&#xff0c;并说明理由&#xff0e; Answer&#xff1a; 分析&#xff1a;过D作DM⊥AB于…

postgresql搭建主备

文章目录 1. 要求2. 主库设置3. 主库创建复制槽、复制用户4. 导出主库5. 备库设置6. 备库创建standby.signal7. 启动备库8. 备库创建复制槽、查询数据9. 查看日志10. 测试切换11. 同步测试12. 监控主备状态 1. 要求 1.使用流复制&#xff0c;配置复制槽 2.只有一个备库时不配置…

技术分享 | 接口自动化测试中,文件上传该如何测试?

在服务端自动化测试过程中&#xff0c;文件上传类型的接口对应的请求头中的 content-type 为 multipart/form-data; boundary…&#xff0c;碰到这种类型的接口&#xff0c;使用 Java 的 REST Assured 或者 Python 的 Requests 均可解决。 实战练习 Python 版本 在 Python 版…

Mysql原理篇--第三章 事务与锁

文章目录 前言一、mysql 的事务1 .1 mysql 事务:1 .2 mysql 为什么要支持事务: 二、mysql的事务实现&#xff1a;2.1 mysql的事务隔离级别&#xff1a;2.2 mysql 脏读&#xff0c;不可重复读&#xff0c;幻读&#xff1a;2.2.1 脏读&#xff1a;2.2.2 不可重复读&#xff1a;2.…

生态共建 | 5-6月,YashanDB与14款产品完成兼容互认证

5-6月&#xff0c;深圳计算科学研究院崖山数据库系统YashanDB与中科可控信息产业有限公司、云基智慧工程股份有限公司、上海天玑数据技术有限公司、金蝶软件&#xff08;中国&#xff09;有限公司、上海布雷德科技有限公司、深圳市杉岩数据技术有限公司、广东中兴新支点技术有限…

prometheus和cAdvisor组合

文章目录 docker内部署PromethuesPrometheuscAdvisorPrometheus和cAdvisor关系配置 docker内部署Promethues Prometheus Prometheus是一个开源的系统监控和报警工具&#xff0c;由SoundCloud开发并在2012年捐赠给了Cloud Native Computing Foundation (CNCF)。它被广泛用于监…

CAN学习笔记1:计算机网络

计算机网络 1 概述 计算机网络就是把多种形式的计算机用通信线路连接起来&#xff0c;并使其能够互相进行交换的系统。实际上&#xff0c;计算机网络包括了计算机、各种硬件、各种软件、组成网络的体系结构、网络传输介质和网络通信计数。因此&#xff0c;计算机网络是计算机…

第G3周:CGAN|生成手势图像

目录 一、准备工作1. 导入数据2. 数据可视化 二、构建模型1. 构建生成器2. 构建鉴别器 三、训练模型1. 定义损失函数2. 定义优化器3. 训练模型 四、理论基础1.DCGAN原理2.DCGAN网络3.个人感悟 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f…

自动化测试:让软件测试更高效更愉快!

谈谈那些实习测试工程师应该掌握的基础知识&#xff08;一&#xff09;_什么时候才能变强的博客-CSDN博客https://blog.csdn.net/qq_17496235/article/details/131839453谈谈那些实习测试工程师应该掌握的基础知识&#xff08;二&#xff09;_什么时候才能变强的博客-CSDN博客h…

【C++ 进阶】学习导论:C/C++ 进阶学习路线、大纲与目标

目录 一、C 学习路线 二、C 课程大纲与学习目标 &#xff08;1&#xff09;第一阶段&#xff1a;C 语言基础 &#xff08;2&#xff09;第二阶段&#xff1a;C 高级编程 &#xff08;3&#xff09;第三阶段&#xff1a;C 核心编程与桌面应用开发 &#xff08;4&#xf…

Cpp03 — 类和对象、this指针

一、类和对象 类由两部分构成&#xff1a;1.成员变量2.成员函数 C中的struct兼容C的所有用法&#xff0c;同时C中把struct升级成类 注意&#xff1a;类中的成员函数和成员数据都是声明&#xff0c;不是定义 类里的函数不是存到了对象中&#xff0c;而是存在公共区域&#x…

8.7 PowerBI系列之DAX函数专题-排名逻辑的4种实现-rankx详解

需求 实现 1 度量值 序号1排名 rankx(all(成绩表),成绩表[scoreandname],sum(成绩表[scoreandname])) --按照组合列排序2 rankx稀疏排名 rankx(all(成绩表),成绩表[分数],sum(成绩表[分数]),desc,skip) 3 rankx稠密排名 switch(true(),hasonevalue(成绩表[姓名])&#xff…

CCD光斑图像质量分析仪的作用和工作原理

激光光斑的成型在激光加工中起着至关重要的作用&#xff0c;在实际加工中激光可以被变换成各种形状以满足加工需求&#xff0c;激光光斑的形状大致可以被分为四种&#xff1a;矩形、环形、椭圆形、线形。矩形的激光光斑适用于激光切割、激光焊接&#xff0c;环形的激光光斑适用…

契约测试之 - 使用Pact-JS编写契约测试

契约测试是一种通过对每个应用程序进行孤立检查&#xff0c;以确保其发送或接收的消息符合在“合同”中记录的共享理解的集成点测试技术。对于通过HTTP进行通信的应用程序&#xff0c;这些“消息”将是HTTP请求和响应&#xff0c;而对于使用队列的应用程序&#xff0c;则是放入…

代码版本管理工具 git

1. 去B站看视频学习&#xff0c;只看前39集&#xff1a; 01-Git概述&#xff08;Git历史&#xff09;_哔哩哔哩_bilibili 2.学习Linux系统文本编辑器的使用 vi编辑器操作指令分享 (baidu.com) (13条消息) nano编辑器的使用_SudekiMing的博客-CSDN博客 windows下载安装Git官…