微服务开发(16)--SpringBoot源码分析外部tomcat启动war包的原理

  • 前面我们已经介绍了SpringBoot的内置Tomcat的启动过程原理
  • 那么,SpringBoot打包成war包的时候,又是怎么运行的?
  • 在此之前先记结论
    SpringBoot程序是jar的方式,是通过IOC容器启动的方式,带动了Tomcat的启动;若SpringBoot程序是war包的方式,则是Tomcat启动带动了IOC容器的启动

如何写一个war类型的SpringBoot程序

  1. 首先打包的类型选择为war
  2. 导入的maven依赖中,排除内置的Tomcat,因为我们需要放在自己的Tomcat容器中去执行
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
</dependency>
  1. 在主启动类中继承SpringBootServletInitializer类,并重写其configure方法,该方法的作用就是指明其主启动类
package com.xiyou.war;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
 * @author 92823
 */
@SpringBootApplication
public class SpringBootWarApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootWarApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(SpringBootWarApplication.class);
    }
}

带着问题看源码:
  • 为什么我们需要在启动类上继承SpringBootServletInitializer 类?
  • 我们重写的configure方法又是在什么时候触发调用的呢?

源码解析

  1. 首先从Servlet3.0的特性开始说起

(1)Web应用启动的时候,会创建当前Web应用导入jar包中的ServletContainerInitializer类的实例
(2)ServletContainerInitializer类必须放在jar包的META-INF/services目录下,文件名称为javax.servlet.ServletContainerInitializer在这里插入图片描述
(3)文件的内容指向了ServletContainerInitializer实现类的全路径
(4)使用@HandlesTypes在我们应用启动的时候,加载我们感兴趣的类

  1. 观察加载的类
/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;


@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {


	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {
		// 保存感兴趣的类的集合
		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				// 排除感兴趣的类中的接口和抽象类
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						// 通过反射创建这些对象
						// 我们的主启动类因为继承SpringBootServletInitializer 的关系也是一个感兴趣的类
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		// 循环调用集合中的感兴趣的类的对象的onstartup方法,其中我们的主启动类就会在这里调用onStartup方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

  1. 观察其onstart方法
  • 因为我们的主启动类中没有onstartup方法,所以我们调用的实际是父类的onstartup方法
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

.......
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// Logger initialization is deferred in case an ordered
		// LogServletContextInitializer is being used
		this.logger = LogFactory.getLog(getClass());
		// 创建Web应用的上下文对象
		WebApplicationContext rootAppContext = createRootApplicationContext(
				servletContext);
		if (rootAppContext != null) {
			servletContext.addListener(new ContextLoaderListener(rootAppContext) {
				@Override
				public void contextInitialized(ServletContextEvent event) {
					// no-op because the application context is already initialized
				}
			});
		}
		else {
			this.logger.debug("No ContextLoaderListener registered, as "
					+ "createRootApplicationContext() did not "
					+ "return an application context");
		}
	}
.......
  • 我们继续追踪代码,跟进WebApplicationContext rootAppContext = createRootApplicationContext(
    servletContext);
	protected WebApplicationContext createRootApplicationContext(
			ServletContext servletContext) {
		// 创建Spring的应用的构建器
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		builder.main(getClass());
		// 设置环境
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(
				new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
		// 注意:因为我们已经重写了configure方法,所以实际上调用的是我们的configure方法
		// 此时的application已经指向了我们的主启动类了
		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()));
		}
		Assert.state(!application.getAllSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");
		// Ensure error pages are registered
		if (this.registerErrorPageFilter) {
			application.addPrimarySources(
					Collections.singleton(ErrorPageFilterConfiguration.class));
		}
		// 调用run方法,正式开启加载IOC
		return run(application);
	}
  • 继续观察上面的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);
			// 启动我们的IOC容器
			// 这个就开始IOC的加载了
			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;
	}

执行到此我们已经开始加载IOC容器,我们可以明确是Tomcat的启动带动了IOC容器的启动,通过onStartUp方法逐渐引入到run方法的执行,引入到Spring IOC容器的加载

总结
  1. Tomcat的Host容器在添加子容器的时候,会通过类加载器加载@HandlesTypes注解的类
  2. 读取@HandlesTypes注解的value值,并将其放入到ServletContainerInitializers 对应的Set集合中
  3. ApplicationContext内部启动的时候会通知ServletContainerInitializer的onStart方法
  4. 通过onStart方法,执行自己实现的configure方法,指明主类,执行run方法,加载Spring的IOC
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值