Springboot之啓動分析

    Springboot版本是2.0.5.release.java

    以下List-1所示是咱們平時使用Springboot的方式,底層上發生了些什麼呢,咱們接下來分析下。react

    List-1git

@SpringBootApplication
public class HelloApplication {

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

    SpringApplication的靜態方法run,最終會先構造SpringApplication以後調用run方法,以下List-2,primarySources是咱們傳入的HelloApplication,args數組是List-1中傳入的args。github

    List-2web

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

一、SpringApplication的構造方法

    來看SpringApplication的構造方法,以下List-3,spring

    List-3設計模式

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = deduceWebApplicationType();//1
    setInitializers((Collection) getSpringFactoriesInstances( //2
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//3
    this.mainApplicationClass = deduceMainApplicationClass();//4
}
  1. 判斷是servlet web、reactive web仍是普通的應用,如List-4所示,根據classpath是否有對應的類判斷是哪一種類型,當咱們引入spring web依賴和servlet依賴,則是servlet web應用。
  2. 從spring.factories中得到全部ApplicationContextInitializer對應的值,以後實例化並進行排序,以後所有添加到SpringApplication的屬性initializers中。
  3. 從spring.factories中得到全部ApplicationListener的值,以後實例化並進行排序,以後所有添加到SpringApplication的屬性listeners中。
  4. 有意的拋出一個異常,以後判斷哪一個含有main方法,即List-1中的HelloApplication。

    List-4數組

private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
			+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
			+ "web.servlet.DispatcherServlet";
private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

二、SpringApplication的run方法

    以下List-5,內容看着有些多,咱們逐個深刻tomcat

    List-5springboot

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);//1
    listeners.starting();//2
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);//3
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);//4
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);//5
        context = createApplicationContext();//6
        exceptionReporters = getSpringFactoriesInstances(//7
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,//8
                printedBanner);
        refreshContext(context);//9
        afterRefresh(context, applicationArguments);//10
        stopWatch.stop();//11
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)//12
                    .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);//13
        callRunners(context, applicationArguments);//14
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);//15
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}
  1. 從spring.factories中獲取SpringApplicationRunListener的值,實例化、排序,以後放到SpringApplicationRunListeners中,SpringApplicationRunListeners使用了組合設計模式。
  2. 調用starting方法。
  3. 構造DefaultApplicationArguments實例。
  4. 獲取environment,4裏面有不少操做,如List-6所示,getOrCreateEnvironment()方法根據類型建立對應的environment,咱們通常是servlet web應用,則建立StandardServletEnvironment;"configureEnvironment(environment, applicationArguments.getSourceArgs())"將List-1中傳到main函數的參數數組解析到environment;"bindToSpringApplication(environment)"將environment綁定到SpringApplication中。
  5. 打印banner,如List-7所示,bannerMode默認是Banner.Mode.CONSOLE,即打印到控制檯,2位置處打印banner,默認使用的是SpringBootBanner,如List-8所示,List-8中的printBanner方法的參數printStream默認是System.out。
  6. 會根據應用的類型返回對應的applicationContext,以servlet web爲例,建立的是AnnotationConfigServletWebServerApplicationContext,具體較爲簡單,能夠看源碼。
  7. 從spring.factories中獲取SpringBootExceptionReporter的值,實例化排序。
  8. 該方法裏面沒有太多的操做,會逐個調用SpringApplication的ApplicationContextInitializer.initialize()方法,以後會將environment和printBanner註冊到Spring的beanFactory裏面。
  9. 實際上是調用AbstractApplicationContext的refresh()方法,AbstractApplicationContext的refresh方法裏面有不少內容,這裏不詳細講。Springboot啓動tomcat就是和這裏有關了。
  10. 這個方法爲空。
  11. StopWatch的stop方法,記錄啓動springboot用時多少。
  12. 將Springboot啓動用時多少時間打印出來,這個就是咱們平時在控制檯看到相似"15:57:28.657 INFO  Started HelloApplication in 4.643 seconds (JVM running for 5.18)"的代碼,如List-9所示。
  13. 調用listeners的started方法。
  14. 如List-10,從BeanFactory中獲取ApplicationRunner和CommandLineRunner,以後排序,再逐個調用run方法。

    List-6

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}

    List-7

private Banner printBanner(ConfigurableEnvironment environment) {
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }
    ResourceLoader resourceLoader = (this.resourceLoader != null)
            ? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(//1
            resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);//2
}

    List-8

class SpringBootBanner implements Banner {

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

	private static final String SPRING_BOOT = " :: Spring Boot :: ";

	private static final int STRAP_LINE_SIZE = 42;

	@Override
	public void printBanner(Environment environment, Class<?> sourceClass,
			PrintStream printStream) {
		for (String line : BANNER) {
			printStream.println(line);
		}
		String version = SpringBootVersion.getVersion();
		version = (version != null) ? " (v" + version + ")" : "";
		StringBuilder padding = new StringBuilder();
		while (padding.length() < STRAP_LINE_SIZE
				- (version.length() + SPRING_BOOT.length())) {
			padding.append(" ");
		}

		printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
				AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version));
		printStream.println();
	}
}

    List-9

private StringBuilder getStartedMessage(StopWatch stopWatch) {
    StringBuilder message = new StringBuilder();
    message.append("Started ");
    message.append(getApplicationName());
    message.append(" in ");
    message.append(stopWatch.getTotalTimeSeconds());
    try {
        double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
        message.append(" seconds (JVM running for " + uptime + ")");
    }
    catch (Throwable ex) {
        // No JVM time available
    }
    return message;
}

    List-10

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);
        }
    }
}

三、Springboot是如何啓動內嵌的tomcat的

    看List-5的9裏面,調用的是AbstractApplicationContext.refresh->AbstractApplicationContext->onRefresh()->ServletWebServerApplicationContext.onRefresh(),AnnotationConfigServletWebServerApplicationContext的父類是ServletWebServerApplicationContext,來看下ServletWebServerApplicationContext的onRefresh方法,如List-11所示,createWebServer()裏面,建立web服務容器,有多是tomcat/jetty等。這樣就建立好了web容器,以後refresh完成後就啓動web容器了。

    List-11

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    initPropertySources();
}

    思考: 咱們能夠修改成使用jetty容器而不是tomcat這是怎麼作到的? 看List-11中getWebServerFactory(),從beanFactory中獲取ServletWebServerFactory類型的bean,以後用該工廠建立webServer。WebServerFactory的實現類不少,具體使用哪一個:

    ServletWebServerFactoryConfiguration以下List-2,根據ConditionalOnClass然後注入不一樣的WebServerFactory實現,其實還有個ReactiveWebServerFactoryConfiguration,這裏面的是reactive的。

    List-2

@Configuration
class ServletWebServerFactoryConfiguration {

	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
			return new TomcatServletWebServerFactory();
		}

	}

	/**
	 * Nested configuration if Jetty is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
			WebAppContext.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyServletWebServerFactory JettyServletWebServerFactory() {
			return new JettyServletWebServerFactory();
		}

	}

	/**
	 * Nested configuration if Undertow is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedUndertow {

		@Bean
		public UndertowServletWebServerFactory undertowServletWebServerFactory() {
			return new UndertowServletWebServerFactory();
		}
	}
}

 

Reference

  1. springboot源碼
相關文章
相關標籤/搜索