SpringBoot的启动原理

我们启动一个SpringBoot项目只需要执行java -jar xxx.jar命令就行了,这个时候我们就启动了一个web服务器容器,可以在浏览器上通过接口地址访问对应的接口了,但是整个启动过程对于我们来说是透明的,还是有必要了解下的。

启动前置

  • java -jar命令:这个命令会找到jar中的manifest文件,然后执行里面配置的Main-Class类;
  • MANIFEST文件:
    Spring Boot中MANIFEST文件是/META-INF/MANIFEST.MF文件,看下它里面的内容:
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: sdk-api-demo
Implementation-Version: 1.0.0
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.xxx.xxx.XxxApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.6.1
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher

最后面配置了我们需要的Main-Class,所以当我们执行java -jar命令时,实际执行的是org.springframework.boot.loader.JarLauncher类。往上看还有一个Start-Class参数配置,可以看到这个参数配置的值才是我们应用项目的启动类,所以可以猜测应该是在JarLauncher类中有对XxxApplication类的调用。

  • JarLauncher类:这是整个应用启动的入口类,他会加载/BOOT-INF/lib/下所有的依赖jar包,并且会创建一个新线程来执行main方法,看下它的源码,哦,对了,想要看源码要先加入依赖:
	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-loader</artifactId>
        <scope>provided</scope>
    </dependency>

源码:

public class JarLauncher extends ExecutableArchiveLauncher {

	static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

	static final String BOOT_INF_LIB = "BOOT-INF/lib/";

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		if (entry.isDirectory()) {
			return entry.getName().equals(BOOT_INF_CLASSES);
		}
		return entry.getName().startsWith(BOOT_INF_LIB);
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}
}

这个类实际上没什么内容,主要看下launch方法,这里的launch方法调用的是Launcher#launch方法:

	protected void launch(String[] args) throws Exception {
		JarFile.registerUrlProtocolHandler();
		ClassLoader classLoader = createClassLoader(getClassPathArchives());
		// 看这里的getMainClass方法
		launch(args, getMainClass(), classLoader);
	}

这里的getMainClass()方法会去读取上面Start-Class参数所配置的class路径,ExecutableArchiveLauncher#getMainClass:

	@Override
	protected String getMainClass() throws Exception {
		// MANIFEST文件
		Manifest manifest = this.archive.getManifest();
		String mainClass = null;
		if (manifest != null) {
			// 得到Start-Class配置的值
			mainClass = manifest.getMainAttributes().getValue("Start-Class");
		}
		if (mainClass == null) {
			throw new IllegalStateException(
					"No 'Start-Class' manifest entry specified in " + this);
		}
		return mainClass;
	}

得到Start-Class配置的值之后,经过一系列的调用栈,会到MainMethodRunner#run方法:

	public void run() throws Exception {
		// mainClassName就是Start-Class的值
		Class<?> mainClass = Thread.currentThread().getContextClassLoader()
				.loadClass(this.mainClassName);
		// 获取main方法
		Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
		// 利用反射来调用配置类的main方法
		mainMethod.invoke(null, new Object[] { this.args });
	}

到这里,整个流程就到了我们熟悉的XxxApplication.main(String[] args)方法中了。

从XxxApplication.main(String[] args)方法开始启动

从XxxApplication中的main方法开始,就是启动spring容器的流程了,哦,还要启动web容器。从main方法沿着调用栈可以找到SpringApplication#run方法的调用。

  • SpringApplication#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;
	}

先忽略其他的,看看refreshContext(context)的调用,这个是spring容器初始化的方法,然后再沿着调用栈就可以到SpringApplication#refresh方法。

  • SpringApplication#refresh方法:
	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

看到了吧,调用了AbstractApplicationContext#refresh方法,看到ioc容器初始化过程的对这个方法太熟悉了,剩下的过程就是spring的内容了。哦,还有容器的启动,在refresh方法中,有一个onRefresh方法的调用,这个方法是一个钩子方法,在spring中没有任何实现,容器的启动就是在这个方法中实现的。

  • ReactiveWebServerApplicationContext#onRefresh
	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
			createWebServer();
		}
		catch (Throwable ex) {
			throw new ApplicationContextException("Unable to start reactive web server",
					ex);
		}
	}
  • ReactiveWebServerApplicationContext#createWebServer
	private void createWebServer() {
		WebServer localServer = this.webServer;
		// 这个主要是为了兼容外置web容器启动
		// 如果已经有了webServer,就不会再去启动一个webServer了
		if (localServer == null) {
			this.webServer = getWebServerFactory().getWebServer(getHttpHandler());
		}
		initPropertySources();
	}

看注释吧,接着看下getWebServer方法,我们一般用的是tomcat,所以就看下tomcat的实现吧。

  • TomcatReactiveWebServerFactory#getWebServer
	@Override
	public WebServer getWebServer(HttpHandler httpHandler) {
		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());
		TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
		prepareContext(tomcat.getHost(), servlet);
		// 看这里
		return new TomcatWebServer(tomcat, getPort() >= 0);
	}

这个方法主要是创建了一个Tomcat对象,填充了一些属性,主要的创建工作还是在new TomcatWebServer中,new TomcatWebServer中主要是调用了TomcatWebServer#initialize方法,直接看这个方法吧。

  • TomcatWebServer#initialize:
	private void initialize() throws WebServerException {
		TomcatWebServer.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())) {
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});

				// 看这里,tomcat启动了
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
				    // 绑定类加载器 
					ContextBindings.bindClassLoader(context, context.getNamingToken(),
							getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}

				// 启动一个后台线程运行,不能main方法跑完了,tomcat线程就不在了。
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

这个方法就是启动tomcat的方法,到这里,tomcat也启动起来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值