Spring Boot——Spring Boot启动原理

系列文章目录


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;

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

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

相关文章

Lab———Git使用指北

Lab———Git使用指北 &#x1f916;:使用IDEA Git插件实际工作流程 &#x1f4a1; 本文从实际使用的角度出发&#xff0c;以IDEA Git插件为基本讲述了如果使用IDEA的Git插件来解决实际开发中的协作开发问题。本文从 远程仓库中拉取项目&#xff0c;在本地分支进行开发&#x…

OpenCvSharp (C# OpenCV) 二维码畸变矫正--基于透视变换(附源码)

导读 本文主要介绍如何使用OpenCvSharp中的透视变换来实现二维码的畸变矫正。 由于CSDN文章中贴二维码会导致显示失败,大家可以直接点下面链接查看图片: C# OpenCV实现二维码畸变矫正--基于透视变换 (详细步骤 + 代码) 实现步骤 讲解实现步骤之前先看下效果(左边是原图,右边…

CSS 瀑布流效果效果

示例 <!DOCTYPE html> <html lang="cn"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>瀑布流效果</title><style>…

Windows安装PyTorch

文章目录 前言CPU版本安装安装步骤测试CPU版PyTorch是否安装成功 GPU版本安装新建一个conda环境安装torch法一&#xff1a;在线安装(建议用法二)法二&#xff1a;下载torch、torchvision后本地安装(建议使用此方法)Jupyter Lab虚拟环境的配置测试是否安装成功 前言 此份文档适…

Java 版 spring cloud +spring boot 工程系统管理 工程项目管理系统源码 工程项目各模块及其功能点清单

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…

【JavaEE初阶】HTTP请求的构造及HTTPS

文章目录 1.HTTP请求的构造1.1 from表单请求构造1.2 ajax构造HTTP请求1.3 Postman的使用 2. HTTPS2.1 什么是HTTPS?2.2 HTTPS中的加密机制(SSL/TLS)2.2.1 HTTP的安全问题2.2.2 对称加密2.2.3 非对称加密2.2.3 中间人问题2.2.5 证书 1.HTTP请求的构造 常见的构造HTTP 请求的方…

EMO:重新思考高效的基于注意力的移动块模型

文章目录 摘要1、介绍2、方法论:归纳法和演绎法2.1、通用效率模型标准2.2、元移动块2.3、微设计:倒置残余移动块2.4、面向密集预测的EMO宏观设计 3、实验3.1、图像分类3.2、下游任务3.3、额外的消融和解释分析 4、相关工作5、结束语及未来工作 摘要 论文链接&#xff1a;https…

Ubuntu更改虚拟机网段(改成桥接模式无法连接网络)

因为工作需要&#xff0c;一开始在安装vmware和虚拟机时&#xff0c;是用的Nat网络。 现在需要修改虚拟机网段&#xff0c;把ip设置成和Windows端同一网段&#xff0c;我们就要去使用桥接模式。 环境&#xff1a; Windows10、Ubuntu20.04虚拟机编辑里打开虚拟网络编辑器&#…

在Mac上搭建Gradle环境

在Mac上搭建Gradle环境&#xff1a; 步骤1&#xff1a;下载并安装Java开发工具包&#xff08;JDK&#xff09; Gradle运行需要Java开发工具包&#xff08;JDK&#xff09;。您可以从Oracle官网下载适合您的操作系统版本的JDK。请按照以下步骤进行操作&#xff1a; 打开浏览器…

520 · 一致性哈希 II

链接&#xff1a;LintCode 炼码 - ChatGPT&#xff01;更高效的学习体验&#xff01; 题解&#xff1a; class Solution{private:int n;const int mVirtualNodeCount;map<int, int> mVirtualNodeToMachineIdMap;set<int> mVirtualNodeSet;public:Solution(int n…

SpringBoot房屋租赁系统【附ppt|万字文档(LW)和搭建文档】

主要功能 前台登录&#xff1a; ①首页&#xff1a;公告信息、房屋信息展示、查看更多等 ②房屋信息、房屋类型、我要当房主、公告信息、留言反馈等 ③个人中心&#xff1a;可以查看自己的信息、更新图片、更新信息、退出登录、我的收藏 后台登录&#xff1a; ①首页、个人中心…

软件测试-基础阶段学习

目录 一、测试介绍 二、测试常用分类 三、模型 四、测试流程 五、测试用例 六、用例设计方法 七、缺陷 八、html 资料获取方法 阶段目标 能独立针对web项目实施功能测试 一、测试介绍 什么是软件测试 使用技术手段验证软件是否满足需求 测试主流技能 功能测试自…

golang,gin框架的请求参数(一)--推荐

golang&#xff0c;gin框架的请求参数&#xff08;一&#xff09; gin框架的重要性不过多的强调&#xff0c;重点就gin使用中的参数传递&#xff0c;获取进行梳理文件&#xff0c;满足使用需求。 获取前端请求参数的几种方法&#xff1a; 一、获取参数【浏览器地址获取参数】…

【业务功能篇58】Springboot + Spring Security 权限管理 【中篇】

4.2.3 认证 4.2.3.1 什么是认证&#xff08;Authentication&#xff09; 通俗地讲就是验证当前用户的身份&#xff0c;证明“你是你自己”&#xff08;比如&#xff1a;你每天上下班打卡&#xff0c;都需要通过指纹打卡&#xff0c;当你的指纹和系统里录入的指纹相匹配时&…

图注意力网络论文详解和PyTorch实现

图神经网络(gnn)是一类功能强大的神经网络&#xff0c;它对图结构数据进行操作。它们通过从节点的局部邻域聚合信息来学习节点表示(嵌入)。这个概念在图表示学习文献中被称为“消息传递”。 消息(嵌入)通过多个GNN层在图中的节点之间传递。每个节点聚合来自其邻居的消息以更新其…

【小白必看】Python爬虫实战之批量下载女神图片并保存到本地

文章目录 前言运行结果部分图片1. 引入所需库2. 发送请求获取网页内容3. 解析网页内容并提取图片地址和名称4. 下载并保存图片完整代码关键代码讲解 结束语 前言 爬取网络上的图片是一种常见的需求&#xff0c;它可以帮助我们批量下载大量图片并进行后续处理。本文将介绍如何使…

django学习笔记(1)

django创建项目 先创建一个文件夹用来放django的项目&#xff0c;我这里是My_Django_it 之后打开到该文件下&#xff0c;并用下面的指令来创建myDjango1项目 D:\>cd My_Django_itD:\My_Django_it>"D:\zzu_it\Django_learn\Scripts\django-admin.exe" startpr…

echarts遇到的问题

文章目录 折线图-区域面积图 areaStyley轴只有整数y轴不从0开始y轴数值不确定&#xff0c;有大有小&#xff0c;需要动态处理折线-显示label标线legend的格式化和默认选中状态x轴的lable超长处理x轴的相关设置 echarts各个场景遇到的问题 折线图-区域面积图 areaStyle areaStyl…

分库分表之基于Shardingjdbc+docker+mysql主从架构实现读写分离(二)

说明&#xff1a;如果实现了docker部署mysql并完成主从复制的话再继续&#xff0c;本篇文章主要说明springboot配置实现Shardingjdbc进行读写分离操作。 如果没实现docker部署mysql实现主从架构的话点击我 Shardingjdbc配置介绍&#xff08;版本&#xff1a;5.3.2&#xff09;…

STM32 Flash学习(一)

STM32 FLASH简介 不同型号的STM32&#xff0c;其Flash容量也不同。 MiniSTM32开发板选择的STM32F103RCT6的FLASH容量为256K字节&#xff0c;属于大容量产品。 STM32的闪存模块由&#xff1a;主存储器、信息块和闪存存储器接口寄存器等3部分组成。 主存储器&#xff0c;该部分…