SpringBoot源码分析(二)启动原理

Springboot的jar启动方式,是经过IOC容器启动 带动了Web容器的启动java

而Springboot的war启动方式,是经过Web容器(如Tomcat)的启动 带动了IOC容器相关的启动react

1、不可不说的Web容器(如Tomcat)

无论是jar启动仍是war包启动,都绕不开web容器相关。先了解这个怎么工做的,以Tomcat为例,web

看看Springboot 怎么来自动装配tomcat 相关的组件?spring

1.1 相关类

相关包org.springframework.boot.autoconfigure.web,在springboot的自动配置包的web下(自动配置功能都在这个autoconfigure包下)。tomcat

embedded(内嵌)里面四个类一个A四B,一个:springboot

EmbeddedWebServerFactoryCustomizerAutoConfiguration(内嵌web容器工厂自定义定制器装配类)app

四个具体容器相关:less

JettyWebServerFactoryCustomizer、NettyWebServerFactoryCustomizer、TomcatWebServerFactoryCustomizer、UndertowWebServerFactoryCustomizeride

一个自动配置类+四个经常使用web容器定制器工具

1.2.工做流程

以Tomcat定制器切入,断点落在构造器上,启动。

总结出它的工做流程:

1.启动=》2.createWebServer=》

3.拿TomcatServletWebServerFactory(tomcatWeb容器工厂)=》

4.拿WebServerFactoryCustomizer(工厂定制器)

也就是拿工厂定制器获取工厂,再拿工厂获取web容器,这么个流程

1.3.具体工做

能够仔细看下相关工厂是如何配置建立容器运行的

TomcatServletWebServerFactory的建立Tomcat方法

public WebServer getWebServer(ServletContextInitializer... initializers) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory
				: createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		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);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}

建立Tocmat类并设置相关这些组件,应该很熟悉(之后出Tomcat源码分析)。

TomcatWebServerFactoryCustomizer的customize定制方法,经过类serverProperties配置文件设置工厂的属性

2、SpringBoot的jar启动方式

来自:

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

打开源码:

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

其实两步,一步建立SpringApplication ,一步run运行。

建立类作的事情比较简单,主要包括判断web应用类型、用SpringFactories技术从 spring.factories 文件里获取ApplicationContextInitializer 对应类和ApplicationListener,最后获取当前应用的启动类的类对象。

2.1 run方法

public ConfigurableApplicationContext run(String... args) {
        //StopWatch是记录时间的工具,为了打印那句SpringBoot启动耗时的
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //系统设置,在缺失显示屏、鼠标或者键盘时也让这个java应用相关正常工做
		configureHeadlessProperty();
        //去meta-info/spring.factories中获取SpringApplicationRunListener 监听器(事件发布监听器)
		SpringApplicationRunListeners listeners = getRunListeners(args);
        //发布容器 starting事件(for循环一个个调用,经过spring的事件多播器)
		listeners.starting();
		try {
            //封装命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
            //准备容器环境
            //1:获取或者建立环境
            //2:把命令行参数设置到环境中
            //3:经过监听器发布环境准备事件
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
            //配置是否跳过搜索BeanInfo类,默认忽略跳过
			configureIgnoreBeanInfo(environment);
            //打印控制台那个SpringBoot图标
			Banner printedBanner = printBanner(environment);
            //根据类型(servlet或者reactive?)建立应用上下文ApplicationContext
			context = createApplicationContext();
            //到spring.factoris文件里拿springboot异常报告类的集合
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            //准备环境
            //1:把应用上下文ApplicationContext环境设置到容器中
            //2:循环调用AppplicationInitnazlier 进行容器初始化工做
            //3:发布 容器上下文准备 完成事件
            //4:注册关于springboot特定特性的相关单例Bean
            //5:BeanDefinitionLoader加载资源源码,将启动类注入容器
            //6:发布 容器上下文加载 完毕事件
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
            //IOC容器refresh,见之前IOC源码分析
			refreshContext(context);
            //springboot2.x已经改为空方法,之前里面是后面的callRunners
			afterRefresh(context, applicationArguments);
            //计时(耗时统计)中止
			stopWatch.stop();
            //打印那句springboot在多少秒内启动了
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
            //发布容器启动事件
			listeners.started(context);
            //运行 ApplicationRunner 和CommandLineRunner
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
            //发布容器运行事件
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

其实大可能是准备、工具、事件等,最核心的仍是里面的refreshContext(context);带动了IOC容器启动

2.2 refreshContext(context)

其实大部份内容在以前IOC容器源码写过,惟一的区别在于:

SpringIOC的refresh方法里的onRefresh方法是空的,而SpringBoot继承重写了这个方法!

SpringBoot的onRefresh:

@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start web server", ex);
		}
	}

SpringBoot里应用上下文是用的新的ServletWebServerApplicationContext类(更具体实现之一是AnnotationConfigServletWebServerApplicationContext)

这里就开始和上面说过的Web容器相关知识衔接上了,这里进行的Web容器(Tomcat)的建立运行!

createWebServer:

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) {
				//...
			}
		}
		initPropertySources();
	}

这里就是判断有没有Server以及环境,没有的话就获取web容器制造工厂,最后经过工厂获取Tomcat赋值。

实际上获取Tomcat建立的时候,此时构造器最后的代码就是启动,TomcatWebServer类构造器以下:

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

TomcatWebServer的initialize:

private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource())
							&& Lifecycle.START_EVENT.equals(event.getType())) {
						removeServiceConnectors();
					}
				});
				this.tomcat.start();
				rethrowDeferredStartupExceptions();
				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(),
							getClass().getClassLoader());
				}
				catch (NamingException ex) {
				}
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

最终在IOC 容器中的 org.springframework.context.support.AbstractApplicationContext的refresh 的
onReFresh方法带动了Tomcat启动

3、SpringBoot的war包启动方式

@SpringBootApplication
public class StudySpringbootApplication extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(StudySpringbootApplication.class);
    }
}

Springboot的war启动方式,是经过Web容器(如Tomcat)的启动 带动了IOC容器相关的启动

3.1 Tomcat加载war

要说Tomcat怎么加载war包就不得不从servlet3.0的特性提及:

1.web应用启动,会建立当前Web应用导入jar包中的 ServletContainerInitializer类的实例

2.ServletContainerInitializer 类必须放在jar包的 META-INF/services目录下,文件名称为javax.servlet.ServletContainerInitializer

3.文件的内容指向ServletContainerInitializer实现类的全路径
4.ServletContainerInitializer实现类使用@HandlesTypes注解, 在咱们应用启动的时候,加载注解指定的的类

3.2 Spring中的ServletContainerInitializer

Spring中实现ServletContainerInitializer的类是SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) {
		//建立保存须要加载的类的集合
		List<WebApplicationInitializer> initializers = new LinkedList<>();
        
		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
                //判断须要加载的类不是接口不是抽象类
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
                        //经过反射建立实例而且加入到集合中
						initializers.add((WebApplicationInitializer)
						ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						//...
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			return;
		}
		AnnotationAwareOrderComparator.sort(initializers);
        //循环调用集合中的感兴趣类对象的onstartup的方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

总结:HandlesTypes指定了WebApplicationInitializer类,并在onStartup方法中,建立这些须要加载的类的实例,而且循环调用他们的onStartup方法。

3.2 工做过程

1.Tomcat启动,war包应用的jar包里找ServletContainerInitializer 文件,而后找到spring-web-5.1.2.RELEASE.jar这个jar包里的META-INF\services\javax.servlet.ServletContainerInitializer 文件指向本身实现的SpringServletContainerInitializer并执行它

2.将@HandlesTypes标注的类(WebApplicationInitializer)都传入到 onStartup()的方法中Set<Class<?>>参数中
,经过 ReflectionUtils.accessibleConstructor(waiClass).newInstance());,为这些类建立实例

3.调用WebApplicationInitializer的onStartup方法

4.而Springboot启动类继承了SpringBootServletInitializer(实现了接口WebApplicationInitializer)

5.而咱们的启动类StudySpringbootApplication没有重写onStartup,调的SpringBootServletInitializer的onStartup

6.而SpringBootServletInitializer的onStartup方法调了咱们重写的configure方法,加载启动。

4.1 实战调试细节

@SpringBootApplication
public class StudySpringbootApplication extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(StudySpringbootApplication.class);
    }
}

能够把断点打到第六行里builder.sources,经过断点一看调用栈和代码,逻辑就全出来了:

StudySpringbootApplication.configure <<==== 父类SpringBootServletInitializer(主类继继承的这个类).createRootApplicationContext

父类SpringBootServletInitializer.createRootApplicationContext:

protected WebApplicationContext createRootApplicationContext(
			ServletContext servletContext) {
        //建立spring应用的构建器
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		builder.main(getClass());
        //设置环境
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			servletContext.setAttribute(
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(
				new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
        //调用咱们本身启动类上的confiure方法 传入咱们本身的主启动类
		builder = configure(builder);
		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty() && AnnotationUtils
				.findAnnotation(getClass(), Configuration.class) != null) {
			application.addPrimarySources(Collections.singleton(getClass()));
		}
	
		if (this.registerErrorPageFilter) {
			application.addPrimarySources(
					Collections.singleton(ErrorPageFilterConfiguration.class));
		}
        //调用咱们类上的run方法
		return run(application);
	}

注意重点是 调用了本身的方法(传入主类)和 run方法
run源码:

protected WebApplicationContext run(SpringApplication application) {
		return (WebApplicationContext) application.run();
	}

继续打开run源码:

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

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

是否是很熟悉,和jar启动的run IOC同样!因此最终仍是异曲同工,仍是走了application.run()方法,走了IOC容器启动Refresh!

相关文章
相关标签/搜索