亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot如何使用內(nèi)嵌Tomcat問題

 更新時(shí)間:2023年06月26日 09:40:29   作者:吉星23526  
這篇文章主要介紹了SpringBoot如何使用內(nèi)嵌Tomcat問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

前言

Springboot使用了內(nèi)置tomcat,在服務(wù)部署時(shí)無需額外安裝Tomcat,直接啟動(dòng)jar包即可。

這里會(huì)有很多疑問

  • 1 內(nèi)置Tomcat長什么樣,它與原來的Tomcat有啥區(qū)別
  • 2 Springboot是如何使用的內(nèi)置tomcat
  • 3 DispatcherServlet是如何加載到tomcat容器的

對(duì)于以上的種種疑問在這里做了一個(gè)總結(jié)

一、原來的Tomcat啟動(dòng)流程

1 運(yùn)行catalina.sh start腳本 最終會(huì)執(zhí)行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" \
        //這里會(huì)運(yùn)行Bootstrap的main方法 并傳入start參數(shù)
        org.apache.catalina.startup.Bootstrap "$@" start
    fi

2 執(zhí)行Bootstrap的mian方法 構(gòu)建Catalina對(duì)象 并執(zhí)行其load和start方法

//全局變量 用于保存Bootstrap實(shí)例
private static volatile Bootstrap daemon = null;
//全局變量 用于保存Catalina對(duì)象
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 {
                    //這里 構(gòu)建Catalina對(duì)象并賦值給全局變量catalinaDaemon	
                    bootstrap.init();
                } catch (Throwable t) {
                   ...
                }
                //這里初始化了全局變量
                daemon = bootstrap;
            } else {
               ...
            }
			try {
	             ...
	            if (command.equals("start")) {
	                daemon.setAwait(true);
	                //本質(zhì)是調(diào)用了Catalina對(duì)象的load方法
	                daemon.load(args);
	                //本質(zhì)上是調(diào)用了Catalina的start方法
	                daemon.start();
	                ...
	            } 
	        } catch (Throwable t) {
	                 ...
	        }
        ...
 }
 //構(gòu)建Catalina對(duì)象并賦值給全局變量catalinaDaemon	
 public void init() throws Exception {
  		...
  		//通過反射構(gòu)建Catalina對(duì)象
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
		...
		//這里把Catalina對(duì)象賦予了全局變量catalinaDaemon
        catalinaDaemon = startupInstance;
 }
//本質(zhì)是調(diào)用了Catalina對(duì)象的load方法
private void load(String[] arguments) throws Exception {
        // Call the load() method
        String methodName = "load";
        ...
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        //這里就是調(diào)用了Catalina對(duì)象的load方法
        method.invoke(catalinaDaemon, param);
}
//本質(zhì)上是調(diào)用了Catalina的start方法
public void start() throws Exception {
      Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
      method.invoke(catalinaDaemon, (Object [])null);
}

3 Catalina的load方法

//全局變量Server對(duì)象 該對(duì)象通過解析server.xml生成
protected Server server = null;
public void load() {
        ...
        // Parse main server.xml
        // 解析server.xml文件 初始化server對(duì)象
        parseServerXml(true);
        Server s = getServer()
        ...
        // Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
           ...
        }
       ...
    }

server.xml的結(jié)構(gòu)是一個(gè)4層嵌套的樹狀結(jié)構(gòu)。一層也就是根節(jié)點(diǎn)是server元素,二層是service元素,三層是Engine元素,四層是Host元素。

最終其被解析Server對(duì)象。該對(duì)象內(nèi)部包含一組service對(duì)象,每個(gè)service對(duì)象包含一個(gè)Engine對(duì)象,每個(gè)Engine對(duì)象包含一組Host對(duì)象。

其實(shí)每個(gè)Host對(duì)象還對(duì)應(yīng)一組Context對(duì)象也就是我們常說的Servlet容器,只是在server.xml文件中體現(xiàn)的比較隱晦。Host對(duì)象有一個(gè)屬性叫做appBase,該屬性的默認(rèn)值是webapps,最終解析時(shí)會(huì)去Tomcat根目錄下的webapps文件中找web.xml,找到一個(gè)就生成一個(gè)Context對(duì)象

4 Catalina的start方法

//本質(zhì)上就是調(diào)用server的start方法
public void start() {
        ...
        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
           ...
        }
         ...
    }
//返回全局變量server
public Server getServer() {
        return server;
    }    

這里蘊(yùn)含這一個(gè)設(shè)計(jì)模式值得一提,通過load方法可以知道Server內(nèi)部有一組service,每個(gè)service內(nèi)部有一個(gè)Engine,每個(gè)Engine內(nèi)部有一組host,每個(gè)host內(nèi)部有一組context。

這里提到的每一個(gè)對(duì)象都有init方法和start方法,在server的start方法被調(diào)用后需要執(zhí)行其下每個(gè)service對(duì)象的init方法和start方法,當(dāng)service的start方法被調(diào)用后需要執(zhí)行其下Engine的init方法和start方法以此類推一直到調(diào)用完Context的init方法和start方法。

Tomcat使用抽象模板的設(shè)計(jì)模式完成了該流程的實(shí)現(xiàn)。

首先看看抽象模板類LifecycleBase,上述提到的所有對(duì)象都繼承該類,該類有4個(gè)主要方法,其中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);
            //該方法是一個(gè)抽象方法由子類完成實(shí)現(xiàn)
            //server類的實(shí)現(xiàn)方式  就是便利其內(nèi)部的sercie對(duì)象 挨個(gè)調(diào)用其init方法
            //service類的實(shí)現(xiàn)方法 就是調(diào)用engine的 init方法
            //engine的實(shí)現(xiàn)方法 就是便利其內(nèi)部的host對(duì)象 挨個(gè)調(diào)用其init方法
            //以此類推。。。
            initInternal();
            //這里會(huì)發(fā)生狀態(tài)變更 防止重復(fù)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方法中會(huì)先執(zhí)行init方法
            init();
        } else if (state.equals(LifecycleState.FAILED)) {
             ...
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
             ...
        }
        ...
        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            //該方法是一個(gè)抽象方法由子類完成實(shí)現(xiàn)
            //server類的實(shí)現(xiàn)方式 就是便利其內(nèi)部的sercie對(duì)象 挨個(gè)調(diào)用其start方法
            //service類的實(shí)現(xiàn)方法 就是調(diào)用engine的 start方法
            //engine的實(shí)現(xiàn)方法 就是便利其內(nèi)部的host對(duì)象 挨個(gè)調(diào)用其start方法
            //以此類推。。。
            startInternal();
            ...
        } catch (Throwable t) {
           ...
        }
    }
	//子類實(shí)現(xiàn)
	protected abstract void initInternal() throws LifecycleException;
    //子類實(shí)現(xiàn)
    protected abstract void startInternal() throws LifecycleException;
}   

基于對(duì)LifecycleBase的4個(gè)方法的分析,我們看看當(dāng)server的start方法被調(diào)用時(shí)會(huì)發(fā)生什么

1 server的start方法會(huì)調(diào)用其父類LifecycleBase的公共start方法

2 接著會(huì)調(diào)用LifecycleBase的init方法

3 接著會(huì)調(diào)用LifecycleBase的initInternal方法,該方法由子類server實(shí)現(xiàn),便利其下的service對(duì)象挨個(gè)調(diào)用init方法

4 service對(duì)象的init方法是由父類LifecycleBase實(shí)現(xiàn)的,所以會(huì)執(zhí)行LifecycleBase的init方法。這里有一個(gè)狀態(tài)變更即元素的state狀態(tài)由LifecycleState.NEW變成了LifecycleState.INITIALIZING防止在start方法中再次執(zhí)行init方法

5 以此類推最終所有元素的init方法會(huì)被調(diào)用并且狀態(tài)變成了LifecycleState.INITIALIZING,最終又回到了server的start方法此時(shí)init方法已經(jīng)執(zhí)行完了

6繼續(xù)向下走執(zhí)行startInternal方法,該方法由子類server實(shí)現(xiàn),便利其下的service對(duì)象挨個(gè)調(diào)用start方法

7start方法由父類LifecycleBase實(shí)現(xiàn)的,所以會(huì)執(zhí)行LifecycleBase的start方法,此時(shí)因?yàn)閷?duì)象狀態(tài)已經(jīng)不是new狀態(tài)了,init方法不會(huì)執(zhí)行,繼續(xù)執(zhí)行startInternal方法,以此類推最終所有元素的start方法會(huì)被執(zhí)行
最終各個(gè)元素的init和start方法都被執(zhí)行了一遍

二、內(nèi)嵌Tomcat

阿帕奇提供了一個(gè)類,名字就叫Tomcat。該類和Catalina類十分相似,內(nèi)部也有一個(gè)Server對(duì)象并且提供了start方法,本質(zhì)也是調(diào)用的server.start。

接下來看看這個(gè)類

public class Tomcat {
	//全局變量
	protected Server server;
	//啟動(dòng)方法
	public void start() throws LifecycleException {
        getServer();
        //本質(zhì)是server的start方法
        server.start();
    }
    //重點(diǎn)在后邊的這幾個(gè)方法
	//獲取server
	public Server getServer() {
		...
		if (server != null) {
            return server;
        }
        //這里直接new對(duì)象了 不像Catalina那樣需要解析server.xml文件
        server = new StandardServer();
        initBaseDir();
        ...
        //順便為其創(chuàng)建了一個(gè)service對(duì)象
        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    }
	//獲取service 內(nèi)部調(diào)用了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;
    }
}

最終可以發(fā)現(xiàn)內(nèi)嵌Tomcat本質(zhì)上和Catalina對(duì)象一樣,都是通過初始化一個(gè)Server對(duì)象然后調(diào)用Server對(duì)象的start方法完成tomcat啟動(dòng)的。

區(qū)別就是初始化Server的過程不在需要解析server.xml文件了,各種get就能完成初始化。

三、Springboot啟動(dòng)Tomcat的時(shí)機(jī)

springboot啟動(dòng)類的mian方法中會(huì)執(zhí)行SpringApplication.run方法,該方法會(huì)創(chuàng)建并啟動(dòng)一個(gè)容器[AnnotationConfigServletWebServerApplicationContext],容器啟動(dòng)會(huì)執(zhí)行祖先類AbstractApplicationContext的refresh方法,該方法中的onRefresh方法被AnnotationConfigServletWebServerApplicationContext的父類ServletWebServerApplicationContext重寫了,內(nèi)置Tomcat就在onRefresh方法中被啟動(dòng)了

接下來看下ServletWebServerApplicationContext的onRefresh方法

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
		implements ConfigurableWebServerApplicationContext {
	//重寫了父類的方法
	@Override
	protected void onRefresh() {
		super.onRefresh();
		try {
		    //重點(diǎn)在這里
			createWebServer();
		}
		....
	}
	//創(chuàng)建WebServer,其內(nèi)部封裝了Tomcat對(duì)象
	private void createWebServer() {
		WebServer webServer = this.webServer;
		//這里取Tomcat容器對(duì)象 首次是取不到的
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			//獲取工廠對(duì)象 該對(duì)象是自動(dòng)裝配中裝配的 默認(rèn)是TomcatServletWebServerFactory
			ServletWebServerFactory factory = getWebServerFactory();
			//通過工廠創(chuàng)建WebServer對(duì)象,WebServer對(duì)象創(chuàng)建完Tomcat就會(huì)啟動(dòng) 所以重點(diǎn)跟下這里
			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();
	}
}

最終會(huì)通過TomcatServletWebServerFactory工廠類構(gòu)建WebServer對(duì)象,跟getWebServer方法

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
	//主要方法	
	@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		if (this.disableMBeanRegistry) {
			Registry.disableRegistry();
		}
		//內(nèi)嵌Tomcat對(duì)象
		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);
		//這里可以知道 其創(chuàng)建了server對(duì)象和service對(duì)象 并為service對(duì)象設(shè)置了connector 
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		//這里可以知道 其創(chuàng)建了engine對(duì)象和host對(duì)象
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		//這里創(chuàng)建了Servlet容器
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}
	//創(chuàng)建Servlet容器
	protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
		File documentRoot = getValidDocumentRoot();
		//直接new了一個(gè)容器 該類是StandardContext的子類
		TomcatEmbeddedContext context = new TomcatEmbeddedContext();
		if (documentRoot != null) {
			context.setResources(new LoaderHidingResourceRoot(context));
		}
		...
		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
		//將容器放入host對(duì)象中
		host.addChild(context);
		configureContext(context, initializersToUse);
		postProcessContext(context);
	}
	//構(gòu)造TomcatWebServer對(duì)象將Tomcat對(duì)象封裝其中
	protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
	}
}

工廠類中會(huì)創(chuàng)建Tomcat對(duì)象,并初始化其內(nèi)部的Server對(duì)象。

最終將Tomcat對(duì)象封裝到TomcatWebServer中返回,接著看下TomcatWebServer的構(gòu)造器

public class TomcatWebServer implements WebServer {
	//用于封裝Tomcat對(duì)象
	private final Tomcat tomcat;
	public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		//初始化Tomcat對(duì)象
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
		//重點(diǎn)看這里 這里啟動(dòng)了Tomcat
		initialize();
	}
	//啟動(dòng)了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) {
				...
			}
		}
	}
}

到這里可以知道工廠類在構(gòu)造WebServer之后,Tomcat就被啟動(dòng)了,這里就是內(nèi)嵌Tomcat的啟動(dòng)時(shí)機(jī)。

和原來相比,原來的啟動(dòng)類是Tomcat,再由Tomcat啟動(dòng)觸發(fā)容器的創(chuàng)建和啟動(dòng),而現(xiàn)在的啟動(dòng)類是容器,由容器啟動(dòng)觸發(fā)了Tomcat的啟動(dòng)

四、SpringBoot中的Tomcat如何加載Servlet

4.1 Servlet3.0標(biāo)準(zhǔn)可以不使用web.xml完成Servlet的注冊(cè)

早期的項(xiàng)目一個(gè)web.xml文件最終被解析成一個(gè)Context對(duì)象【容器對(duì)象】,web.xml內(nèi)部可以配置很多servlet,最終在解析完web.xml會(huì)將解析出來的servlet對(duì)象注冊(cè)到容器中。

而springboot項(xiàng)目中并沒有web.xml文件,所以引發(fā)了一個(gè)問題。

Servlet對(duì)象是如何被注冊(cè)到Tomcat容器中的呢?

servlet3.0標(biāo)準(zhǔn)中提供了一個(gè)不用web.xml也能加載Servlet的方法。

需要三步:

  • 1 寫一個(gè)類實(shí)現(xiàn)ServletContainerInitializer接口
  • 2 實(shí)現(xiàn)ServletContainerInitializer接口的onStartup方法
  • 3 在/META-INF/services目錄下創(chuàng)建javax.servlet.ServletContainerInitializer文件,將實(shí)現(xiàn)類的全名稱寫入到配置文件中

實(shí)現(xiàn)完以上步驟,Tomcat啟動(dòng)后會(huì)回調(diào)實(shí)現(xiàn)類的onStartup方法,并將Servlet容器的裝飾類【ServletContext】當(dāng)作入?yún)魅雘nStartup方法。

看下ServletContext這個(gè)類的方法

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);
}

這個(gè)類有很多方法,其中新增servlet的就有3個(gè)重載方法。

也就是說我們寫的實(shí)現(xiàn)類在實(shí)現(xiàn)onStartup的方法中就可以調(diào)用ServletContext的addServlet方法完成Servlet的注冊(cè)了。

4.2 SpringBoot中的ServletContainerInitializer的實(shí)現(xiàn)類

那么SpringBoot中的Tomcat就是用這個(gè)方式加載的Servlet嗎?是也不全是。

springboot確實(shí)搞了一個(gè)實(shí)現(xiàn)類TomcatStarter來實(shí)現(xiàn)ServletContainerInitializer接口并實(shí)現(xiàn)了onStartup方法。

但是和web.xml文件一樣javax.servlet.ServletContainerInitializer文件在springboot項(xiàng)目中也沒有。

其實(shí)與寫javax.servlet.ServletContainerInitializer文件的方式相比還有一種更加簡單粗暴的方式,在Context對(duì)象創(chuàng)建好后直接調(diào)用其addServletContainerInitializer方法將ServletContainerInitializer的實(shí)現(xiàn)類傳進(jìn)去。

再次看下創(chuàng)建Context對(duì)象的地方

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
	//創(chuàng)建Servlet容器
	protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
		File documentRoot = getValidDocumentRoot();
		//直接new了一個(gè)容器 該類是StandardContext的子類
		TomcatEmbeddedContext context = new TomcatEmbeddedContext();
		if (documentRoot != null) {
			context.setResources(new LoaderHidingResourceRoot(context));
		}
		...
		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
		//將容器放入host對(duì)象中
		host.addChild(context);
		//這個(gè)方法之前沒根 這次下這個(gè)方法
		configureContext(context, initializersToUse);
		postProcessContext(context);
	}
	protected void configureContext(Context context, ServletContextInitializer[] initializers) {
		//創(chuàng)建了ServletContainerInitializer的實(shí)現(xiàn)類
		TomcatStarter starter = new TomcatStarter(initializers);
		...
		//這里直接將其放入到了容器中
		context.addServletContainerInitializer(starter, NO_CLASSES);
		...
	}
}

4.3 ServletContainerInitializer的實(shí)現(xiàn)類執(zhí)行onStartup方法的時(shí)機(jī)

之前分析過server.start方法執(zhí)行后各個(gè)元素的init、start、initInternal、startInternal都會(huì)被調(diào)用,Context對(duì)象也不例外。

接著看下Context的startInternal方法。

雖然我們的Context對(duì)象類型是TomcatEmbeddedContext,但是startInternal方法是由其父類StandardContext實(shí)現(xiàn)的。

所以看下StandardContext類

public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
	//內(nèi)部有一個(gè)集合 用于保存所有ServletContainerInitializer的實(shí)現(xiàn)類
    private Map<ServletContainerInitializer,Set<Class<?>>> initializers =
        new LinkedHashMap<>();
	//還記得這個(gè)方法嗎TomcatEmbeddedContext就是通過該方法將TomcatStarter添加進(jìn)來的
	public void addServletContainerInitializer(
            ServletContainerInitializer sci, Set<Class<?>> classes) {
        initializers.put(sci, classes);
    }
	//Tomcat啟動(dòng)時(shí)會(huì)執(zhí)行該方法 這個(gè)方法巨長無比 我只把關(guān)鍵的保留了
	protected synchronized void startInternal() throws LifecycleException {
	    //便利集合
		for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
                initializers.entrySet()) {
                try {
                    //集合中的key就是ServletContainerInitializer的實(shí)現(xiàn)類 這里調(diào)用了onStartup方法
                    entry.getKey().onStartup(entry.getValue(),
                    		//最后看下getServletContext方法,看看容器的裝飾類到底是什么
                            getServletContext());
                } catch (ServletException e) {
                    log.error(sm.getString("standardContext.sciFail"), e);
                    ok = false;
                    break;
                }
        }
   }
   //這里可以知道容器最終把自己封裝到了ApplicationContext對(duì)象中,
   //最終將ApplicationContext對(duì)象暴露給ServletContainerInitializer實(shí)現(xiàn)類
   public ServletContext getServletContext() {
        if (context == null) {
            context = new ApplicationContext(this);
            if (altDDName != null)
                context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
        }
        return context.getFacade();
    }
}

也就是容器對(duì)象啟動(dòng)后,在執(zhí)行其startInternal方法是會(huì)調(diào)用ServletContainerInitializer的實(shí)現(xiàn)類的onStartup方法并將容器對(duì)象的裝飾類ApplicationContext當(dāng)作入?yún)魅雘nStartup方法。

4.4 分析TomcatStarter的onStartup方法

鋪墊了那么多,我們看下TomcatStarter的onStartup方法

class TomcatStarter implements ServletContainerInitializer {
	//一堆ServletContextInitializer接口的實(shí)現(xiàn)類
	private final ServletContextInitializer[] initializers;
	//構(gòu)造器 初始化內(nèi)部的initializers屬性
	TomcatStarter(ServletContextInitializer[] initializers) {
		this.initializers = initializers;
	}
	//這個(gè)方法里沒有任何servlet的添加操作,而是便利了initializers,并執(zhí)行initializers每一個(gè)實(shí)例的onStartup方法,將servletContext當(dāng)入?yún)魅肫渲?
	@Override
	public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
		try {
			for (ServletContextInitializer initializer : this.initializers) {
				initializer.onStartup(servletContext);
			}
		}
		catch (Exception ex) {
			....
		}
	}
}

和想象中的不一樣,onStartup方法中并沒有添加servlet,而是將ServletContext對(duì)象再次傳給了ServletContextInitializer的實(shí)現(xiàn)類去完成后續(xù)工作。

為什么要這樣做呢?

其實(shí)原因很簡單,到目前為止要想拿到ServletContext對(duì)象就必須實(shí)現(xiàn)ServletContainerInitializer接口。

而ServletContainerInitializer接口并不是spring的類。

所以spring搞了一個(gè)自己的接口ServletContextInitializer并且內(nèi)部也有一個(gè)待實(shí)現(xiàn)的方法onStartup。

spring想實(shí)現(xiàn)的目標(biāo)是所有實(shí)現(xiàn)了ServletContextInitializer接口的bean都能拿到ServletContext對(duì)象。

最終借助TomcatStarter類中的onStartup完成了實(shí)現(xiàn)。

大致看下實(shí)現(xiàn)過程,起點(diǎn)在ServletWebServerApplicationContext類中

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
		implements ConfigurableWebServerApplicationContext {
	//最終這個(gè)私有方法會(huì)被調(diào)用,可以看出如果TomcatStarter中的onStartup方法能調(diào)用到該方法,上述說的spirng目的就達(dá)成了
	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		//這里拿到容器中所有實(shí)現(xiàn)了ServletContextInitializer接口的bean并依次執(zhí)行其onStartup方法 入?yún)⑹莝ervletContext
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}
	//ServletContextInitializer本身是一個(gè)@FunctionalInterface
	//這里將上述的私有方法封裝成了一個(gè)ServletContextInitializer實(shí)例 很感慨既然還能這樣干
	private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return this::selfInitialize;
	}	
	//還記得這個(gè)方法嗎,這里通過factory完成了WebServer的創(chuàng)建,也就是tomcat啟動(dòng)的位置
	private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
			ServletWebServerFactory factory = getWebServerFactory();
			//這里將ServletContextInitializer實(shí)例傳入到了TomcatServletWebServerFactory中
			this.webServer = factory.getWebServer(getSelfInitializer());
			...
		}
		....
	}
}

上述可以看到spring用一種很詭異的方式將一個(gè)私有方法封裝成了ServletContextInitializer實(shí)例并傳給了TomcatServletWebServerFactory的getWebServer方法中,再次根下TomcatServletWebServerFactory類。

這次主要看ServletContextInitializer實(shí)例的傳遞過程

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
	//這里ServletContextInitializer實(shí)例被傳入
	public WebServer getWebServer(ServletContextInitializer... initializers) {
		...
		//被傳入到該方法
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatWebServer(tomcat);
	}
	protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
		//這里做了依次封裝 之前的ServletContextInitializer實(shí)例就在其中
		ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
		//根這里
		configureContext(context, initializersToUse);
		postProcessContext(context);
	}
	//做了一層擴(kuò)展
	protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) {
		List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
		mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter));
		mergedInitializers.add(new SessionConfiguringInitializer(this.session));
		//被傳入到mergedInitializers集合中
		mergedInitializers.addAll(Arrays.asList(initializers));
		mergedInitializers.addAll(this.initializers);
		//集合轉(zhuǎn)數(shù)組
		return mergedInitializers.toArray(new ServletContextInitializer[0]);
	}
	//最終會(huì)將ServletContextInitializer傳入TomcatStarter的構(gòu)造函數(shù),和之前說的完全對(duì)應(yīng)上了
	protected void configureContext(Context context, ServletContextInitializer[] initializers) {
		TomcatStarter starter = new TomcatStarter(initializers);
		...
	}
}

通過對(duì)TomcatStarter的onStartup方法的分析可以知道,所有實(shí)現(xiàn)了ServletContextInitializer接口的bean都能拿到ServletContext對(duì)象,完成servlet對(duì)象的添加

4.5 DispatcherServlet如何加載到Tomcat容器

springboot會(huì)自動(dòng)裝配springmvc,而springmvc的核心類就是DispatcherServlet。

上邊鋪墊了那么多最終看看DispatcherServlet是如何加載到tomcat中的

首先看下自動(dòng)裝配類DispatcherServletAutoConfiguration

...省略注解
public class DispatcherServletAutoConfiguration {
	...省略注解
	protected static class DispatcherServletConfiguration {
		//這里創(chuàng)建了DispatcherServlet類
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
			...省略DispatcherServlet構(gòu)造內(nèi)容
			return dispatcherServlet;
		}
		...
		...省略注解
		protected static class DispatcherServletRegistrationConfiguration {
			...省略注解
			//重點(diǎn)是這個(gè)類,上邊的DispatcherServlet會(huì)被傳入到該類中,最終由該類完成DispatcherServlet向Tomcat容器的注冊(cè)
			public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
					WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
				DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					webMvcProperties.getServlet().getPath());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
			multipartConfig.ifAvailable(registration::setMultipartConfig);
			return registration;
			}
		}
	}
}

可以看到自動(dòng)裝配時(shí)向spring容器中注冊(cè)了DispatcherServletRegistrationBean類,該類構(gòu)造器中包含DispatcherServlet對(duì)象。

看下DispatcherServletRegistrationBean類的家譜

可以看到該類實(shí)現(xiàn)了ServletContextInitializer接口也就是其能拿到Tomcat容器對(duì)象。

看下其祖先類RegistrationBean的onStartup方法

public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
	public final void onStartup(ServletContext servletContext) throws ServletException {
		String description = getDescription();
		if (!isEnabled()) {
			logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
			return;
		}
		//根這個(gè)方法 該方法由子類DynamicRegistrationBean實(shí)現(xiàn)
		register(description, servletContext);
	}
}

DynamicRegistrationBean類

public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
	protected final void register(String description, ServletContext servletContext) {
		//servlet注冊(cè)在這里完成 該方法由子類ServletRegistrationBean實(shí)現(xiàn)
		//servlet注冊(cè)完后會(huì)返回一個(gè)registration對(duì)象,用于完成servlet-mapping的配置
		D registration = addRegistration(description, servletContext);
		if (registration == null) {
			logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
			return;
		}
		//servlet的mapping配置在這里完成 該方法由子類ServletRegistrationBean實(shí)現(xiàn)
		configure(registration);
	}
}

ServletRegistrationBean類

public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
	//這里可以看到servletContext.addServlet方法終于被調(diào)用了
	@Override
	protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
		String name = getServletName();
		//this.servlet就是DispatcherServlet
		return servletContext.addServlet(name, this.servlet);
	}
	//這里來配置servlet-mapping
	@Override
	protected void configure(ServletRegistration.Dynamic registration) {
		super.configure(registration);
		String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
		if (urlMapping.length == 0 && this.alwaysMapUrl) {
			urlMapping = DEFAULT_MAPPINGS;
		}
		if (!ObjectUtils.isEmpty(urlMapping)) {
			registration.addMapping(urlMapping);
		}
		registration.setLoadOnStartup(this.loadOnStartup);
		if (this.multipartConfig != null) {
			registration.setMultipartConfig(this.multipartConfig);
		}
	}
}

總結(jié)

1 springboot使用內(nèi)嵌Tomcat完成了tomcat的啟動(dòng)。內(nèi)嵌Tomcat本質(zhì)上和正常Tomcat的Catalina對(duì)象一樣都是通過初始化內(nèi)部的server對(duì)象,最終調(diào)用server對(duì)象的start方法來完成啟動(dòng)的。區(qū)別就是server對(duì)象的創(chuàng)建構(gòu)成,前者直接new后者解析server.xml文件

2 springboot中tomcat的啟動(dòng)時(shí)機(jī)是在容器啟動(dòng)時(shí),執(zhí)行onRefresh方法中。創(chuàng)建webServer對(duì)象時(shí)啟動(dòng)的。

3 springboot基于servlet3.0標(biāo)準(zhǔn)。創(chuàng)建了ServletContainerInitializer的實(shí)現(xiàn)類TomcatStarter最終拿到Tomcat容器對(duì)象

4 springboot基于TomcatStarter拿到的tomcat容器對(duì)象做了進(jìn)一步優(yōu)化。最終實(shí)現(xiàn)了所有實(shí)現(xiàn)ServletContextInitializer接口的bean都能拿到tomcat容器

5 ServletContextInitializer的實(shí)現(xiàn)類之一DispatcherServletRegistrationBean完成了DispatcherServlet向tomcat容器的注冊(cè)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java編程GUI中的事件綁定代碼示例

    Java編程GUI中的事件綁定代碼示例

    這篇文章主要介紹了Java編程GUI中的事件綁定代碼示例,簡單介紹了綁定的概念,然后分享了相關(guān)代碼,具有一定參考價(jià)值,需要的朋友可以參考下。
    2017-10-10
  • JAVA 根據(jù)Url把多文件打包成ZIP下載實(shí)例

    JAVA 根據(jù)Url把多文件打包成ZIP下載實(shí)例

    這篇文章主要介紹了JAVA 根據(jù)Url把多文件打包成ZIP下載的相關(guān)資料,需要的朋友可以參考下
    2017-08-08
  • Intellij IDEA 如何通過數(shù)據(jù)庫表生成帶注解的實(shí)體類(圖文詳細(xì)教程)

    Intellij IDEA 如何通過數(shù)據(jù)庫表生成帶注解的實(shí)體類(圖文詳細(xì)教程)

    這篇文章主要介紹了Intellij IDEA 如何通過數(shù)據(jù)庫表生成帶注解的實(shí)體類(圖文詳細(xì)教程),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • java計(jì)算工作時(shí)間除去節(jié)假日以及雙休日

    java計(jì)算工作時(shí)間除去節(jié)假日以及雙休日

    這篇文章主要為大家詳細(xì)介紹了java計(jì)算工作時(shí)間除去節(jié)假日以及雙休日的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • SpringBoot整合mybatisplus和druid的示例詳解

    SpringBoot整合mybatisplus和druid的示例詳解

    這篇文章主要介紹了SpringBoot整合mybatisplus和druid的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • Spring框架中IoC容器與DI依賴注入教程

    Spring框架中IoC容器與DI依賴注入教程

    IOC也是Spring的核心之一了,之前學(xué)的時(shí)候是采用xml配置文件的方式去實(shí)現(xiàn)的,后來其中也多少穿插了幾個(gè)注解,但是沒有說完全采用注解實(shí)現(xiàn)。那么這篇文章就和大家分享一下,全部采用注解來實(shí)現(xiàn)IOC + DI
    2023-01-01
  • Java多線程實(shí)現(xiàn)快速切分文件的程序

    Java多線程實(shí)現(xiàn)快速切分文件的程序

    這篇文章主要為大家詳細(xì)介紹了Java多線程實(shí)現(xiàn)快速切分文件的相關(guān)資料,感興趣的小伙伴們可以參考一下
    2016-06-06
  • mybatis?selectKey賦值未生效的原因分析

    mybatis?selectKey賦值未生效的原因分析

    這篇文章主要介紹了mybatis?selectKey賦值未生效的原因分析,selectKey 會(huì)將 SELECT LAST_INSERT_ID()的結(jié)果放入到傳入的實(shí)體類的主鍵里面,文中通過代碼示例給大家講解非常詳細(xì),需要的朋友可以參考下
    2024-02-02
  • java實(shí)現(xiàn)銀行家算法

    java實(shí)現(xiàn)銀行家算法

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)銀行家算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • MyBatis使用annonation定義類型映射的簡易用法示例

    MyBatis使用annonation定義類型映射的簡易用法示例

    這篇文章主要介紹了MyBatis使用annonation定義類型映射的簡易用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09

最新評(píng)論