Springboot之启动分析

    Springboot版本是2.0.5.release.

    如下List-1所示是我们平时使用Springboot的方式,底层上发生了些什么呢,我们接下来分析下。

    List-1

@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。

    List-2

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

1、SpringApplication的构造方法

    来看SpringApplication的构造方法,如下List-3,

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

2、SpringApplication的run方法

    如下List-5,内容看着有些多,我们逐个深入

    List-5

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

3、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源码

转载于:https://my.oschina.net/u/2518341/blog/3063871

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值