关于springboot:深入学习-Spring-Web-开发-启动日志

45次阅读

共计 8025 个字符,预计需要花费 21 分钟才能阅读完成。

上一篇文章,咱们紧紧围绕 @SpringBootApplication 引入的注解和类,对 Spring Boot 我的项目的启动过程做了一次剖析。在理论的开发过程中,我的项目的代码毫无疑问是与咱们最为相干的,另外,咱们也不可漠视我的项目日志在咱们日常开发中所起的作用。因而,本文将围绕我的项目的启动日志,对我的项目的启动过程再做一次剖析,以便于咱们更好地了解我的项目的运行逻辑。本系列文章,笔者将会应用较多的笔墨展现代码与日志的互相关联,心愿通过这样的形式,能够缓缓让读者造就起一种代码与日志彼此贯通的思路,以帮忙读者在理论开发过程中,更好地解决所遇到的问题。

上面是我的项目的一次启动日志:


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

2022-09-08 08:28:22.964  INFO 54995 --- [main] o.s.springweb.HelloWorldApplication      : Starting HelloWorldApplication using Java 11.0.12 on susamludeMac.local with PID 54995 (/Users/susamlu/code/java/spring-web/spring-web-helloworld/target/classes started by susamlu in /Users/susamlu/code/java/spring-web)
2022-09-08 08:28:22.968  INFO 54995 --- [main] o.s.springweb.HelloWorldApplication      : No active profile set, falling back to 1 default profile: "default"
2022-09-08 08:28:24.900  INFO 54995 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-09-08 08:28:24.912  INFO 54995 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-09-08 08:28:24.913  INFO 54995 --- [main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-09-08 08:28:25.090  INFO 54995 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-09-08 08:28:25.091  INFO 54995 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1910 ms
2022-09-08 08:28:25.857  INFO 54995 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-09-08 08:28:25.873  INFO 54995 --- [main] o.s.springweb.HelloWorldApplication      : Started HelloWorldApplication in 3.735 seconds (JVM running for 4.41)

SpringApplication 的动态 run() 办法,最终会调用到本身的实例 run() 办法。实例 run() 办法的内容会绝对比较复杂,为了简化其中的逻辑,咱们重点关注 printBanner()、prepareContext()、refreshContext() 几个办法。

public class SpringApplication {

    // ...

    public ConfigurableApplicationContext run(String... args) {
        // ...
        Banner printedBanner = printBanner(environment);
        // ...
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        refreshContext(context);
        // ...
    }

    // ...

}

printBanner

printBanner() 见名知意,是用来打印 Spring Boot 我的项目的 Banner 的:


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

SpringApplication 调用 SpringApplicationBannerPrinter 的 print() 办法打印 Banner。

public class SpringApplication {

    // ...

    private Banner printBanner(ConfigurableEnvironment environment) {
        // ...
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
        // ...
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

    // ...

}

SpringApplicationBannerPrinter 优先打印 ImageBanner 或 TextBanner,如果没有设置 ImageBanner 和 TextBanner,则打印 Spring Boot 默认的 Banner。

class SpringApplicationBannerPrinter {

    // ...

    // TextBanner 的文件名
    static final String DEFAULT_BANNER_LOCATION = "banner.txt";

    // ImageBanner 反对的后缀格局
    static final String[] IMAGE_EXTENSION = {"gif", "jpg", "png"};

    // 默认的 Banner
    private static final Banner DEFAULT_BANNER = new SpringBootBanner();

    // ...

    Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {Banner banner = getBanner(environment);
        banner.printBanner(environment, sourceClass, out);
        return new PrintedBanner(banner, sourceClass);
    }

    // ...

    private Banner getBanner(Environment environment) {Banners banners = new Banners();
          // 优先打印 ImageBanner
        banners.addIfNotNull(getImageBanner(environment));
        // 没有设置 ImageBanner,则优先打印 TextBanner
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {return banners;}
        // ...
        // ImageBanner、TextBanner 都没有设置,则打印 Spring Boot 默认的 Banner
        return DEFAULT_BANNER;
    }

    // ...

}

默认的 Banner 值的次要内容由 SpringBootBanner 类的一个常量值定义:

class SpringBootBanner implements Banner {private static final String[] BANNER = {"","  .   ____          _            __ _ _"," /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", "(()\\___ |'_ | '_| |'_ \\/ _` | \\ \\ \\ \\"," \\\\/  ___)| |_)| | | | | || (_| |) ) ) )","  '|____| .__|_| |_|_| |_\\__, | / / / /",
            "=========|_|==============|___/=/_/_/_/"};

    // ...

}

如果咱们想自定义 Banner,咱们能够在我的项目的 resources 目录下搁置 banner.txt 文件,从而扭转 Banner 的打印。如我的 banner.txt 文件的内容为:

                     _    __               
    ____   ____ _   (_)  / /_   _____  ___ 
   / __ \ / __ `/  / /  / __/  / ___/ / _ \
  / /_/ // /_/ /  / /  / /_   (__) /  __/
 / .___/ \__,_/  /_/   \__/  /____/  \___/ 
/_/                                        

程序启动后打印的 Banner 信息被批改为:

                     _    __               
    ____   ____ _   (_)  / /_   _____  ___ 
   / __ \ / __ `/  / /  / __/  / ___/ / _ \
  / /_/ // /_/ /  / /  / /_   (__) /  __/
 / .___/ \__,_/  /_/   \__/  /____/  \___/ 
/_/                                        

prepareContext

prepareContext() 是其中十分重要的一个办法,该办法的作用在上一篇文章中就所有提及。

public class SpringApplication {

    // ...

    private void prepareContext(/* ... */) {
        // ...
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
        // ...
    }

    // ...

}

prepareContext() 通过 logStartupInfo()、logStartupProfileInfo() 两个办法,别离输入上面两句日志:

2022-09-08 08:28:22.964  INFO 54995 --- [main] o.s.springweb.HelloWorldApplication      : Starting HelloWorldApplication using Java 11.0.12 on susamludeMac.local with PID 54995 (/Users/susamlu/code/java/spring-web/spring-web-helloworld/target/classes started by susamlu in /Users/susamlu/code/java/spring-web)
2022-09-08 08:28:22.968  INFO 54995 --- [main] o.s.springweb.HelloWorldApplication      : No active profile set, falling back to 1 default profile: "default"

refreshContext

refreshContext() 也是其中的外围办法,Tomcat 的启动就产生在该办法的调用过程中。refreshContext() 最终会调用 AbstractApplicationContext 的 refresh() 办法,refresh() 又调用了本身的 onRefresh() 和 finishRefresh() 办法。

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {

    // ...

    public void refresh() throws BeansException, IllegalStateException {
        // ...
        onRefresh();
        // ...
        finishRefresh();
        // ...
    }

    // ...

}

其中 onRefresh() 理论调用的是 ServletWebServerApplicationContext 的 onRefresh() 办法,而后再通过 ServletWebServerFactory 获取 WebServer。

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
        implements ConfigurableWebServerApplicationContext {

    // ...

    protected void onRefresh() {
        // ...
        createWebServer();
        // ...
    }

    // ...

    private void createWebServer() {
        // ...
        ServletWebServerFactory factory = getWebServerFactory();
        // ...
        this.webServer = factory.getWebServer(getSelfInitializer());
        // ...
    }

    // ...

}

ServletWebServerFactory 会创立 TomcatWebServer,创立 TomcatWebServer 的时候,它的 initialize() 办法会被主动调用,initialize() 会启动 Tomcat 并输入相干日志。

public class TomcatWebServer implements WebServer {

    // ...

    private void initialize() throws WebServerException {logger.info("Tomcat initialized with port(s):" + getPortsDescription(false));
        // ...
        this.tomcat.start();
        // ...
    }

    // ...

}
2022-09-08 08:28:24.900  INFO 54995 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-09-08 08:28:24.912  INFO 54995 --- [main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-09-08 08:28:24.913  INFO 54995 --- [main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-09-08 08:28:25.090  INFO 54995 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-09-08 08:28:25.091  INFO 54995 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1910 ms

Tomcat 启动完,onRefresh() 也接着调用实现,再接着 finishRefresh() 会调用 TomcatWebServer 的 start() 办法,输入 Tomcat 启动实现的日志。

public class TomcatWebServer implements WebServer {

    // ...

    public void start() throws WebServerException {
        // ...
        logger.info("Tomcat started on port(s):" + getPortsDescription(true) + "with context path'"
                + getContextPath() + "'");
        // ...
    }

    // ...

}
2022-09-08 08:28:25.857  INFO 54995 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''

下面的办法都调用完后,最终又回到了 SpringApplication 的 run() 办法,run() 最初输入利用启动实现的日志。

public class SpringApplication {
    
    // ...

    public ConfigurableApplicationContext run(String... args) {
        // ...
        new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        // ...
    }

    // ...
    
}
2022-09-08 08:28:25.873  INFO 54995 --- [main] o.s.springweb.HelloWorldApplication      : Started HelloWorldApplication in 3.735 seconds (JVM running for 4.41)

整个流程,能够总结为以下一张图:

回顾

上一篇文章,咱们以一张图展现了我的项目启动的流程,本文也同样总结成了一张图,对于这两张图所波及到的过程,它们相互之间的程序是怎么样的呢?

当咱们把这两张图拿来比照,并对照相干的源代码,不难发现,SpringFactoriesLoader 的 loadSpringFactories() 办法是最先被触发的,接着,开始输入 Spring Banner,再接着,开始执行 logStartupInfo()、logStartupProfileInfo() 两个办法。下面办法都执行完之后,接着,SpringApplication 的 load() 办法开始执行,再接着就是配置类和主动配置类的解析。这些都执行完了之后,就开始启动 Tomcat,始终到整个我的项目启动实现。这当中的程序,能够用上面一张图来示意:

正文完
 0