目錄
- 引言
- 一、代碼啟動(dòng)tomcat
- 二、tomcat框架
- 三、創(chuàng)建容器(addWebapp())
- 3.1 方法 調(diào)用流程圖
- 3.2 源碼分析
- 四、啟動(dòng)容器(tomcat.start())
- 4.1、方法調(diào)用流程圖
- 4.2、源碼分析
- 五、總結(jié)
引言
上一篇博客我們將tomcat源碼在本地成功運(yùn)行了,所以在本篇博客中我們從源碼層面分析,tomcat在啟動(dòng)的過(guò)程中,是如何初始化servlet容器的。我們平常都是將我們的服務(wù)部署到 tomcat中,然后修改一下配置文件,啟動(dòng)就可以對(duì)外提供 服務(wù)了,但是我們對(duì)于其中的一些流程并不是非常的了解,例如如何加載的web.xml等。這是我們分析servlet 和 sringMVC必不可少的過(guò)程。
注釋源碼地址:https://github.com/good-jack/tomcat_source/tree/master
一、代碼啟動(dòng)tomcat
平常我們不論是Windows還是linux,我們都是通過(guò)腳本來(lái)啟動(dòng)tomcat,這對(duì)于我們分析源碼不是很友好,所以我們 需要通過(guò)代碼啟動(dòng),啟動(dòng)代碼如下:
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
//new 出各層容器,并且維護(hù)各層容器的關(guān)系
tomcat.addWebapp("/","/");
tomcat.start();
//阻塞監(jiān)聽(tīng)端口
tomcat.getServer().await();
啟動(dòng)代碼還是非常非常簡(jiǎn)單,從代碼中我們就可以看出,我們本篇博客主要分析的就是 addWebapp()方法和start()方法,通過(guò)這兩個(gè)方法我們就可以找到servlet容器是在什么時(shí)候被初始化的。
二、tomcat框架
在我們進(jìn)行分析上面兩個(gè)方法之前,我們先總結(jié)一下tomcat的基礎(chǔ)框架,其實(shí)從我們非常熟悉的 server.xml配置文件中就可以知道,tomcat就是一系列父子容器組成:
Server ---> Service --> Connector Engine addChild---> context(servlet容器) ,這就是我們從配置文件中分析出來(lái)的幾個(gè)容器,tomcat啟動(dòng)時(shí)候就是逐層啟動(dòng)容器。
三、創(chuàng)建容器(addWebapp())
3.1 方法 調(diào)用流程圖
上面的流程圖就是,從源碼中逐步分析出來(lái)的幾個(gè)重要的方法,這對(duì)于我們分析源碼非常有幫助。
3.2 源碼分析
1)通過(guò)反射獲得configContext監(jiān)聽(tīng)器
方法路徑:package org.apache.catalina.startup.Tomcat.addWebapp(Host host, String contextPath, String docBase);
public Context addWebapp(Host host, String contextPath, String docBase) {
//通過(guò)反射獲得一個(gè)監(jiān)聽(tīng)器 ContextConfig,
//通過(guò)反射得到的一定是LifecycleListener的一個(gè)實(shí)現(xiàn)類(lèi),進(jìn)入getConfigClass得到實(shí)現(xiàn)類(lèi)(org.apache.catalina.startup.ContextConfig)
LifecycleListener listener = null;
try {
Class<?> clazz = Class.forName(getHost().getConfigClass());
listener = (LifecycleListener) clazz.getConstructor().newInstance();
} catch (ReflectiveOperationException e) {
// Wrap in IAE since we can't easily change the method signature to
// to throw the specific checked exceptions
throw new IllegalArgumentException(e);
}
return addWebapp(host, contextPath, docBase, listener);
}
2) 獲得一個(gè)context容器(StandardContext)
在下面代碼中,createContext()方法通過(guò)反射加載StandardContext容器,并且將設(shè)置監(jiān)聽(tīng)ContextConfig, ctx.addLifecycleListener(config);
public Context addWebapp(Host host, String contextPath, String docBase,
LifecycleListener config) {
silence(host, contextPath);
//獲得一個(gè)context容器(StandardContext)
Context ctx = createContext(host, contextPath);
ctx.setPath(contextPath);
ctx.setDocBase(docBase);
if (addDefaultWebXmlToWebapp) {
ctx.addLifecycleListener(getDefaultWebXmlListener());
}
ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
//把監(jiān)聽(tīng)器添加到context中去
ctx.addLifecycleListener(config);
if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) {
// prevent it from looking ( if it finds one - it'll have dup error )
((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
}
if (host == null) {
//getHost會(huì)逐層創(chuàng)建容器,并維護(hù)容器父子關(guān)系
getHost().addChild(ctx);
} else {
host.addChild(ctx);
}
return ctx;
}
3)維護(hù)各層容器
getHost()方法中得到各層容器,并且維護(hù)父親容器關(guān)系,其中包括,server容器、Engine容器。并且將StandardContext容器通過(guò)getHost().addChild(ctx); 調(diào)用containerBase中的addChild()方法維護(hù)在 children 這個(gè)map中。
public Host getHost() {
//將每一層的容器都new 出來(lái)
Engine engine = getEngine();
if (engine.findChildren().length > 0) {
return (Host) engine.findChildren()[0];
}
Host host = new StandardHost();
host.setName(hostname);
//維護(hù)tomcat中的父子容器
getEngine().addChild(host);
return host;
}
getEngine().addChild(host); 方法選擇調(diào)用父類(lèi)containerBase中的addChild方法
@Override
public void addChild(Container child) {
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> dp =
new PrivilegedAddChild(child);
AccessController.doPrivileged(dp);
} else {
//這里的child 參數(shù)是 context 容器
addChildInternal(child);
}
}
addChildInternal()方法的 核心代碼
private void addChildInternal(Container child) {
if( log.isDebugEnabled() )
log.debug("Add child " + child + " " + this);
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '" +
child.getName() +
"' is not unique");
child.setParent(this); // May throw IAE
children.put(child.getName(), child);
}
四、啟動(dòng)容器(tomcat.start())
4.1、方法調(diào)用流程圖
4.2、源碼分析
說(shuō)明:StandardServer 、StandardService、StandardEngine等容器都是繼承LifecycleBase
所以這里是模板模式的經(jīng)典應(yīng)用
1)逐層啟動(dòng)容器
此時(shí)的server對(duì)應(yīng)的是我們前面創(chuàng)建的StandardServer
public void start() throws LifecycleException {
//防止server容器沒(méi)有創(chuàng)建
getServer();
//獲得connector容器,并且將得到的connector容器設(shè)置到service容器中
getConnector();
//這里的start的實(shí)現(xiàn)是在 LifecycleBase類(lèi)中實(shí)現(xiàn)
//LifecycleBase方法是一個(gè)模板方法,在tomcat啟動(dòng)流程中非常關(guān)鍵
server.start();
}
2) 進(jìn)入start方法
進(jìn)入LifecycelBase中的start方法,其中核心方法是startInternal。
從上面我們知道現(xiàn)在我們調(diào)用的是StandardServer容器的startInternal()方法,所以我們這里選擇的是StandardServer
方法路徑:org.apache.catalina.core.StandardServer.startInternal()
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
//啟動(dòng) service容器,一個(gè)tomcat中可以配置多個(gè)service容器,每個(gè)service容器都對(duì)應(yīng)這我們的一個(gè)服務(wù)應(yīng)用
for (Service service : services) {
//對(duì)應(yīng) StandardService.startInternal()
service.start();
}
}
}
從上面代碼中我們可以看出,啟動(dòng)server容器的時(shí)候需要啟動(dòng)子容器 service容器,從這里開(kāi)始就是容器 逐層向向內(nèi)引爆,所以接下來(lái)就是開(kāi)始依次調(diào)用各層容器的star方法。在這里就不在贅述。
2)ContainerBase中的startInternal()方法 核心代碼,從這開(kāi)始啟動(dòng)StandardContext容器
// Start our child containers, if any
//在addWwbapp的流程中 addChild方法中加入的,所以這里需要找出來(lái)
//這里找出來(lái)的就是 context 容器
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
//通過(guò)線程池 異步的方式啟動(dòng)線程池 開(kāi)始啟動(dòng) context容器,進(jìn)入new StartChild
results.add(startStopExecutor.submit(new StartChild(child)));
}
new StartChild(child)) 方法開(kāi)始啟動(dòng)StandardContext容器
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
//開(kāi)始啟動(dòng)context,實(shí)際調(diào)用 StandardContext.startInternal()
child.start();
return null;
}
}
StandardContext.startInternal() 方法中的核心代碼:
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
//lifecycleListeners 在addwebapp方法的第一步中,設(shè)置的監(jiān)聽(tīng)的 contextConfig對(duì)象
for (LifecycleListener listener : lifecycleListeners) {
//這里調(diào)用的是 contextConfig的lifecycleEvent()方法
listener.lifecycleEvent(event);
}
}
進(jìn)入到 contextConfig中的lifecycleEvent()方法
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
//完成web.xml的內(nèi)容解析
configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
在上面方法中,完成對(duì)web.xml的加載和解析,同時(shí)加載xml中配置的servlet并且封裝成wrapper對(duì)象。
3)、啟動(dòng)servlet容器,StandardContext.startInternal() 中的 loadOnStartup(findChildren())方法
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
//這里的 Wrapper就是 我們前面封裝的 servlet
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
//通過(guò) load 方法 最終會(huì)調(diào)用 servlet的init方法
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
通過(guò) load 方法 最終會(huì)調(diào)用 servlet的init方法。
五、總結(jié)
上面內(nèi)容就是整個(gè)tomcat是如何調(diào)用servlet初始化方法的流程,整個(gè)流程小編的理解,如果有錯(cuò)誤,歡迎指正,小編已經(jīng)在源碼中重要部分進(jìn)行了注釋?zhuān)匀绻行枰母魑蛔x者,可以下載我的注釋 源碼,注釋源碼地址:
https://github.com/good-jack/tomcat_source/tree/master
到此這篇關(guān)于詳解從源碼分析tomcat如何調(diào)用Servlet的初始化的文章就介紹到這了,更多相關(guān)tomcat調(diào)用Servlet初始化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!