上一节我们引出了calatina类进行应用加载,再回顾下调用代码
daemon.setAwait(true);
daemon.load(args);
daemon.start();
对应在calatina中都能找到相应方法。setAwait(true)表示阻塞等待的含义我们先跳过。此类中有两个重点要分析的方法load()和start(),一个加载配置,一个启动应用,看下我分析完的整个思维导图,图片太大,可以查看原图
catalina
我将转化为我们重点分析类的时序图:
时序图放大时序图,我们看到了如下流程
-
catalina 先调用load加载了server.xml,配置文件生成了对应tocmat架构的主要类图,此时通过Digester技术实现,主要类图如下
tomcat主要类图.jpg
其中stndardService为每一个service服务,connector为此service中的一个接收器,它和standardEngine代表的应用容器通过MapperListener进行映射,standardEngine内部又分成standardHost、standardConetxt还有StandardWrapper(未化出,后生成),HostConfig和ConetxtConfig为对应host和context的事件监听者用于初始化web应用类。
-
catalina方法的start方法,最终调用到standardService的start()方法,此方法采用模版方法先调用父级的公用模版方法,最后调用自己的startInernal方法,最终将会初始化整个service服务。
-
standardService的启动方法中核心代码如下:
protected void startInternal() throws LifecycleException {
.....
//更新tomcat状态
setState(LifecycleState.STARTING);
// 启动容器服务
if (container != null) {
synchronized (container) {
container.start();
}
}
//开启定义线程
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// 开启接收connector
synchronized (connectors) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
核心部分是三个服务启动,我们重点看container.start()
和connector.start()
方法。
- container.start()方法启动业务容器,其都遵守tomcat的生命周期最高接口Lifecycle来管理,通过事件驱动模式,配置观察者模式,通过观察tomcat的状态变化,进行启动驱动。
此时有个重点类为ContainBase,此类是StandardEngine、StandardHost、StandardPipeline、StandardWrapper抽象父类,看下ContainBase中的重点方法
第一,startInternal方法,每层容器,查找子容器,开启子容器,添加pipeline通道,更新此容器状态,驱动监听者进行状态更新,相关操作。
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
...........
//添加子容器,启动子容器
Container children[] = findChildren();
for (int i = 0; i < children.length; i++) {
children[i].start();
}
// 启动当前容器的pipeline,配置调用责任链
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
//事件驱动,通知当前容器的监听者,进行相应操作
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
}
第二,setState方法,事件驱动方法
private synchronized void setStateInternal(LifecycleState state,
Object data, boolean check) throws LifecycleException {
。。。。。。。。
this.state = state;
String lifecycleEvent = state.getLifecycleEvent();
if (lifecycleEvent != null) {
//事件驱动
fireLifecycleEvent(lifecycleEvent, data);
}
}
此方法继续进入,会看到
public void /**/fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = listeners;
for (int i = 0; i < interested.length; i++)
//典型的观察者模式写法
interested[i].lifecycleEvent(event);
}
第三,类图已经说了两个重点的监听者HostConfig和ContextConfig,HostConfig用于找出目录下的war包
protected void deployApps(String name) {
//根据你配置的appBase路径,查找文件
File appBase = appBase();
//判断你是否在conf/Catalina/你应用名.xml中定制自己的应用文件,没有定制,后面将会使用默认的context.xml
File configBase = configBase();
ContextName cn = new ContextName(name);
String baseName = cn.getBaseName();
// Deploy XML descriptors from configBase
File xml = new File(configBase, baseName + ".xml");
if (xml.exists())
deployDescriptor(cn, xml, baseName + ".xml");
// Deploy WARs, and loop if additional descriptors are found
File war = new File(appBase, baseName + ".war");
if (war.exists())
deployWAR(cn, war, baseName + ".war");
// Deploy expanded folders
File dir = new File(appBase, baseName);
if (dir.exists())
deployDirectory(cn, dir, baseName);
}
再看下 deployWAR()方法发布我们常见的war包,其核心逻辑
protected void deployWAR(ContextName cn, File war, String file) {
............
//生成StandardContext类
context = (Context) Class.forName(contextClass).newInstance();
.........
//配置ContextConfig监听器
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(file);
//host中添加context,并将在此方法中启动conetxt
host.addChild(context);
..........
deployed.put(cn.getName(), deployedApp);
}
第四,addChildInternal()增加子容器方法,上一步host.addChild(context)
添加context,随后addChildInternal中启动了context,然后还是驱动模式通过fireContainerEvent方法通知观察者。
private void addChildInternal(Container child) {
...............
//启动子容器
child.start();
success = true;
............
//驱动事件发生
fireContainerEvent(ADD_CHILD_EVENT, child);
}
- ContextConfig类,查看其接收事件方法,最后进行驱动configureStart方法发布服务
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;
}
// 启动服务
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
.....
}
查看configureStart方法
*/
protected synchronized void configureStart() {
...............
//创建WebXm解析其
createWebXmlDigester(context.getXmlNamespaceAware(),
context.getXmlValidation());
//解析web.xml
webConfig();
.............
}
最后终于看到熟悉的web.xml,看下 webConfig()方法,此时解析web.xml,解析所有servlet,并通过webXml.configureContext(context)生成对应的每个StandardWrapper,
protected void webConfig() {
WebXml webXml = createWebXml();
// Parse global web.xml if present
InputSource globalWebXml = getGlobalWebXmlSource();
if (globalWebXml == null) {
// This is unusual enough to log
log.info(sm.getString("contextConfig.defaultMissing"));
} else {
parseWebXml(globalWebXml, webXml, false);
}
// Parse host level web.xml if present
// Additive apart from welcome pages
webXml.setReplaceWelcomeFiles(true);
InputSource hostWebXml = getHostWebXmlSource();
parseWebXml(hostWebXml, webXml, false);
// Parse context level web.xml
webXml.setReplaceWelcomeFiles(true);
InputSource contextWebXml = getContextWebXmlSource();
parseWebXml(contextWebXml, webXml, false);
// Assuming 0 is safe for what is required in this case
double webXmlVersion = 0;
if (webXml.getVersion() != null) {
webXmlVersion = Double.parseDouble(webXml.getVersion());
}
if (webXmlVersion >= 3) {
// Ordering is important here
// Step 1. Identify all the JARs packaged with the application
// If the JARs have a web-fragment.xml it will be parsed at this
// point.
Map<String,WebXml> fragments = processJarsForWebFragments();
// Only need to process fragments and annotations if metadata is
// not complete
Set<WebXml> orderedFragments = null;
if (!webXml.isMetadataComplete()) {
// Step 2. Order the fragments.
orderedFragments = WebXml.orderWebFragments(webXml, fragments);
// Step 3. Look for ServletContainerInitializer implementations
if (ok) {
processServletContainerInitializers(orderedFragments);
}
// Step 4. Process /WEB-INF/classes for annotations
// This will add any matching classes to the typeInitializerMap
if (ok) {
URL webinfClasses;
try {
webinfClasses = context.getServletContext().getResource(
"/WEB-INF/classes");
processAnnotationsUrl(webinfClasses, webXml);
} catch (MalformedURLException e) {
log.error(sm.getString(
"contextConfig.webinfClassesUrl"), e);
}
}
// Step 5. Process JARs for annotations - only need to process
// those fragments we are going to use
// This will add any matching classes to the typeInitializerMap
if (ok) {
processAnnotations(orderedFragments);
}
// Step 6. Merge web-fragment.xml files into the main web.xml
// file.
if (ok) {
ok = webXml.merge(orderedFragments);
}
// Step 6.5 Convert explicitly mentioned jsps to servlets
if (!false) {
convertJsps(webXml);
}
// Step 7. Apply merged web.xml to Context
if (ok) {
webXml.configureContext(context);
// Step 7a. Make the merged web.xml available to other
// components, specifically Jasper, to save those components
// from having to re-generate it.
// TODO Use a ServletContainerInitializer for Jasper
String mergedWebXml = webXml.toXml();
context.getServletContext().setAttribute(
org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML,
mergedWebXml);
if (context.getLogEffectiveWebXml()) {
log.info("web.xml:\n" + mergedWebXml);
}
}
} else {
webXml.configureContext(context);
}
// Always need to look for static resources
// Step 8. Look for static resources packaged in JARs
if (ok) {
// Spec does not define an order.
// Use ordered JARs followed by remaining JARs
Set<WebXml> resourceJars = new LinkedHashSet<WebXml>();
if (orderedFragments != null) {
for (WebXml fragment : orderedFragments) {
resourceJars.add(fragment);
}
}
for (WebXml fragment : fragments.values()) {
if (!resourceJars.contains(fragment)) {
resourceJars.add(fragment);
}
}
processResourceJARs(resourceJars);
// See also StandardContext.resourcesStart() for
// WEB-INF/classes/META-INF/resources configuration
}
// Only look for ServletContainerInitializer if metadata is not
// complete
if (!webXml.isMetadataComplete()) {
// Step 9. Apply the ServletContainerInitializer config to the
// context
if (ok) {
for (Map.Entry<ServletContainerInitializer,
Set<Class<?>>> entry :
initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(
entry.getKey(), null);
} else {
context.addServletContainerInitializer(
entry.getKey(), entry.getValue());
}
}
}
}
} else {
// Apply unmerged web.xml to Context
convertJsps(webXml);
webXml.configureContext(context);
}
}
最终的StandardWrapper类中存了每个servlet的相关信息。
到这里,业务容器从Engine-->Host--->Conetext--->Wrapper层层驱动,初始化了整个web服务处理核心。
- 业务容器启动完后,启动Connector,根据LifecycleBase模版模式,最终落到startInternal方法上
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
try {
//启动通信处理handler,开启endpoint,监听socket端口
protocolHandler.start();
} catch (Exception e) {
String errPrefix = "";
if(this.service != null) {
errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
}
throw new LifecycleException
(errPrefix + " " + sm.getString
("coyoteConnector.protocolHandlerStartFailed"), e);
}
//绑定connect和container关系,最后存入其属性`Mapper mapper`中
mapperListener.start();
}
再看下endpoint开启监听的代码,其中设置了接收线程等配置,并异步开启了Acceptor进行端口监听。
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
}
initializeConnectionLatch();
// 控制轮询线程
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
// 控制接收线程
for (int i = 0; i < acceptorThreadCount; i++) {
Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
acceptorThread.setPriority(threadPriority);
acceptorThread.setDaemon(getDaemon());
acceptorThread.start();
}
}
}
看下Acceptor类,socket监听实现:
protected class Acceptor implements Runnable {
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
@Override
public void run() {
int errorDelay = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
try {
//if we have reached max connections, wait
awaitConnection();
SocketChannel socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = serverSock.accept();
} catch (IOException ioe) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
}
// Successful accept, reset the error delay
errorDelay = 0;
// Hand this socket off to an appropriate processor
//TODO FIXME - this is currently a blocking call, meaning we will be blocking
//further accepts until there is a thread available.
if ( running && (!paused) && socket != null ) {
// setSocketOptions() will add channel to the poller
// if successful
if (!setSocketOptions(socket)) {
try {
socket.socket().close();
socket.close();
} catch (IOException ix) {
if (log.isDebugEnabled())
log.debug("", ix);
}
} else {
countUpConnection();
}
}
} catch (SocketTimeoutException sx) {
//normal condition
} catch (IOException x) {
if (running) {
log.error(sm.getString("endpoint.accept.fail"), x);
}
} catch (OutOfMemoryError oom) {
try {
oomParachuteData = null;
releaseCaches();
log.error("", oom);
}catch ( Throwable oomt ) {
try {
try {
System.err.println(oomParachuteMsg);
oomt.printStackTrace();
}catch (Throwable letsHopeWeDontGetHere){
ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
}
}catch (Throwable letsHopeWeDontGetHere){
ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.accept.fail"), t);
}
}//while
}//run
}
到此,web服务全部启动成功
总结下知识点:
- Digester 解析xml技术
- LifecycleBase生命周期管理
- fireContainerEvent事件驱动模式
- ExceptionUtils.handleThrowable(Throwable t)异常统一处理方式
目录: tomcat 源码学习系列
上一篇: tomcat启动源码分析(一)--入口代码Bootstrap初始化
下一篇: tomcat启动源码分析(三)--http请求nio处理
网友评论