SpringBoot与Tomcat的启动(内嵌Tomcat)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot与Tomcat的启动(内嵌Tomcat)相关的知识,希望对你有一定的参考价值。

参考技术A 环境:
SpringBoot 2.0.1

使用SpringBoot开发时,可以通过Maven将工程打成jar包,jar包内嵌Tomcat,这种方式SpringBoot工程将在启动的时候,带动Tomcat的启动,下面分析SpringBoot如何带动Tomcat启动

一个简单的SpringBoot工程启动类

跟进 SpringApplication#run 方法

如果对Spring的启动流程有了解的话,应该知道Spring启动过程中,最重要的就是 AbstractApplicationContext#refresh() 过程,在这个方法中Spring执行了BeanFactory的初始化,Bean的实例化、属性填充、初始化操作等等重要的操作,该方法主要逻辑如下:

仔细观察这个方法,可以发现其中调用了几个空方法,这里应用了 模板模式 ,在 refresh 方法中定义了主干执行流程,并留有空的方法给子类做个性化定制

现在开始,通过debug来看看SpringBoot如何启动Tomcat容器

前面构造 SpringApplicaton 时,已经推断出当前Web工程类型,当开始执行 #run 方法时,会根据不同类型的Web项目创建不同类型的ApplicationContext

创建好 ApplicationContext 之后,看到 refreshContext(context) ,联系本文开头Spring启动的 AbstractApplicationContext#refresh 方法,该方法其实就是触发了后者的执行

执行 refreshContext 方法,来到前面根据 webApplicationType 创建的容器类 ServletWebServerApplicationContext

进入到 TomcatServletWebServerFactory ,可以看到如下启动内置Tomcat的过程

至此,Tomcat继承Spring的 AbstractApplicationContext 类,覆盖它的模板方法 onRefresh ,SpringBoot在自身启动的过程中,启动了内置的Tomcat服务器

SpringBoot如何使用内嵌Tomcat

文章目录


前言

Springboot使用了内置tomcat,在服务部署时无需额外安装Tomcat,直接启动jar包即可。这里会有很多疑问

1 内置Tomcat长什么样,它与原来的Tomcat有啥区别

2 Springboot是如何使用的内置tomcat

3 DispatcherServlet是如何加载到tomcat容器的


对于以上的种种疑问在这里做了一个总结

一、原来的Tomcat启动流程

1 运行catalina.sh start脚本 最终会执行Bootstrap的mian方法

eval exec "\\"$_RUNJDB\\"" "\\"$CATALINA_LOGGING_CONFIG\\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \\
        -D$ENDORSED_PROP="$JAVA_ENDORSED_DIRS" \\
        -classpath "$CLASSPATH" \\
        -sourcepath "$CATALINA_HOME"/../../java \\
        -Dcatalina.base="$CATALINA_BASE" \\
        -Dcatalina.home="$CATALINA_HOME" \\
        -Djava.io.tmpdir="$CATALINA_TMPDIR" \\
        //这里会运行Bootstrap的main方法 并传入start参数
        org.apache.catalina.startup.Bootstrap "$@" start
    fi

2 执行Bootstrap的mian方法 构建Catalina对象 并执行其load和start方法

//全局变量 用于保存Bootstrap实例
private static volatile Bootstrap daemon = null;
//全局变量 用于保存Catalina对象
private Object catalinaDaemon = null;

public static void main(String args[]) 
        synchronized (daemonLock) 
            if (daemon == null) 
                // Don't set daemon until init() has completed
                Bootstrap bootstrap = new Bootstrap();
                try 
                    //这里 构建Catalina对象并赋值给全局变量catalinaDaemon	
                    bootstrap.init();
                 catch (Throwable t) 
                   ...
                
                //这里初始化了全局变量
                daemon = bootstrap;
             else 
               ...
            

			try 
	             ...
	            if (command.equals("start")) 
	                daemon.setAwait(true);
	                //本质是调用了Catalina对象的load方法
	                daemon.load(args);
	                //本质上是调用了Catalina的start方法
	                daemon.start();
	                ...
	             
	         catch (Throwable t) 
	                 ...
	        
        ...
 
 //构建Catalina对象并赋值给全局变量catalinaDaemon	
 public void init() throws Exception 
  		...
  		//通过反射构建Catalina对象
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
		...
		//这里把Catalina对象赋予了全局变量catalinaDaemon
        catalinaDaemon = startupInstance;
 
//本质是调用了Catalina对象的load方法
private void load(String[] arguments) throws Exception 
        // Call the load() method
        String methodName = "load";
        ...
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        //这里就是调用了Catalina对象的load方法
        method.invoke(catalinaDaemon, param);

//本质上是调用了Catalina的start方法
public void start() throws Exception 
      Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
      method.invoke(catalinaDaemon, (Object [])null);

3 Catalina的load方法

//全局变量Server对象 该对象通过解析server.xml生成
protected Server server = null;

public void load() 
        ...
        // Parse main server.xml
        // 解析server.xml文件 初始化server对象
        parseServerXml(true);
        Server s = getServer()
        ...
        // Start the new server
        try 
            getServer().init();
         catch (LifecycleException e) 
           ...
        
       ...
    

server.xml的结构是一个4层嵌套的树状结构。一层也就是根节点是server元素,二层是service元素,三层是Engine元素,四层是Host元素。
最终其被解析Server对象。该对象内部包含一组service对象,每个service对象包含一个Engine对象,每个Engine对象包含一组Host对象。
其实每个Host对象还对应一组Context对象也就是我们常说的Servlet容器,只是在server.xml文件中体现的比较隐晦。Host对象有一个属性叫做appBase,该属性的默认值是webapps,最终解析时会去Tomcat根目录下的webapps文件中找web.xml,找到一个就生成一个Context对象

4 Catalina的start方法

//本质上就是调用server的start方法
public void start() 
        ...
        // Start the new server
        try 
            getServer().start();
         catch (LifecycleException e) 
           ...
        
         ...
    
//返回全局变量server
public Server getServer() 
        return server;
        

这里蕴含这一个设计模式值得一提,通过load方法可以知道Server内部有一组service,每个service内部有一个Engine,每个Engine内部有一组host,每个host内部有一组context。这里提到的每一个对象都有init方法和start方法,在server的start方法被调用后需要执行其下每个service对象的init方法和start方法,当service的start方法被调用后需要执行其下Engine的init方法和start方法以此类推一直到调用完Context的init方法和start方法。Tomcat使用抽象模板的设计模式完成了该流程的实现。
首先看看抽象模板类LifecycleBase,上述提到的所有对象都继承该类,该类有4个主要方法,其中start是模板类的核心方法

public abstract class LifecycleBase implements Lifecycle 
	//抽象模板类提供的公共方法
	public final synchronized void init() throws LifecycleException 
        if (!state.equals(LifecycleState.NEW)) 
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        
        try 
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            //该方法是一个抽象方法由子类完成实现
            //server类的实现方式  就是便利其内部的sercie对象 挨个调用其init方法
            //service类的实现方法 就是调用engine的 init方法
            //engine的实现方法 就是便利其内部的host对象 挨个调用其init方法
            //以此类推。。。
            initInternal();
            //这里会发生状态变更 防止重复init用的
            setStateInternal(LifecycleState.INITIALIZED, null, false);
         catch (Throwable t) 
            handleSubClassException(t, "lifecycleBase.initFail", toString());
        
    
    //抽象模板类提供的公共方法
	public final synchronized void start() throws LifecycleException 
		if (state.equals(LifecycleState.NEW)) 
		    //start方法中会先执行init方法
            init();
         else if (state.equals(LifecycleState.FAILED)) 
             ...
         else if (!state.equals(LifecycleState.INITIALIZED) &&
             ...
        
        ...
        try 
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            //该方法是一个抽象方法由子类完成实现
            //server类的实现方式 就是便利其内部的sercie对象 挨个调用其start方法
            //service类的实现方法 就是调用engine的 start方法
            //engine的实现方法 就是便利其内部的host对象 挨个调用其start方法
            //以此类推。。。
            startInternal();
            ...
         catch (Throwable t) 
           ...
        
    

	//子类实现
	protected abstract void initInternal() throws LifecycleException;
    //子类实现
    protected abstract void startInternal() throws LifecycleException;

   

基于对LifecycleBase的4个方法的分析,我们看看当server的start方法被调用时会发生什么
1 server的start方法会调用其父类LifecycleBase的公共start方法
2 接着会调用LifecycleBase的init方法
3 接着会调用LifecycleBase的initInternal方法,该方法由子类server实现,便利其下的service对象挨个调用init方法
4 service对象的init方法是由父类LifecycleBase实现的,所以会执行LifecycleBase的init方法。这里有一个状态变更即元素的state状态由LifecycleState.NEW变成了LifecycleState.INITIALIZING防止在start方法中再次执行init方法
5 以此类推最终所有元素的init方法会被调用并且状态变成了LifecycleState.INITIALIZING,最终又回到了server的start方法此时init方法已经执行完了
6继续向下走执行startInternal方法,该方法由子类server实现,便利其下的service对象挨个调用start方法
7start方法由父类LifecycleBase实现的,所以会执行LifecycleBase的start方法,此时因为对象状态已经不是new状态了,init方法不会执行,继续执行startInternal方法,以此类推最终所有元素的start方法会被执行
最终各个元素的init和start方法都被执行了一遍

二、内嵌Tomcat

阿帕奇提供了一个类,名字就叫Tomcat。该类和Catalina类十分相似,内部也有一个Server对象并且提供了start方法,本质也是调用的server.start。接下来看看这个类

public class Tomcat 
	//全局变量
	protected Server server;
	//启动方法
	public void start() throws LifecycleException 
        getServer();
        //本质是server的start方法
        server.start();
    
    //重点在后边的这几个方法

	//获取server
	public Server getServer() 
		...
		if (server != null) 
            return server;
        
        //这里直接new对象了 不像Catalina那样需要解析server.xml文件
        server = new StandardServer();

        initBaseDir();
        ...
        //顺便为其创建了一个service对象
        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    
	//获取service 内部调用了getServer 一样的道理 没有就new
	public Service getService() 
        return getServer().findServices()[0];
    
	//获取引擎 一样的逻辑 没有就new
	public Engine getEngine() 
        Service service = getServer().findServices()[0];
        if (service.getContainer() != null) 
            return service.getContainer();
        
        Engine engine = new StandardEngine();
        engine.setName( "Tomcat" );
        engine.setDefaultHost(hostname);
        engine.setRealm(createDefaultRealm());
        service.setContainer(engine);
        return engine;
    
	//获取host 同上没有就new
	public Host getHost() 
        Engine engine = getEngine();
        if (engine.findChildren().length > 0) 
            return (Host) engine.findChildren()[0];
        

        Host host = new StandardHost();
        host.setName(hostname);
        getEngine().addChild(host);
        return host;
    


最终可以发现内嵌Tomcat本质上和Catalina对象一样,都是通过初始化一个Server对象然后调用Server对象的start方法完成tomcat启动的。区别就是初始化Server的过程不在需要解析server.xml文件了,各种get就能完成初始化。

三、Springboot启动Tomcat的时机

springboot启动类的mian方法中会执行SpringApplication.run方法,该方法会创建并启动一个容器[AnnotationConfigServletWebServerApplicationContext],容器启动会执行祖先类AbstractApplicationContext的refresh方法,该方法中的onRefresh方法被AnnotationConfigServletWebServerApplicationContext的父类ServletWebServerApplicationContext重写了,内置Tomcat就在onRefresh方法中被启动了

接下来看下ServletWebServerApplicationContext的onRefresh方法

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
		implements ConfigurableWebServerApplicationContext 

	//重写了父类的方法
	@Override
	protected void onRefresh() 
		super.onRefresh();
		try 
		    //重点在这里
			createWebServer();
		
		....
	

	//创建WebServer,其内部封装了Tomcat对象
	private void createWebServer() 
		WebServer webServer = this.webServer;
		//这里取Tomcat容器对象 首次是取不到的
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) 
			//获取工厂对象 该对象是自动装配中装配的 默认是TomcatServletWebServerFactory
			ServletWebServerFactory factory = getWebServerFactory();
			//通过工厂创建WebServer对象,WebServer对象创建完Tomcat就会启动 所以重点跟下这里
			this.webServer = factory.getWebServer(getSelfInitializer());
			getBeanFactory().registerSingleton("webServerGracefulShutdown",
					new WebServerGracefulShutdownLifecycle(this.webServer));
			getBeanFactory().registerSingleton("webServerStartStop",
					new WebServerStartStopLifecycle(this, this.webServer));
		
		else if (servletContext != null) 
			...
		
		initPropertySources();
	


最终会通过TomcatServletWebServerFactory工厂类构建WebServer对象,跟getWebServer方法

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware 
	
	//主要方法	
	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) 
		if (this.disableMBeanRegistry) 
			Registry.disableRegistry();
		
		//内嵌Tomcat对象
		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);
		//这里可以知道 其创建了server对象和service对象 并为service对象设置了connector 
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		//这里可以知道 其创建了engine对象和host对象
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) 
			tomcat.getService().addConnector(additionalConnector);
		
		//这里创建了Servlet容器
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	

	//创建Servlet容器
	protected void prepareContext(Host host, ServletContextInitializer[] initializers) 
		File documentRoot = getValidDocumentRoot();
		//直接new了一个容器 该类是StandardContext的子类
		TomcatEmbeddedContext context = new TomcatEmbeddedContext();
		if (documentRoot != null) 
			context.setResources(new LoaderHidingResourceRoot(context));
		
		...
		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
		//将容器放入host对象中
		host.addChild(context);
		configureContext(context, initializersToUse);
		postProcessContext(context);
	
	//构造TomcatWebServer对象将Tomcat对象封装其中
	protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) 
		return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
	

工厂类中会创建Tomcat对象,并初始化其内部的Server对象。最终将Tomcat对象封装到TomcatWebServer中返回,接着看下TomcatWebServer的构造器

public class TomcatWebServer implements WebServer 
	//用于封装Tomcat对象
	private final Tomcat tomcat;

	public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) 
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		//初始化Tomcat对象
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
		//重点看这里 这里启动了Tomcat
		initialize();
	
	//启动了Tomcat
	private void initialize() throws WebServerException 
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) 
			try 
				...
				// Start the server to trigger initialization listeners
				this.tomcat.start();
				...
			
			catch (Exception ex) 
				...
			
		
	

到这里可以知道工厂类在构造WebServer之后,Tomcat就被启动了,这里就是内嵌Tomcat的启动时机。和原来相比,原来的启动类是Tomcat,再由Tomcat启动触发容器的创建和启动,而现在的启动类是容器,由容器启动触发了Tomcat的启动

四、SpringBoot中的Tomcat如何加载Servlet

4.1 Servlet3.0标准可以不使用web.xml完成Servlet的注册

早期的项目一个web.xml文件最终被解析成一个Context对象【容器对象】,web.xml内部可以配置很多servlet,最终在解析完web.xml会将解析出来的servlet对象注册到容器中。而springboot项目中并没有web.xml文件,所以引发了一个问题。Servlet对象是如何被注册到Tomcat容器中的呢?
servlet3.0标准中提供了一个不用web.xml也能加载Servlet的方法。需要三步

1 写一个类实现ServletContainerInitializer接口
2 实现ServletContainerInitializer接口的onStartup方法
3 在/META-INF/services目录下创建javax.servlet.ServletContainerInitializer文件,将实现类的全名称写入到配置文件中

实现完以上步骤,Tomcat启动后会回调实现类的onStartup方法,并将Servlet容器的装饰类【ServletContext】当作入参传入onStartup方法。看下ServletContext这个类的方法

public interface ServletContext 
	public ServletRegistration.Dynamic addServlet(String servletName, String className);
    public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet);
    public ServletRegistration.Dynamic addServlet(String servletName,
            Class<? extends Servlet> servletClass);

这个类有很多方法,其中新增servlet的就有3个重载方法。也就是说我们写的实现类在实现onStartup的方法中就可以调用ServletContext的addServlet方法完成Servlet的注册了。

4.2 SpringBoot中的ServletContainerInitializer的实现类

那么SpringBoot中的Tomcat就是用这个方式加载的Servlet吗?是也不全是。springboot确实搞了一个实现类TomcatStarter来实现ServletContainerInitializer接口并实现了onStartup方法。但是和web.xml文件一样javax.servlet.ServletContainerInitializer文件在springboot项目中也没有。其实与写javax.servlet.ServletContainerInitializer文件的方式相比还有一种更加简单粗暴的方式,在Context对象创建好后直接调用其addServletContainerInitializer方法将ServletContainerInitializer的实现类传进去。再次看下创建Context对象的地方

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware 
	

	以上是关于SpringBoot与Tomcat的启动(内嵌Tomcat)的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot如何使用内嵌Tomcat

解密Springboot内嵌Tomcat

springBoot学习笔记内嵌tomcat和springmvc

springBoot学习笔记内嵌tomcat和springmvc

springboot去除内嵌tomcat和打包在tomcat中运行需要做的步骤

springboot内嵌tomcat的默认内存能配置吗