Spring Boot (二) web项目的部署方式及启动原理

上篇文章中我们讲了Spring Boot的三种创建方式,本章我们来讲讲Spring Boot web项目两种部署方式,第一种是jar包部署;第二种是war包部署,本文将分别介绍两种方式的原理。

1 jar包部署

1.1 创建demo

首先创建Spring Boot项目,在pom.xml中引入WEB依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

启动类上加入springmvc注解

@SpringBootApplication
@RestController
public class SpringBootLearnDemo1Application {
    
    

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

    @RequestMapping("/hello")
    public String hello() {
    
    

        return "hello springboot";
    }
}

好了以上就是一个简单的demo,然后我们开始启动项目。

打成jar包,在jar包目录下通过java -jar 启动项目

在这里插入图片描述
启动成功如下,可以看到端口为8080

在这里插入图片描述
访问地址:http://127.0.0.1:8080/hello结果如下

在这里插入图片描述

1.2 启动原理

为什么通过java -jar 方式可以启动我们Spring Boot web项目呢?
首先我们知道java -jar 方式启动执行jar包执行的是项目根目录下面的/META-INF/MANIFEST.MF文件中配置的主类(Main-Class)的main方法,现在打开jar找到MANIFEST.MF文件如下

在这里插入图片描述
可以看到该文件见中有两个配置Main-Class,Start-Class,这些配置都是在我们打包时通过我们在pom.xml文件中配置的插件spring-boot-maven-plugin配置的
现在我们打开配置的Main-Class,打开该类需要引入如下依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-loader</artifactId>
</dependency>

打开JarLauncher找到main方法

在这里插入图片描述
main方法中创建了JarLauncher对象,调用了launch方法。

进入launch方法

在这里插入图片描述
launch方法里获取了ClassLoader,且通过getMainClass方法获取了Spring Boot启动类。

进入getMainClass方法

在这里插入图片描述
可以看出该方法在获取MANIFEST.MF文件中配置的Start-Class,而前文我们已经知道Start-Class为com.jt.springbootlearndemo1.SpringBootLearnDemo1Applicati即是Spring Boot的启动类

返回launch方法中,该方法的最后一行调用了另一个launch重载方法

在这里插入图片描述
进入launch重载方法

在这里插入图片描述
该方法创建了一个MainMethodRunner对象,调用了其run方法

直接进入MainMethodRunner的run方法

在这里插入图片描述
该方法通过反射执行了Spring Boot启动类的main方法

我们知道web项目都是运行在web容器当中,而Spring Boot内置了web容器,如tomcat,那么Spring Boot启动类的main方法是如何启动tomcat?

进入启动类的main方法

在这里插入图片描述
可以看到调用了SpringApplication的静态run方法,一路跟进去发现调用了其另一个非静态run方法。

进入run方法

public ConfigurableApplicationContext run(String... args) {
    
    
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		/**
		 *定义了spring容器
		 */
		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);
			/**
			 *创建spring容器AnnotationConfigServletWebServerApplicationContext
			 */
			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;
	}

目前我们只看我注释的地方,即创建了一个SpringBoot实现的spring容器(AnnotationConfigServletWebServerApplicationContext),并初始化容器。

进入初始化容器的方法refreshContext

在这里插入图片描述
该方法又调用了refresh方法,一路refresh下去,最终调用了AbstractApplicationContext的refresh,如下

public void refresh() throws BeansException, IllegalStateException {
    
    
		synchronized (this.startupShutdownMonitor) {
    
    
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
    
    
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				/**
				 * Spring Boot实现了该方法创建了web容器
				 */
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
    
    
				if (logger.isWarnEnabled()) {
    
    
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
    
    
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

该方法为容器的初始化方法,加载了BeanDefinition,并实例化。Spring Boot实现了该方法调用的onRefresh创建了web容器。

进入onRefresh方法

在这里插入图片描述
调用了createWebServer方法

private void createWebServer() {
    
    
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
    
    
		/**
		 * 实例化了创建web容器的工厂类
		 */
			ServletWebServerFactory factory = getWebServerFactory();
			/**
			 * 通过工程类创建web容器
			 */
			this.webServer = factory.getWebServer(getSelfInitializer());
			getBeanFactory().registerSingleton("webServerGracefulShutdown",
					new WebServerGracefulShutdownLifecycle(this.webServer));
			getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
		}
		else if (servletContext != null) {
    
    
			try {
    
    
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
    
    
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

该类通过ServletWebServerFactory 工厂类的getWebServer方法创建了web容器。

进入getWebServer方法

@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
    
    
		if (this.disableMBeanRegistry) {
    
    
			Registry.disableRegistry();
		}
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		connector.setThrowOnFailure(true);
		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);
		/**
		 * 启动tomcat容器
		 */
		return getTomcatWebServer(tomcat);
	}

在该方法中创建并启动了Tomcat容器;启动tomcat容器在最后一行调用了getTomcatWebServer方法,进入该方法

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    
    
		return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
	}

该方法中将tomcat封装成了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())) {
    
    
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});

				// Start the server to trigger initialization listeners
				/**
				 * 启动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
				}

				// Unlike Jetty, all Tomcat threads are daemon threads. We create a
				// blocking non-daemon to stop immediate shutdown
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
    
    
				stopSilently();
				destroySilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}

该方法中通过 this.tomcat.start()启动了tomcat,以上就是jar包方式启动的原理。

2 war包部署

前面我们说了jar包部署,Spring Boot也给我们提供了war包部署方式。

2.1 创建demo

修改pom.xml文件,配置packaging为war包方式,显示指定spring-boot-starter-tomcat,并将scope属性配置为provided,表示打包的时候将不会将该依赖打进去。

在这里插入图片描述
创建一个WebApplicationInitializer接口的子类,不直接实现该接口Spring Boot已经帮我们实现了一个SpringBootServletInitializer,继承该类并重写configure方法,在SpringApplicationBuilder 中传入启动类。

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

配置完毕,打成war部署到tomcat即可

2.2 启动原理

我们知道使用springmvc的时候我们需要在web.xml文件中配置DispatcherServlet,在初始化方法里spring容器启动,那使用springbooot打成war部署到tomcat是如何启动spring容器的呢?下面让我们一起来看看Spring Boot打成war包是如何启动spring容器的。

首先看我们新创建的类

在这里插入图片描述
该类继承了SpringBootServletInitializer,查看其类图
在这里插入图片描述
SpringBootServletInitializer实现了WebApplicationInitializer接口,我们知道该接口是spring利用servlet3.0特性做的一个扩展,在tomcat容器启动的时候会调其onStartup方法,下面进入onStartup方法

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 rootApplicationContext = createRootApplicationContext(servletContext);
		if (rootApplicationContext != null) {
    
    
			servletContext.addListener(new SpringBootContextLoaderListener(rootApplicationContext, servletContext));
		}
		else {
    
    
			this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not "
					+ "return an application context");
		}
	}

可以看到该方法中调用createRootApplicationContext方法创建了spring容器。

进入createRootApplicationContext方法

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
    
    
		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);
		builder = configure(builder);
		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
		/**
		 * 创建SpringApplication
		 */
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty()
				&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
    
    
			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));
		}
		application.setRegisterShutdownHook(false);
		return run(application);
	}

该方法创建了一个SpringApplicationBuilder,见名知意,肯定是使用该类创建了一个SpringApplication,这个类大家是否有似曾相识的感觉,上文中通过jar包部署就是调用了该类的run方法启动,下面我们进入该方法的最后一行,调用了run方法。
在这里插入图片描述
果然在run方法里面调用SpringApplication的run方法,进入了和上文中jar包方式相同的逻辑。
但是这个逻辑不是会在spring容器初始化的时候,在onRefresh方法中启动内置的tomcat容器吗?
我们再次来看onRefresh方法

在这里插入图片描述
进入createWebServer方法

private void createWebServer() {
    
    
		WebServer webServer = this.webServer;
		/**
		 * 由于我们同tomcat以及启动所以servletContext不为空
		 * 所以不走内容容器的逻辑
		 */
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
    
    
			ServletWebServerFactory factory = getWebServerFactory();
			this.webServer = factory.getWebServer(getSelfInitializer());
			getBeanFactory().registerSingleton("webServerGracefulShutdown",
					new WebServerGracefulShutdownLifecycle(this.webServer));
			getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
		}
		else if (servletContext != null) {
    
    
			try {
    
    
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
    
    
				throw new ApplicationContextException("Cannot initialize servlet context", ex);
			}
		}
		initPropertySources();
	}

该方法在创建Tomcat容器之前判断了ServletContext是否为空,由于tomcat已经先启动所以此处的ServletContext是不等于空的所以,不会走内置tomcat容器。以上就是war部署方式的原理。

3 总结

以上就是jar包和war包部署的启动原理,我们可以看出这是两个相反的逻辑,jar包部署是spring容器带动tomcat容器启动,而war包部署是tomcat容器带动spring容器启动。

4 其他启动方式

除了以上介绍的启动方式,还有:
1.不需要打包直接进入项目的pom.xml文件所在目录,maven插件启动

mvn spring-boot:run

2.进入jar或war目录, loader 启动

java org.springframework.boot.loader.JarLauncher  
java org.springframework.boot.loader.WarLauncher

猜你喜欢

转载自blog.csdn.net/qq_36706941/article/details/108548415