Proceso de inicio del código fuente de SpringBoot (2)

prefacio

Para algunas partes vagas del código fuente, las sacaré por separado, y las otras básicamente se comentan en el método principal.

correr

Arranque de Spring Boot

@SpringBootApplication
public class App {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(App.class);
    }
}

Las anotaciones @SpringBootApplicationno se explicarán nuevamente. El punto es SpringApplication.run(App.class);que desde SpringMvc hasta Spring Boot, los servicios web se pueden iniciar a través del programa principal, por lo que en este método, hay pasos para iniciar Tomcat, pero si inicializar el contenedor Spring y el contenedor Servlet. sigue siendo lo mismo que tomcat+ Like SpringMvc?

SpringApplication.run(App.class);Después de ingresar, aquí hay uno nuevo SpringApplication, y se pasa la clase principal. Mirando el nombre de este método ConfigurableApplicationContext, su función debería ser cargar el entorno y luego usar el método de ejecución para iniciar el contenedor preparado.

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    
    
		return new SpringApplication(primarySources).run(args);
	}
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    
    
        // 资源加载器,main启动的这里是null 
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // primarySources就是我们的app.class
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 这里它会判断应用环境,有3中:none(无web程序),REACTIVE,SERVLET, 一般都是servlet环境
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 从META-INFO/spring.factories中获取启动注册器,帮助启动上下文注册对象
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
        // 从META-INFO/spring.factories中获取ApplicationContextInitializer的实现类
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 从META-INFO/spring.factories中获取ApplicationListener的实现类
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // SpringBoot主程序类(App.class)
		this.mainApplicationClass = deduceMainApplicationClass();
	}

SpringBoot es compatible con una variedad de aplicaciones web. Determina WebApplicationType.deduceFromClasspath();el entorno web al inferir si hay clases correspondientes en el jvm actual. Cuando integramos Tomcat, recuperará javax.servlet.Servletestas ConfigurableWebApplicationContext``DispatcherServlettres clases.

imagen-20221101204327614

public ConfigurableApplicationContext run(String... args) {
    
    
    // 监视器对象,记录开始时间,结束时间等
    // 它记录了开始时间,结束时间,当前线程名称
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
    // 创建bootstrapContext启动上下文对象
    // 并执行启动初始程序方法(BootstrapRegistryInitializer.initialize)
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
    // 设置无显示器也可以启动
		configureHeadlessProperty();
    // 从META-INFO/spring.factories中获取SpringApplicationRunListener的实现类(spring应用执行监听器)
    // 这里获取完后,内置9个监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
    // 发布`ApplicationStartingEvent`事件并记录步骤
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
    
    
            // 启动参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备环境
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            // 忽略的bean设置到System.property里
			configureIgnoreBeanInfo(environment);
            // 打印SpringBoot logo 
			Banner printedBanner = printBanner(environment);
            // 它这里创建的是AnnotationConfigServletWebServerApplicationContext
            // 1. 创建bean工厂beanFactory,
            // 2. 创建AnnotatedBeanDefinitionReader并注册5个BeanDefinition
            // 3. 创建ClassPathBeanDefinitionScanner用于加载环境变量、设置资源加载器、 注册默认的includeFilter
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
            // 准备上下文
            // 1. 环境配置设置到上下文
            // 2. 执行ApplicationContext后置处理,设置beanName生成器,设置资源加载器
            // 3. 执行初始化程序:执行实现了ApplicationContextInitializer的实现类
            // 4. 发布ApplicationContextInitializedEvent
            // 5. 应用上下文bootstrap发布BootstrapContextClosedEvent事件
            // 6. 注册bean对象springApplicationArguments(这里是bean,不是beanDefinition)
            // 7. 注册bean对象springBootBanner
            // 8. 设置可重写属性设置allowBeanDefinitionOverriding
            // 9. 添加懒加载的beanFactory后置处理器
            // 10. 注册主函数类为bean
            // 11. 发布ApplicationPreparedEvent事件
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // 刷新
            // 在上面的一些了操作将环境,容器,解析器等准备好后,这里会刷新容器,也就是spring那一套:
            // 创建beanFactory,render, classScan等
            // 不过这里有一点不同,入口类是AnnotationConfigServletWebServerApplicationContext
            // new Tomcat()
            // 准备tomcat环境,配置(JspServlet,DefaultServlet),监听器,应用初始化程序等
            // 创建server,并start(),真正启动
            // 创建守护线程,阻塞
			refreshContext(context);
            // 刷新之后的操作
			afterRefresh(context, applicationArguments);
            // 应用启动完成,记录结束
			stopWatch.stop();
			if (this.logStartupInfo) {
    
    
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            // 发布启动完成事件ApplicationStartedEvent
			listeners.started(context);
            // 启动完成回调
            // 1. 找到所有ApplicationRunner的子类
            // 2. 找到所有CommandLineRunner的子类
            // 3. 回调执行找到的类的run方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
    
    
            // 处理启动失败
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
    
    
			listeners.running(context);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

Cargue el oyente de la aplicación y publique el evento de inicio

imagen-20221101230842243

	private SpringApplicationRunListeners getRunListeners(String[] args) {
    
    
		Class<?>[] types = new Class<?>[] {
    
     SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
 // 读取`META-INFO/spring.factories`文件中的SpringApplicationRunListener的实现类,并实例化
 // 这里传入了两个参数当前对象SpringApplication,和启动参数对象
 // 所以,最后他执行的监听器是内置的9个(ApplicationListener实现类)
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
 // 默认是ApplicationStartup.DEFAULT
				this.applicationStartup);
	}

Lee META-INFO/spring.factoriesla configuración de la siguiente manera, hay uno y solo uno EventPublishingRunListener, y solo tiene una estructura de parámetros, por lo que getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),el método de adquisición aquí admite parámetros entrantes, que es nuestro

imagen-20221101231224511

EventPublishingRunListenerSe puede ver que hay una colección de oyentes almacenados en él. Al escuchar, atravesará esta colección para encontrar el oyente correspondiente y luego ejecutarlo. Esta es una explicación simple de su principio.

Finalmente devolverá nuevos SpringApplicationRunListeners

	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners,
			ApplicationStartup applicationStartup) {
    
    
		this.log = log;
        // 从`META-INFO/spring.factories`文件中的SpringApplicationRunListener的实现类EventPublishingRunListener
		this.listeners = new ArrayList<>(listeners);
        // 应用步骤与指标记录
        // 每个步骤都有id,并且记录开始时间和结束时间
		this.applicationStartup = applicationStartup;
	}

Oyente comenzando:listeners.starting(bootstrapContext, this.mainApplicationClass);

	void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
    
    
		doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
				(step) -> {
    
    
					if (mainApplicationClass != null) {
    
    
						step.tag("mainApplicationClass", mainApplicationClass.getName());
					}
				});
	}



	private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
			Consumer<StartupStep> stepAction) {
    
    
		StartupStep step = this.applicationStartup.start(stepName);
		this.listeners.forEach(listenerAction);
		if (stepAction != null) {
    
    
			stepAction.accept(step);
		}
		step.end();
	}

Estos dos métodos juntos parecen más claros:

  1. Crea un objeto de paso "spring.boot.application.starting"
  2. Publique ApplicationStartingEventel evento, el parámetro es el objeto de contexto de inicio ConfigurableBootstrapContext
  3. Paso de registro mainApplicationClass

Resumir:

Muchas cosas en Spring se completan en torno a las funciones basadas en eventos, el inicio y la publicación de eventos y la configuración del entorno para publicar eventos, por lo que este paso es equivalente a la carga basada en eventos y también la publicación de un evento de inicio de la aplicación;

Carga el controlador de eventos. De hecho, carga META-INFO/spring.factoriesla clase de implementación SpringApplicationRunListener EventPublishingRunListener en el archivo. Hay un objeto SimpleApplicationEventMulticaster dentro de este oyente, y el oyente de ejecución debe ejecutar esta clase. El principio interno probablemente sea así: multicastEventcoincidirá de acuerdo con el parámetro, que es ApplicationEvent, se ejecuta el oyente correspondiente.El proceso de coincidencia consiste en atravesar la lista de oyentes internos y hacer coincidir uno por uno.

preparar el ambiente

imagen-20221105133510689

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    
    
		// 创建web环境配置对象,这里根据webApplicationType,进行创建,默认StandardEnvironment,servlet是StandardServletEnvironment
        // 添加4个属性源:`servletContextInitParams,servletConfigInitParams,systemProperties,systemEnvironment`
		ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 配置环境;启动时,如果有参数,那么会将参数封装成属性源对象(commandLineArgs),然后添加到environment
        // 加上这里的参数属性源有5个
		configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 添加属性源(configurationProperties)
        // 6个属性源
		ConfigurationPropertySources.attach(environment);
        // 发布准备环境事件ApplicationEnvironmentPreparedEvent
        // EnvironmentPostProcessorApplicationListener会加载配置文件,作为属性源添加,包含激活的配置文件配置
        // 读取配置是由ConfigDataEnvironmentPostProcessor完成,他会读取激活的配置,比如application-dev.yml的配置,并设置激活的属性`activeProfiles`
        // 如果你在日志中看到:Setting active profiles ,就是这个地方执行的
        // 还要,这里每次执行都会记录步骤,因为都是走的方法doWithListeners
		listeners.environmentPrepared(bootstrapContext, environment);
        // 将默认属性源移动到属性源列表中的最后一个,没有就
		DefaultPropertiesPropertySource.moveToEnd(environment);
		configureAdditionalProfiles(environment);
        // 通过ConversionService将environment绑定到SpringApplication
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
    
    
            // 将已经处理好的环境配置转换为StandardEnvironment
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
        // 这里我不知道为什么又调了一遍
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

Aquí getOrCreateEnvironment()se ejecutará el constructor de la clase padre, y luego se ejecutará el siguiente método para agregar 4 fuentes de propiedades: servletContextInitParams,servletConfigInitParamssystemProperties,systemEnvironment, si es el predeterminado, solo se agregarán 2:systemProperties,systemEnvironment

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
    
    
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
    
    
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
        // 添加默认属性源配置信息
		super.customizePropertySources(propertySources);
	}

Resumir:

Este paso es para preparar el entorno. En este paso, creará un objeto de configuración del entorno ConfigurableEnvironment como portador para cargar la configuración del entorno. Al principio, agregará el origen del atributo del servlet, la configuración del entorno del sistema (systemEnvironment) y el atributo del sistema java (systemProperties).Lea la fuente del atributo y el archivo de configuración (como application.yml) a través del oyente, léalo a través de ConfigDataEnvironmentPostProcessor cargado por EnvironmentPostProcessorApplicationListener para cargar la configuración y, finalmente, obtenga un objeto de entorno de configuración completo ConfigurableEnvironment.

Crear un objeto de contexto de aplicación ApplicationContext

imagen-20221101231117940

imagen-20221031221727717

Aquí this.applicationContextFactoryse creará de acuerdo con el tipo de aplicación, así que aquí estáAnnotationConfigServletWebServerApplicationContext

private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;

imagen-20221031221946620

imagen-20221031222415139

AnnotationConfigServletWebServerApplicationContext Esta clase se hereda de GenericApplicationContext, por lo que primero ejecutará el constructor de la clase principal para crear un beanFactory

imagen-20221105173809631

Crear un servidor web

imagen-20221105194340275

imagen-20221105202359063

La última ejecución es: org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer

imagen-20221105194416840

Aquí, webServer y ServletContext no existen, todos se crearán aquí.

	private void createWebServer() {
    
    
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
       // 通过main方法执行
		if (webServer == null && servletContext == null) {
    
    
            // 创建一个步骤“创建spring.boot.webserver.create步骤”
			StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
            // 创建webServlet工厂,如果使用tomcat,那么这里会创建TomcatServletWebServerFactory
            // 创建原理是,在beanFactory中查找ServletWebServerFactory类型的beanName,因为在前面准备环境时,判别环境并创建了web环境对象,所有这里直接从beanFactory中查找
			ServletWebServerFactory factory = getWebServerFactory();
			createWebServer.tag("factory", factory.getClass().toString());
            // 创建web server(tomcat环境,就是new Tomcat,并启动)
			this.webServer = factory.getWebServer(getSelfInitializer());
            // 记录结束
			createWebServer.end();
            // 注册web服务关闭对象,它用来保证服务停止时接受请求时响应方式
			getBeanFactory().registerSingleton("webServerGracefulShutdown",
					new WebServerGracefulShutdownLifecycle(this.webServer));
			getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
		}
        // 通过war包,通过Tomcat运行的,上下文由Tomcat提供
		else if (servletContext != null) {
    
    
			try {
    
    
                // this.webServer = factory.getWebServer(getSelfInitializer());内部执行也是这一句
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
    
    
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

	public WebServer getWebServer(ServletContextInitializer... initializers) {
    
    
		if (this.disableMBeanRegistry) {
    
    
			Registry.disableRegistry();
		}
        // new了一个tomcat对象
		Tomcat tomcat = new Tomcat();
        // 创建基础目录
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        // 设置目录
		tomcat.setBaseDir(baseDir.getAbsolutePath());
        // tomcat的内置对象Connector,负责处理请求,
        // 下面都是tomcat的一些配置
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
    
    
			tomcat.getService().addConnector(additionalConnector);
		}
        // 1. 设置容器名称,全局路径contextPath,基础路径
        // 2. 加载FixContextListener监听器
        // 3. 设置父加载器
        // 4. 覆盖Tomcat的默认语言环境映射以与其他服务器对齐。
        // 5. 加区域设置编码映射
        // ......
        // 找到tContextInitializer实现类集合,并添加
        // 设置errorPage,设置Mime映射
        // 
		prepareContext(tomcat.getHost(), initializers);
        // server = new StandardServer
        // 初始化根目录
        // 配置文件加载
        // server.start() 真正启动
        // 创建守护线程startDaemonAwaitThread(),
		return getTomcatWebServer(tomcat);
	}

getSelfInitializer()La ejecución es la siguiente, no se ejecuta inmediatamente, sino que devuelve una expresión lambda

// ServletContextInitializer是一个函数式方法(ServletContext) -> {实际方法体}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    
    
		return this::selfInitialize;
	}	
// 实际方法体
private void selfInitialize(ServletContext servletContext) throws ServletException {
    
    
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
    
    
			beans.onStartup(servletContext);
		}
	}

Cuando se ejecuta getSelfInitializer().onStartup(servletContext);, en realidad se ejecuta:

private void selfInitialize(ServletContext servletContext) throws ServletException {
    
    
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
    
    
			beans.onStartup(servletContext);
		}
	}

Resumir

No hay resumen en este artículo, solo mire el método de ejecución

Supongo que te gusta

Origin blog.csdn.net/qq_28911061/article/details/127715657
Recomendado
Clasificación