SpringBoot启动方式

news2024/12/25 0:44:56

SpringBoot启动方式

springboot的启动经过了一些一系列的处理,我们先看看整体过程的流程图
在这里插入图片描述
SpringBoot的启动方式

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.spring</groupId>
    <artifactId>springBoot-demo1</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
      
    </dependencies>


第一种启动方式 @EnableAutoConfiguration

@EnableAutoConfiguration 作用

开启自动装配,帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot,并创建对应配置类的Bean,并把该Bean实体交给IoC容器进行管理。

新建文件 IndexController.java

package com.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@EnableAutoConfiguration
@RestController
public class IndexController {
 
 
     // 访问路径   http://localhost:8080/index
    @RequestMapping("/index")
    public String index(){
        System.out.println("我进来了");
        return "index controller";
    }
 
    public static void main(String[] args) {
        // 启动springboot
        SpringApplication.run(IndexController.class,args);
    }
}

第二种启动方式 @ComponentScan

@ComponentScan() 注解作用

根据定义的扫描路径,将符合规则的类加载到spring容器中,比如在类中加入了以下注解 @Controller、@Service、@Mapper 、@Component、@Configuration 等等;

package com.spring.controller;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
 
@EnableAutoConfiguration  // 开启自动装配
@ComponentScan("com.spring.controller")
public class App {
    
    public static void main(String[] args) {
        // 启动springboot
        SpringApplication.run(App.class,args);
    }
}
 

控制层 OrderController.java

package com.spring.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
    
    @RequestMapping("/order")
    public String order(){
        System.out.println("我进来了 order controller 的 index 方法");
        return "order controller";
    }
}

第三种方式 @SpringBootApplication

@SpringBootApplication 注解作用
标注这是一个springboot的应用,被标注的类是一个主程序, SpringApplication.run(App.class, args);传入的类App.class必须是被@SpringBootApplication标注的类。

@SpringBootApplication是一个组合注解,组合了其他相关的注解,点进去注解后我们可以看到,这个注解集成了以上2种启动方式的注解;在这里的 @ComponentScan() 注解有一堆东西,它的作用是 将主配置类所在包及其下面所有后代包的所有注解扫描;

SpringBootApplication 使用起来更加简单,只需要一个注解即可完成,

package com.spring.controller;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
 
@SpringBootApplication
public class App {
 
    public static void main(String[] args) {
        // 启动springboot
        SpringApplication.run(App.class,args);
    }
}

SpringBoot启动流程

运行main函数,调用run方法
//分为两步
    // 1、new SpringApplication(primarySources)
    // 2、run(args)

创建SpringApplication对象

1、启动SpringBoot启动类SpringbootdemoApplication中的main方法。

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

2、调用SpringApplication.run(SpringbootdemoApplication.class, args),该方法是一个静态方法。

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

3、继续调用SpringApplication内部的run方法

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

且构建了一个SpringApplication对象,应用程序将从指定的主要来源加载Bean

public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   //resourceLoader赋值为Null
    // 初始化类加载器 
   this.resourceLoader = resourceLoader;
   //primarySources不为空,继续向下执行。为空抛异常
   // Assert 断言非空,若传入的class参数为null则打印异常并退出初始化
   Assert.notNull(primarySources, "PrimarySources must not be null");
   //将SpringbootdemoApplication(启动类)赋值给primarySources ,去重转换成primarySources对象
   // 传进来的class对象变成一个集合对象,只是做了一个格式转换,基本信息没有变化
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   //Web容器类型推断
   //从classpath类路径推断Web应用类型,有三种Web应用类型,分别是
   //NONE: 该应用程序不应作为 Web 应用程序运行,也不应启动嵌入式 Web 服务器
   //SERVLET: 该应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。
   //REACTIVE: 该应用程序应作为响应式 Web 应用程序运行,并应启动嵌入式响应式 Web 服务器
   // 返回NONE
   //<dependency>
       //     <groupId>org.springframework.boot</groupId>
        //    <artifactId>spring-boot-starter</artifactId>
     //   </dependency>
      // 返回SERVLET
   //<dependency>
       //     <groupId>org.springframework.boot</groupId>
        //    <artifactId>spring-boot-starter-web</artifactId>
     //   </dependency>
      //<dependency>
       //     <groupId>org.springframework.boot</groupId>
        //    <artifactId>spring-boot-starter-webflux</artifactId>
     //   </dependency>
     // 简单点说是:要不要加载web容器,或者说加载什么类型的容器
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
 
   //初始化ApplicationContextInitializer集合
    // 获取 Spring 工厂实例 -> 容器上下文相关的初始化
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   //初始化ApplicationListener
   // 获取 Spring 工厂实例 -> 设置应用程序监听器
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   //获取StackTraceElement数组遍历,通过反射获取堆栈中有main方法的类。
    // 推导出主应用程序类,即从当前的栈信息中寻找main所在主类:包名.类名
   this.mainApplicationClass = deduceMainApplicationClass();
}

在SpringApplication的构造方法内,首先会通过 WebApplicationType.deduceFromClasspath(); 方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式编程);

this.webApplicationType = WebApplicationType.deduceFromClasspath();

Spring boot依据Class path来推断web应用类型

  • reactive

路径包含org.springframework.web.reactive.DispatcherHandler,并且不包含org.springframework.web.servlet.DispatcherServlet和org.glassfish.jersey.servlet.ServletContainer

  • none

不是reactive,并且路径中没有javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext

  • servlet

剩余的部分都是servlet

NONE:不是一个 web 应用,不需要启动内置的 web 服务器,所以应用启动运行后就自动关闭了,并没有启动内置的 web 服务器,也没有监听任何端口。

SERVLET:基于 servlet 的 web 应用,需要启动一个内置的 servlet 服务器;通过启动日志我们可以看到这里启动了内置的 Tomcat Servlet 服务器,监听了 8080 端口,应用程序并不会像 None 类型一样,启动后就自动关闭。

REACTIVE:一个 reactive 的 web 应用,需要启动一个内置的 reactive 服务器;通过启动日志我们可以看到,这里启动了内置的 Netty 服务器,并监听在 8080 端口上

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot;

import org.springframework.util.ClassUtils;

/**
 * An enumeration of possible types of web application.
 *
 * @author Andy Wilkinson
 * @author Brian Clozel
 * @since 2.0.0
 */
public enum WebApplicationType {

    /**
     * The application should not run as a web application and should not start an
     * embedded web server.
     */
    NONE,

    /**
     * The application should run as a servlet-based web application and should start an
     * embedded servlet web server.
     */
    SERVLET,

    /**
     * The application should run as a reactive web application and should start an
     * embedded reactive web server.
     */
    REACTIVE;

    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";

    private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";

    private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

    /**
     * deduceFromClasspath
     * 依次循环遍历当前应用中是否存在相关的类来判断最终应用的启动类型
     *
     * @return
     */
    static WebApplicationType deduceFromClasspath() {
        /**
         * REACTIVE:响应式WEB项目
         * 若启动类型为REACTIVE,
         * 则类路径下存在 org.springframework.web.reactive.DispatcherHandler 类
         * 并且不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer
         * 两者指的是SpringMVC/Tomcat和jersey容器
         */
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        /**
         * NONE:非WEB项目,就是一个最简单的Springboot应用
         * 若启动类型为NONE
         * 则类路径下 javax.servlet.Servlet 和org.springframework.web.context.ConfigurableWebApplicationContext都不存在
         */
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        /**
         * SERVLET:SERVLET WEB 项目
         * 若启动类型为Servlet,则必须有SERVLET_INDICATOR_CLASSES中的javax.servlet.Servlet
         * 和org.springframework.web.context.ConfigurableWebApplicationContext
         */
        return WebApplicationType.SERVLET;
    }

    static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
        if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
            return WebApplicationType.SERVLET;
        }
        if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
            return WebApplicationType.REACTIVE;
        }
        return WebApplicationType.NONE;
    }

    private static boolean isAssignable(String target, Class<?> type) {
        try {
            return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
        } catch (Throwable ex) {
            return false;
        }
    }

}

getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object… args)中首先获取ClassLoader ,通过SpringFactoriesLoader机制,根据ClassLoader从类路径jar包中加载META-INF/spring.factories下的所有工厂类及其实现类 。

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

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    //获取ClassLoader 
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// 使用SpringFactoriesLoader机制加载出工厂名,并放入Set集合中确保其唯一性。但是在META-INF/spring.factories中并无BootstrapRegistryInitializer所以此处的names的size为0。
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	//创建Spring工厂实例(但因为上述names的size为0,所以对于BootstrapRegistryInitializer来说它并不会创建SpringFactory实例)
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	//通过AnnotationAwareOrderComparator对Spring工厂实例排序
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

这里加载的初始化器是springboot自带初始化器,从从 META-INF/spring.factories 配置文件中加载的,那么这个文件在哪呢?自带有2个,分别在源码的jar包的 spring-boot-autoconfigure 项目 和 spring-boot 项目里面各有一个

在这里插入图片描述
spring.factories文件里面,看到开头是 org.springframework.context.ApplicationContextInitializer 接口就是初始化器了 ,
在这里插入图片描述
当然,我们也可以自己实现一个自定义的初始化器:实现 ApplicationContextInitializer接口既可

MyApplicationContextInitializer.java

package com.spring.application;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/** * 自定义的初始化器 */
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { 

@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) { 

		System.out.println("我是初始化的 MyApplicationContextInitializer...");
	}
}

在resources目录下添加 META-INF/spring.factories 配置文件,内容如下,将自定义的初始化器注册进去;

org.springframework.context.ApplicationContextInitializer=\
com.spring.application.MyApplicationContextInitializer

在这里插入图片描述
启动springboot后,就可以看到控制台打印的内容了,在这里我们可以很直观的看到它的执行顺序,是在打印banner的后面执行的;
在这里插入图片描述
加载监听器也是从 META-INF/spring.factories 配置文件中加载的,与初始化不同的是,监听器加载的是实现了 ApplicationListener 接口的类

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

在这里插入图片描述
自定义监听器也跟初始化器一样

org.springframework.context.ApplicationListener=\
com.maweiqi.MyListener
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

//public class MyListener implements ApplicationListener {
//    @Override
//    public void onApplicationEvent(ApplicationEvent event) {
//        System.out.println("-----------");
//        System.out.println(event.getTimestamp());
//        System.out.println(event.getSource());
//        System.out.println(event.getClass());
//    }
//}

public class MyListener implements ApplicationListener<ApplicationStartingEvent> {

    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        System.out.println("-----可以干预不同的监听器------");
        System.out.println(event.getTimestamp());
        System.out.println(event.getSource());
        System.out.println(event.getClass());
    }
}

deduceMainApplicationClass(); 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备,deduce是推断的意思,所以准确地说,这个方法作用是推断出主方法所在的类;

this.mainApplicationClass = deduceMainApplicationClass();

执行Run方法

我们看启动springboot最核心的逻辑run方法

public ConfigurableApplicationContext run(String... args) {

   // new 一个StopWatch用于统计run启动过程花了多少时间
   StopWatch stopWatch = new StopWatch();
   
  // 开始计时
   stopWatch.start();
  
   //关键类,它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现
   ConfigurableApplicationContext context = null;
     // exceptionReporters集合用来存储异常报告器,用来报告SpringBoot启动过程的异常
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    /**
     * 设置jdk系统属性
     * headless直译就是无头模式,
     * headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是     true;
     */

   configureHeadlessProperty();
   
   // 【1】从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
    // EventPublishingRunListener对象主要用来发射SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段
   SpringApplicationRunListeners listeners = getRunListeners(args);
   
    // 启动SpringApplicationRunListener的监听,表示SpringApplication开始启动。
    // 》》》》》发射【ApplicationStartingEvent】事件
   listeners.starting();
   try {
   
      // 根据命令行参数 实例化一个ApplicationArguments
      			// 封装命令行参数,也就是在命令行下启动应用带的参数,如--server.port=9000 
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
            
        // 【2】准备环境变量,包括系统变量,环境变量,命令行参数,默认变量,servlet相关配置变量,随机值,
        // JNDI属性值,以及配置文件(比如application.properties)等,注意这些环境变量是有优先级的
        // 》》》》》发射【ApplicationEnvironmentPreparedEvent】事件
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      //忽略beaninfo的bean      
      configureIgnoreBeanInfo(environment);
      
        // 【3】控制台打印SpringBoot的bannner标志
      Banner printedBanner = printBanner(environment);
      
      //根据webApplicationType创建Spring上下文
       // 【4】根据不同类型创建不同类型的spring applicationcontext容器
        // 因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象
      context = createApplicationContext();
      // 【5】从spring.factories配置文件中加载异常报告期实例,这里加载的是FailureAnalyzers
        // 注意FailureAnalyzers的构造器要传入ConfigurableApplicationContext,因为要从context中获取beanFactory和environment
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
            
            
            
         // 【6】为刚创建的AnnotationConfigServletWebServerApplicationContext容器对象做一些初始化工作,准备一些容器属性值等
        // 1)为AnnotationConfigServletWebServerApplicationContext的属性AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner设置environgment属性
        // 2)根据情况对ApplicationContext应用一些相关的后置处理,比如设置resourceLoader属性等
        // 3)在容器刷新前调用各个ApplicationContextInitializer的初始化方法,ApplicationContextInitializer是在构建SpringApplication对象时从spring.factories中加载的
        // 4)》》》》》发射【ApplicationContextInitializedEvent】事件,标志context容器被创建且已准备好
        // 5)从context容器中获取beanFactory,并向beanFactory中注册一些单例bean,比如applicationArguments,printedBanner
        // 6)TODO 加载bean到application context,注意这里只是加载了部分bean比如mainApplication这个bean,大部分bean应该是在AbstractApplicationContext.refresh方法中被加载?这里留个疑问先
        // 7)》》》》》发射【ApplicationPreparedEvent】事件,标志Context容器已经准备完成 
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
            
      // 【7】刷新容器,这一步至关重要,以后会在分析Spring源码时详细分析,主要做了以下工作:
        // 1)在context刷新前做一些准备工作,比如初始化一些属性设置,属性合法性校验和保存容器中的一些早期事件等;
        // 2)让子类刷新其内部bean factory,注意SpringBoot和Spring启动的情况执行逻辑不一样
        // 3)对bean factory进行配置,比如配置bean factory的类加载器,后置处理器等
        // 4)完成bean factory的准备工作后,此时执行一些后置处理逻辑,子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置
        // 在这一步,所有的bean definitions将会被加载,但此时bean还不会被实例化
        // 5)执行BeanFactoryPostProcessor的方法即调用bean factory的后置处理器:
        // BeanDefinitionRegistryPostProcessor(触发时机:bean定义注册之前)和BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)
        // 6)注册bean的后置处理器BeanPostProcessor,注意不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的
        // 7)初始化国际化MessageSource相关的组件,比如消息绑定,消息解析等
        // 8)初始化事件广播器,如果bean factory没有包含事件广播器,那么new一个SimpleApplicationEventMulticaster广播器对象并注册到bean factory中
        // 9)AbstractApplicationContext定义了一个模板方法onRefresh,留给子类覆写,比如ServletWebServerApplicationContext覆写了该方法来创建内嵌的tomcat容器
        // 10)注册实现了ApplicationListener接口的监听器,之前已经有了事件广播器,此时就可以派发一些early application events
        // 11)完成容器bean factory的初始化,并初始化所有剩余的单例bean。这一步非常重要,一些bean postprocessor会在这里调用。
        // 12)完成容器的刷新工作,并且调用生命周期处理器的onRefresh()方法,并且发布ContextRefreshedEvent事件
      refreshContext(context);
      // 【8】执行刷新容器后的后置处理逻辑,注意这里为空方法
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
       // 【9】调用ApplicationRunner和CommandLineRunner的run方法,实现spring容器启动后需要做的一些东西比如加载一些业务数据等
      callRunners(context, applicationArguments);
   }
   //部分代码省略
   return context;
}

上面的每行代码,每行解释,都要细细品一下,接下来我们把上面几个重要的点再深入下。

1、StopWatch stopWatch = new StopWatch();

StopWatch 是一个spring util 包下的工具类,是一个计时器,能计算代码段耗时时间。

run方法中在最开始创建StopWatch并start开始计算。在所有的操作完成后调用stop方法结束计时,然后通过log info输出格式化后的时间。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // do something
        stopWatch.stop();

        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); //输出计时时间
        }
 }

run方法中在最开始创建StopWatch并start开始计算。在所有的操作完成后调用stop方法结束计时,然后通过log info输出格式化后的时间。

2、configureHeadlessProperty()

//配置java.awt.headless属性
 configureHeadlessProperty();

这里将java.awt.headless设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能。

做了这样的操作后,SpringBoot想干什么呢?其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置.

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
方法主体如下:
private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
                SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

通过方法可以看到,setProperty()方法里面又有个getProperty();这不多此一举吗?其实getProperty()方法里面有2个参数, 第一个key值,第二个是默认值,意思是通过key值查找属性值,如果属性值为空,则返回默认值 true;保证了一定有值的情况;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
        Properties properties = System.getProperties();
        properties.list(System.out);
    }
}

获取并启用监听器

这一步 通过监听器来实现初始化的的基本操作,这一步做了2件事情

  • 创建所有 Spring 运行监听器并发布应用启动事件
  • 启用监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	return new SpringApplicationRunListeners(logger,
			getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
			this.applicationStartup);
}

先看看new SpringApplicationRunListeners中的getSpringFactoriesInstances这个方法做了什么事情

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)

这个主要是看传入了什么接口,然后会从工map中获取对应有哪些实现,然后将这些实现一一启动
我从META-INF/spring.factories看到

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

很明显,Listener主要就是初始化这个监听器

在这里插入图片描述

9、设置应用程序参数

将执行run方法时传入的参数封装成一个对象

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

创建ApplicationArguments对象,封装了args参数

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        args = new String[]{"aaa","bbb","ccc"};
        ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
    }
}

10、准备环境变量

准备环境变量,包含系统属性和用户配置的属性,执行的代码块在 prepareEnvironment 方法内

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

在这里插入图片描述
打了断点之后可以看到,它将maven和系统的环境变量都加载进来了
在这里插入图片描述

12、打印 banner 信息

显而易见,这个流程就是用来打印控制台那个很大的spring的banner的,就是下面这个东东

在这里插入图片描述
那他在哪里打印的呢?他在 SpringBootBanner.java 里面打印的,这个类实现了Banner 接口,

而且banner信息是直接在代码里面写死的;
在这里插入图片描述
有些公司喜欢自定义banner信息,如果想要改成自己喜欢的图标该怎么办呢,其实很简单,只需要在resources目录下添加一个 banner.txt 的文件即可,txt文件内容如下

                 _           _
(_)         | |
_   _  _____  ___ _ __   __| | ___  _ __   __ _
| | | |/ _ \ \/ / | '_ \ / _` |/ _ \| '_ \ / _` |
| |_| |  __/>  <| | | | | (_| | (_) | | | | (_| |
\__, |\___/_/\_\_|_| |_|\__,_|\___/|_| |_|\__, |
__/ |                                     __/ |
|___/                                     |___/
:: yexindong::

一定要添加到resources目录下,别加错了
在这里插入图片描述
只需要加一个文件即可,其他什么都不用做,然后直接启动springboot,就可以看到效果了
在这里插入图片描述
13、创建应用程序的上下文
实例化应用程序的上下文, 调用 createApplicationContext() 方法,这里就是用反射创建对象,没什么好说的;

context = createApplicationContext();

14、实例化异常报告器
异常报告器是用来捕捉全局异常使用的,当springboot应用程序在发生异常时,异常报告器会将其捕捉并做相应处理,在spring.factories 文件里配置了默认的异常报告器,
在这里插入图片描述
需要注意的是,这个异常报告器只会捕获启动过程抛出的异常,如果是在启动完成后,在用户请求时报错,异常报告器不会捕获请求中出现的异常,
在这里插入图片描述
了解原理了,接下来我们自己配置一个异常报告器来玩玩;

MyExceptionReporter.java 继承 SpringBootExceptionReporter 接口

package com.spring.application;
import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;
public class MyExceptionReporter implements SpringBootExceptionReporter { 

private ConfigurableApplicationContext context;
// 必须要有一个有参的构造函数,否则启动会报错
MyExceptionReporter(ConfigurableApplicationContext context) { 

this.context = context;
}
@Override
public boolean reportException(Throwable failure) { 

System.out.println("进入异常报告器");
failure.printStackTrace();
// 返回false会打印详细springboot错误信息,返回true则只打印异常信息 
return false;
}
}

在 spring.factories 文件中注册异常报告器

# Error Reporters 异常报告器
org.springframework.boot.SpringBootExceptionReporter=\
com.spring.application.MyExceptionReporter

在这里插入图片描述
接着我们在application.yml 中 把端口号设置为一个很大的值,这样肯定会报错,

server:
port: 80828888

启动后,控制台打印如下图

在这里插入图片描述
15、准备上下文环境
这里准备的上下文环境是为了下一步刷新做准备的,里面还做了一些额外的事情;
在这里插入图片描述
15.1、实例化单例的beanName生成器
在 postProcessApplicationContext(context); 方法里面。使用单例模式创建 了BeanNameGenerator 对象,其实就是beanName生成器,用来生成bean对象的名称

15.2、执行初始化方法
初始化方法有哪些呢?还记得第3步里面加载的初始化器嘛?其实是执行第3步加载出来的所有初始化器,实现了ApplicationContextInitializer 接口的类

15.3、将启动参数注册到容器中
这里将启动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的beanName 为 :springApplicationArguments

16、刷新上下文
刷新上下文已经是spring的范畴了,自动装配和启动 tomcat就是在这个方法里面完成的,还有其他的spring自带的机制在这里就不一一细说了,
在这里插入图片描述
17、刷新上下文后置处理
afterRefresh 方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的,

/** * Called after the context has been refreshed. * @param context the application context * @param args the application arguments */
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) { 

}

18、结束计时器
到这一步,springboot其实就已经完成了,计时器会打印启动springboot的时长
在这里插入图片描述
在控制台看到启动还是挺快的,不到2秒就启动完成了;

20、执行自定义的run方法

这是一个扩展功能,callRunners(context, applicationArguments) 可以在启动完成后执行自定义的run方法;有2中方式可以实现:

实现 ApplicationRunner 接口
实现 CommandLineRunner 接口
接下来我们验证一把,为了方便代码可读性,我把这2种方式都放在同一个类里面

package com.spring.init;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/** * 自定义run方法的2种方式 */
@Component
public class MyRunner implements ApplicationRunner, CommandLineRunner { 

@Override
public void run(ApplicationArguments args) throws Exception { 

System.out.println(" 我是自定义的run方法1,实现 ApplicationRunner 接口既可运行"        );
}
@Override
public void run(String... args) throws Exception { 

System.out.println(" 我是自定义的run方法2,实现 CommandLineRunner 接口既可运行"        );
}
}

启动springboot后就可以看到控制台打印的信息了
在这里插入图片描述

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

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

相关文章

一文读懂最新的A股交易手续费,建议收藏

为了活跃资本市场&#xff0c;最近政策密集出台&#xff0c;印花税下降50%&#xff0c;交易经手费下降30%。 财政部、税务总局&#xff1a;为活跃资本市场、提振投资者信心&#xff0c;自2023年8月28日起&#xff0c;证券交易印花税实施减半征收 上交所发布《关于调整股票交易…

【C++初阶】C++STL详解(四)—— vector的模拟实现

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 【C初阶】CSTL详解&#xff08;三…

day22集合01

1.Collection集合 1.1数组和集合的区别【理解】 相同点 都是容器,可以存储多个数据 不同点 数组的长度是不可变的,集合的长度是可变的 数组可以存基本数据类型和引用数据类型 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类 1.2集合类体系结构【理解】…

详谈操作系统中的内核态和用户态

不知道大家有没有思考过这样一个问题:什么是处理器&#xff08;CPU&#xff09;的状态&#xff1f;&#x1f914; 其实CPU和人一样,没有执行程序的时候,是没有什么状态的,当它执行的程序是用户程序的时候就叫用户态&#xff0c;当执行的程序是操作系统的代码时就叫系统态或者内…

C++---继承

继承 前言继承的概念及定义继承的概念继承定义继承关系和访问限定符 基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承与友元继承与静态成员**多重继承**多继承下的类作用域菱形继承虚继承使用虚基类 支持向基类的常规类型转换 前言 在需要写Father类和Mother…

嵌入式Linux驱动开发(I2C专题)(五)

I2C系统驱动程序模型 参考资料&#xff1a; Linux内核文档: Documentation\i2c\instantiating-devices.rstDocumentation\i2c\writing-clients.rst Linux内核驱动程序示例: drivers/eeprom/at24.c 1. I2C驱动程序的层次 I2C Core就是I2C核心层&#xff0c;它的作用&#xf…

20230916在WIN10的资源管理器里关闭最右边的预览窗口

20230916在WIN10的资源管理器里关闭最右边的预览窗口 百度搜索&#xff1a;WIN10 干掉预览模式 https://zhidao.baidu.com/question/1807564480928861867.html win10计算机预览窗口怎么关  我来答 首页 用户 合伙人 商城 法律 手机答题 我的 win10计算机预览窗口怎么关  …

知识库管理工具哪个好?我建议你可以试一下这个!

对于很多企业/用户来说&#xff0c;在职业成长和个人发展的过程中&#xff0c;是需要借助知识库管理工具来进行知识内容沉淀的。 随着工具市场的发展&#xff0c;各种知识库管理工具层出不穷&#xff0c;今天我就结合数据安全、知识管理体系、简单实用三个方面出发&#xff0c;…

flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能

flutter聊天界面-TextField输入框buildTextSpan实现功能展示高亮功能 最近有位朋友讨论的时候&#xff0c;提到了输入框的高亮展示。在flutter TextField中需要插入特殊样式的标签&#xff0c;比如&#xff1a;“请 张三 回答一下”&#xff0c;这一串字符在TextField中输入&a…

day23集合02

1.泛型 1.1泛型概述 泛型的介绍 ​ 泛型是JDK5中引入的特性&#xff0c;它提供了编译时类型安全检测机制 泛型的好处 把运行时期的问题提前到了编译期间避免了强制类型转换 泛型的定义格式 <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:…

多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出

多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出 目录 多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 Matlab实现PSO-BP粒子群优化BP神经网络多输入多输出预测 1.data为数据…

小程序引入vant-Weapp保姆级教程及安装过程的问题解决

小知识&#xff0c;大挑战&#xff01;本文正在参与“程序员必备小知识”创作活动。 本文同时参与 「掘力星计划」&#xff0c;赢取创作大礼包&#xff0c;挑战创作激励金 当你想在小程序里引入vant时&#xff0c;第一步&#xff1a;打开官方文档&#xff0c;第二步&#xff…

Linux C/C++实现SSL的应用层VPN (MiniVPN)

SSL协议和VPN&#xff08;虚拟私人网络&#xff09;原理是网络安全领域中的两个重要概念。 SSL协议&#xff0c;全称安全套接层&#xff08;Secure Sockets Layer&#xff09;&#xff0c;是一种广泛应用于互联网的安全协议&#xff0c;主要在两个通信端点之间建立安全连接&am…

深度解剖数据在栈中的应用

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 望小伙伴们点赞&#x1f44d;收藏✨加关注哟&#x1f495;&#x1…

Vue 2 组件间的通信方式总结

引言 组件间的关系有父子关系、兄弟关系、祖孙关系和远亲关系。 不同的关系间&#xff0c;组件的通信有不同的方式。 一、prop 和 $emit prop向下传递&#xff0c;emit向上传递。 父组件使用 prop 向子组件传递信息。 ParentComponent.vue <template><div><…

红心向阳 百鸟朝凤

背景 最近在玩 folium 模块&#xff0c;基于使用过程中的一些个人体验&#xff0c;对 folium 进行了二次封装&#xff0c;开源在 GpsAndMap.在使用的过程中&#xff0c;发现在地图上打图标是可以进行旋转的。遇到就发现了一些有意思的玩法。 隔海的相望 下面的代码在地图 厦…

GIS跟踪监管系统信息查询

GIS跟踪监管系统信息查询 GIS跟踪监管系统&#xff08;1&#xff09;物资查询与展示。① 几何查询。代码说明&#xff1a;② 物资定位。• 单个物资定位&#xff1a;• 多个物资定位&#xff1a;③ 物资统计。&#xff08;2&#xff09;物资信息更新① 新增物资。 GIS跟踪监管系…

【项目经验】:elementui表格中数字汉字排序问题及字符串方法localeCompare()

一.需求 表格中数字汉字排序&#xff0c;数字按大小排列&#xff0c;汉字按拼音首字母&#xff08;A-Z&#xff09;排序。 二.用到的方法 第一步&#xff1a;把el-table-column上加上sortable"custom" <el-table-column prop"date" label"序号…

第七章 查找 一、查找的基本概念

一、基本概念 查找——在数据集合中寻找满足某种条件的数据元素的过程称为查找。 查找表(查找结构)——用于查找的数据集合称为查找表&#xff0c;它由同一类型的数据元素(或记录)组成。 关键字——数据元素中唯一标识该元素的某个数据项的值&#xff0c;使用基于关键字的查…

2023年的深度学习入门指南(27) - CUDA的汇编语言PTX与SASS

通过前面的学习&#xff0c;我们了解了在深度学习和大模型中&#xff0c;GPU的广泛应用。可以说&#xff0c;不用说没有GPU&#xff0c;就算是没有大显存和足够先进的架构&#xff0c;也没法开发大模型。 有的同学表示GPU很神秘&#xff0c;不知道它是怎么工作的。其实&#x…