Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper

Tomcat源码系列文章

Tomcat源码解析(一):Tomcat整体架构

Tomcat源码解析(二):Bootstrap和Catalina

Tomcat源码解析(三):LifeCycle生命周期管理

Tomcat源码解析(四):StandardServer和StandardService

Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper



前言

  前文中我们介绍了StandServer与StandService的init与start方法,而Service的init方法和start方法则是调用顶级容器Engine、请求url映射Mapper、连接器Connector的init和start方法。本文就介绍下容器的init和start及Mapper的组成。

容器接口与实现类的类图如下:

在这里插入图片描述


一、Container容器

1、容器基本组成及关系

  • Container的子容器Engine、Host、Context、Wrapper 是逐层包含的关系
  • Wrapper:表示一个Servlet,它负责管理Servlet的生命周期
    • Wrapper作为容器中的最底层,不能包含子容器
  • Context:表示一个Web应用程序,是Servlet、Filter的父容器
    • 一个Web应用可包含多个Wrapper
  • Host:表示一个虚拟主机,包含主机名称和IP地址,默认是localhost
    • Tomcat可以配置多个虚拟主机地址
    • 一个虚拟主机下可包含多个Context
  • Engine:表示顶级容器,接受连接器的所有请求,并将响应返回给连接器
    • 一个Service最多只能有一个Engine,但是一个Engine可包含多个Host

在这里插入图片描述

Context和Host的区别

  Context表示一个应用,比如,默认配置下webapps下的每个目录都是一个应用,其中ROOT目录中存放着主应用,其他目录存放着别的子应用。
  而整个webapps是一个Host(站点,有名虚拟主机)。假如www.excelib.com域名对应着webapps目录所代表的站点,其中的ROOT目录里的应用就是主应用,访问时直接使用域名就可以,而webapps/test目录存放的是test子应用,访问时需要www.excelib.com/test,每一个应用对应一个Context ,所有webapps下的应用都属于www.excelib.com站点,而blog.excelib.com则是另外一个站点,属于另外一个Host。

2、Container接口

  • Container的实现类Engine、Host、Context、Wrapper都具有父子关系(注意不是继承)
  • Container提供了父节点的设置、查找以及子节点的添加、删除、查找
  • 由于一个父节点可以有多个子节点因此返回的是数组
public interface Container extends Lifecycle {
	public Container getParent();
	public void setParent(Container container);
	public void addChild(Container child);
	public Container[] findChildren();
	public void removeChild(Container child);
}

二、StandardEngine

  Engine的实现类为StandardEngine,在前文中我们了解到StandardService在init时调用了engine.init()来初始化engine,StandardEngine没有重写Init方法,但由于StandardEngine继承了ContainerBase从而间接继承了LifecycleBase抽象类,得以复用LifecycleBase中的init方法,start方法也是如此,因此以engine为代表的这些子容器实际上只需要重写initInternal、startInternal即可。

1、解析server.xml

  • Engine组件是Service组件中的请求处理组件
  • 在Service组件中有且只有一个
  • Engine组件从一个或多个Connector中接受请求并处理,并将完成的响应返回给Connector,最终传递给客户端
<!--
name: ⽤于指定Engine 的名称, 默认为Catalina
defaultHost:默认使⽤的虚拟主机名称, 当客户端请求指向的主机⽆效时, 
将交由默认的虚拟主机处理, 默认为localhost
-->
<Engine name="Catalina" defaultHost="localhost">
...
</Engine>

2、解析<Engine>标签

# Catalina#createStartDigester方法

digester.addRuleSet(new EngineRuleSet("Server/Service/"));
  • <Engine>标签内容用来实例化StandardEngine组件
  • 通过Service的setContainer方法将StandardEngine对象设置到Service的engine属性中
# EngineRuleSet#addRuleInstances方法

// prefix = "Server/Service/"
/** 解析<Engine>标签实例化StandardEngine对象 **/
digester.addObjectCreate(prefix + "Engine",
                         "org.apache.catalina.core.StandardEngine",
                         "className");
                         
/** 解析<Engine>标签将标签中属性值映射到StandardEngine对象中 **/          
digester.addSetProperties(prefix + "Engine");

/** 将StandardEngine对象通过Service的setContainer设置到Service的engine属性中 **/   
digester.addSetNext(prefix + "Engine",
                    "setContainer",
                    "org.apache.catalina.Engine");

3、initInternal初始化

  • 调用super.initInternal(),也就是ContainerBase的initInternal方法
@Override
protected void initInternal() throws LifecycleException {
    // Realm:安全认证的,不用管
    getRealm();
    super.initInternal();
}
  • ContainerBase的initInternal方法如下
  • 主要创建了一个线程池,将会在下文用于启动子容器,这样可以用多个线程来同时启动,效率更高
protected ThreadPoolExecutor startStopExecutor;

@Override
protected void initInternal() throws LifecycleException {
	// 创建线程安全的队列
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    // 创建线程池
    startStopExecutor = new ThreadPoolExecutor(
            getStartStopThreadsInternal(),
            getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
            startStopQueue,
            new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    // 调用父类LifecycleMBeanBase的初始化方法
    super.initInternal();
}

4、startInternal启动

  • 和initInternal一样,直接调用了父类ContainerBase中的该方法
protected synchronized void startInternal() throws LifecycleException {
    if (log.isInfoEnabled()) {
        log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
    }
    super.startInternal();
}
  • ContainerBase的startInternal方法如下
  • 核心内容使用线程池异步调用子容器的start方法
  • 每一个容器都有一个Pipeline对象,处理请求时候,依次经过每个容器的Pipeline,做一些处理,后面章节讲
protected synchronized void startInternal() throws LifecycleException {
     // Cluster 用于配置集群,在server.xml 中有注释的参考配置,它的作用就是同步Session
     Cluster cluster = getClusterInternal();
     if (cluster instanceof Lifecycle) {
         ((Lifecycle) cluster).start();
     }

     // Realm 是Tomcat 的安全域,可以用来管理资源的访问权限
     Realm realm = getRealmInternal();
     if (realm instanceof Lifecycle) {
         ((Lifecycle) realm).start();
     }
	
    // 将要启动的子容器加入到线程池中异步启动
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }
	
	// 省略获取子容器启动结果results,正常启动返回null,如果返回结果则证明异常,抛出错误
	...	

	// 子容器启动完成后接着启动容器的管道,管道下篇文章中详细讲解
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }
    
    // 设置状态为启动中,这里修改状态是为了触发监听器
    setState(LifecycleState.STARTING);
    
    // 启动后台线程,该线程将定期检查会话超时
    threadStart();
}

// 启动子容器的线程类型StartChild 是一个实现了Callable 的内部类,主要作用就是调用子容器的start 方法
private static class StartChild implements Callable<Void> {

    private Container child;

    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}

5、stopInternal停止

  • StandardEngine没有重写stopInternal 方法,停止默认调用ContainerBase的stopInternal方法
// ContainerBase的stopInternal方法
@Override
protected synchronized void stopInternal() throws LifecycleException {

    // 停止定期检查会话超时的后台线程
    threadStop();

	// 设置状态为停止中
    setState(LifecycleState.STOPPING);

    // 调用容器管道的关闭方法
    if (pipeline instanceof Lifecycle &&
            ((Lifecycle) pipeline).getState().isAvailable()) {
        ((Lifecycle) pipeline).stop();
    }

    // 调用子容器的stop方法
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StopChild(children[i])));
    }

	// 省略容器关闭结果处理,如果关闭中出现错误结果results,抛出异常
	...

    // 停止Realm和Cluster
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).stop();
    }
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).stop();
    }
}

6、destroyInternal销毁

  • StandardEngine没有重写destroyInternal方法,销毁默认调用ContainerBase的destroyInternal方法
// ContainerBase的destroyInternal方法
@Override
protected void destroyInternal() throws LifecycleException {

    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).destroy();
    }
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).destroy();
    }

    // 调用容器管道的销毁方法
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).destroy();
    }

    // 获取所有的子容器,调用子容器的销毁方法
    for (Container child : findChildren()) {
        removeChild(child);
    }

    // 从父容器中移除子容器,并调用当前容器的销毁方法
    if (parent != null) {
        parent.removeChild(this);
    }

    // 中断所有工作线程,并清空工作队列,拒绝新提交的任务,
    if (startStopExecutor != null) {
        startStopExecutor.shutdownNow();
    }
	
	// 父类LifecycleMBeanBase的方法,jmx内容
    super.destroyInternal();
}

三、StandardHost

  Host是Engine的子容器。Engine组件中可以内嵌一个或多个Host组件,每个Host组件代表Engine中的一个虚拟主机。Host组件至少有一个,且其中一个的name必须与Engine组件的defaultHost属性相匹配。

1、解析server.xml

  • Host虚拟主机的作用,运行多个Web应用(一个Context代表一个Web应用 ),并负责安装、展开、启动和结束每个Web应用
  • Host组件代表的虚拟主机,对应了服务器中一个网络名实体(如"www.test.com"或IP地址"116.25.25.25"),为了使用户可以通过网络名连接Tomcat服务器,这个名字应该在DNS服务器上注册
  • Tomcat从HTTP头中提取主机名,寻找名称匹配的主机。如果没有匹配,请求将转发至默认主机(不需要再DNS服务器中注册)
<!--
name:虚拟主机名。一个Engine中有且仅有一个Host组件的name属性与Engine组件的defaultHost属性相匹配
unpackWARs:是否将代表Web应用的WAR文件解压;
	true:通过解压后的文件结构运行该Web应用;
	false:直接使用WAR文件运行web应用
autoDeploy和、APPBase、xmlBase、deployOnStartup属性,与Host内Web应用的自动部署有关
-->
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
...
</Host>

2、解析<Host>标签

# Catalina#createStartDigester方法

digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
  • <Host>标签内容用来实例化StandardHost组件
  • 通过Engine的addChild方法将StandardHost对象设置到StandardEngine的父类ContainerBase的children的map集合属性中
  • HostConfig监听器添加到Host的父类LifecycleBase的监听器集合lifecycleListeners中,很重要下面讲
# HostRuleSet#addRuleInstances方法

// prefix = "Server/Service/Engine/"
/** 解析<Host>标签实例化StandardHost对象 **/
digester.addObjectCreate(prefix + "Host",
                         "org.apache.catalina.core.StandardHost",
                         "className");

/** 将HostConfig添加到Host的父类LifecycleBase的监听器集合lifecycleListeners中 **/ 
digester.addRule(prefix + "Host",
                 new LifecycleListenerRule
                 ("org.apache.catalina.startup.HostConfig",
                  "hostConfigClass"));

/** 解析<Host>标签将标签中属性值映射到StandardHost对象中 **/ 
digester.addSetProperties(prefix + "Host");

/** 通过StandardEngine的addChild方法设置到Engine的children属性中 **/   
digester.addSetNext(prefix + "Host",
                    "addChild",
                    "org.apache.catalina.Container");

3、initInternal初始化

  StandardHost没有重写initlntemal 方法,初始化默认调用ContainerBase的initlntemal方法,ContainerBase的initlntemal方法上面说过了,就是创建了一个线程池,用于启动子容器,StandardEngine创建线程池用于启动子容器StandardHost,StandardHost也创建线程池用于启动子容器StandardContext(下面会讲)。

4、startInternal启动

  核心内容调用父类ContainerBase的startInternal方法上面说过了,就是使用线程池异步调用子容器的start方法,子容器启动完成后接着启动容器的管道。

@Override
protected synchronized void startInternal() throws LifecycleException {
	
	// 省略检查错误报告Class
	...
	
    super.startInternal();
}

5、停止和销毁方法

  StandardHost与StandardEngine一样,都没有重新stopInternal和destroyInternal方法,默认调用ContainerBase的stopInternal和destroyInternal方法。

6、监听器HostConfig

6.1、监听器触发时机

  在前面章节LifeCycle生命周期管理中讲过,生命周期状态转化成功之后会触发事件,如下setStateInternal转化状态的公共方法,初始化、启动前中后都会切换状态,调用如下公共方法,触发事件实际就是调用监听器的lifecycleEvent方法

// LifecycleBase类属性和方法

private volatile LifecycleState state = LifecycleState.NEW;private synchronized void setStateInternal(LifecycleState state,
        Object data, boolean check) throws LifecycleException {
    // 是否校验状态,一般初始化启动流程这里都是false
    if (check) {
        // state不允许为null
        if (state == null) {
        	// 抛异常
            invalidTransition("null");
            return;
        }
        if (!(state == LifecycleState.FAILED ||
                (this.state == LifecycleState.STARTING_PREP &&
                        state == LifecycleState.STARTING) ||
                (this.state == LifecycleState.STOPPING_PREP &&
                        state == LifecycleState.STOPPING) ||
                (this.state == LifecycleState.FAILED &&
                        state == LifecycleState.STOPPING))) {
            invalidTransition(state.name());
        }
    }
    // 设置状态
    this.state = state;
    // 触发事件
    String lifecycleEvent = state.getLifecycleEvent();
    if (lifecycleEvent != null) {
        fireLifecycleEvent(lifecycleEvent, data);
    }
}

protected void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    for (LifecycleListener listener : lifecycleListeners) {
        listener.lifecycleEvent(event);
    }
}
  • 解析<Host>标签的时候,在Host对象里添加了监听器HostConfig,下面看下HostConfig的lifecycleEvent方法
// HostConfig类方法
@Override
public void lifecycleEvent(LifecycleEvent event) {

	...

    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
    	// 定时检查(检查资源修改以触发重新部署、热部署等)
        check();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
    	// 启动前调用,创建webapps目录
        beforeStart();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
    	// 启动中调用
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
    	// 停止中调用
        stop();
    }
}

public void start() {

	...
	// true表示应发现并自动部署此主机的子Web应用
    if (host.getDeployOnStartup()) {
        deployApps();
    }

}

6.2、Web应用部署

  • Web应用的三种部署类型
    • 解析指定的部署文件(.xml文件),加载Web应用,可以进行热部署
    • 读取webapps目录下的web应用的war包
    • 读取webapps目录下的以目录形式存在的web应用
// HostConfig类方法
protected void deployApps() {
    File appBase = host.getAppBaseFile();
    File configBase = host.getConfigBaseFile();
    // 如下图,获取webapps下的文件
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // 部署XML
    deployDescriptors(configBase, configBase.list());
    // 部署WAR
    deployWARs(appBase, filteredAppPaths);
    // 部署扩展的文件夹
    deployDirectories(appBase, filteredAppPaths);
}

在这里插入图片描述

  • 部署WAR(files即webapps目录下文件)
// HostConfig类方法
protected void deployWARs(File appBase, String[] files) {
    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();

    for (int i = 0; i < files.length; i++) {
		// 文件夹为META-INF或WEB-INF跳过不处理
        if (files[i].equalsIgnoreCase("META-INF")) {
            continue;
        }
        if (files[i].equalsIgnoreCase("WEB-INF")) {
            continue;
        }
        File war = new File(appBase, files[i]);
        if (files[i].toLowerCase(Locale.ENGLISH).endsWith(".war") &&
                war.isFile() && !invalidWars.contains(files[i]) ) {
			// 这里以war包文件名+/命名,如果springmvc.war,Context path = /springmvc
            ContextName cn = new ContextName(files[i], true);
			
			// 服务已部署,文件路径文件名不能包含*和?两个字符,否则跳过不处理
			...

            results.add(es.submit(new DeployWar(this, cn, war)));
        }
    }
}

6.2、Context的实例化

  • 反射实例化Context的实现类StandardContext,添加监听器ContextConfig
  • 将context添加到父容器host的子容器集合中
    • host容器启动时候会启动所有的子容器,但那个时候Context还没有创建
    • 修改host容器状态为启动中时候,触发监听器,创建Conext,添加到host子容器集合中
    • 此时添加到子容器集合中的这个动作就包含了host子容器Context的启动
  • path属性,我这里为/springmvc
    • 指定了访问该web应用的上下文路径,当请求到来时,tomcat根据web应用的path属性与URI的匹配程度来选择web应用处理相应请求
    • 如果一个Context元素的path属性为"",则这个Context是虚拟主机的默认web应用
    • 当请求的URI与所有path都不匹配时,使用该默认web应用来处理
    • 可通过http://localhost:8080/springmvc访问我们的自己的项目
    • 也可以http://localhost:8080/,最后的“/”表示http的根-ROOT
// HostConfig内部类DeployWar,线程启动调用deployWAR方法

protected String contextClass = "org.apache.catalina.core.StandardContext";

protected void deployWAR(ContextName cn, File war) {
	
	// 省略,只留关键代码
	...	
	// 实例化Context
	Context context = (Context) Class.forName(contextClass).getConstructor().newInstance();
	
	// 添加监听器ContextConfig
    Class<?> clazz = Class.forName(host.getConfigClass());
    LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
    context.addLifecycleListener(listener);
	
	// 设置context属性,name和path这里是/springmvc
    context.setName(cn.getName());
    context.setPath(cn.getPath());
    context.setWebappVersion(cn.getVersion());
    context.setDocBase(cn.getBaseName() + ".war");
    // 将context添加到父容器host中并启动context
    host.addChild(context);
}

四、StandardContext

1、initInternal初始化

  主要内容调用父类ContainerBase的initlntemal方法,ContainerBase的initlntemal方法上面说过了,就是创建了一个线程池,用于启动子容器。

2、startInternal启动

  与之前容器不同的是,不再调用父类ContainerBase的startInternal方法,对于子类和容器通道的启动都在StandContext类的startInternal方法中。StandContext启动很复杂,涉及很多知识面,我这里只列举出一些比较重要的点。

// 
@Override
protected synchronized void startInternal() throws LifecycleException {

	...

	// ContextConfig监听器
	// 解析web.xml或注解里Servlet、Filter、Listener
	// 并创建对应的Wrapper
	fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

    // 启动Context子节点Wrapper。
    for (Container child : findChildren()) {
        if (!child.getState().isAvailable()) {
            child.start();
        }
    }

    // 启动Context的pipeline(下个章节单独将)
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    // 将Context的Web资源集合添加到ServletContext。
    if (ok) {
        getServletContext().setAttribute(Globals.RESOURCES_ATTR, getResources());
    }

    // 将Jar包扫描器添加到ServletContext。
    if (ok) {
        getServletContext().setAttribute(
                JarScanner.class.getName(), getJarScanner());
    }

    // 实例化application域监听器,并执行
    if (ok) {
        if (!listenerStart()) {
            log.error(sm.getString("standardContext.listenerFail"));
            ok = false;
        }
    }

    // 实例化FilterConfig、Filter并调用Filter.init()。
    if (ok) {
        if (!filterStart()) {
            log.error(sm.getString("standardContext.filterFail"));
            ok = false;
        }
    }

    // 对于loadOnStartup大于等于0的Wrapper,调用Wrapper.load()
    // 该方法负责实例化Servlet,并调用Servlet.init()进行初始化。
    if (ok) {
        if (!loadOnStartup(findChildren())){
            log.error(sm.getString("standardContext.servletFail"));
            ok = false;
        }
    }
	
	...

}

2.1、监听器ContextConfig

  • context启动中修改状态,触发监听器configureStart方法
// ContextConfig类方法
@Override
public void lifecycleEvent(LifecycleEvent event) {
	...
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
        configureStart();// Context容器启动中触发调用方法
    } 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();
    }
	...
}

protected synchronized void configureStart() {
	...
	// 核心方法,解析web.xml和注解
    webConfig();
    ...
}        
2.1.1、解析web.xml或注解里Servlet、Filter、Listener
  • 获取/WEB-INF/web.xml的资源输入流
  • 解析web.xml中的Servlet、Filter、Listener添加到webXml中
  • 筛选/WEB-INF/classes路径下所有@WebServlet、@WebFilter、@WebListener添加到webXml中
// ContextConfig类方法
protected void webConfig() {
	...
	// xml和注解里的Servlet、Filter、Listener都会添加到这里
    WebXml webXml = createWebXml();

    // 获取/WEB-INF/web.xml的资源输入流
    InputSource contextWebXml = getContextWebXmlSource();
    // 解析xml中的Servlet、Filter、Listener
    if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
        ok = false;
    }
	
	if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
	   // 筛选/WEB-INF/classes路径下所有@WebServlet、@WebFilter、@WebListener
	   processClasses(webXml, orderedFragments);
	}	
	
	// 添加wrapper为context的子容器
	configureContext(webXml);
	...
}
  • 递归循环/WEB-INF/classes路径下的Class,根据注解处理
// ContextConfig类方法
protected void processClass(WebXml fragment, JavaClass clazz) {
    AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
    if (annotationsEntries != null) {
        String className = clazz.getClassName();
        // 遍历类注解处理Servlet、Filter或Listener
        for (AnnotationEntry ae : annotationsEntries) {
            String type = ae.getAnnotationType();
            if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {
                processAnnotationWebServlet(className, ae, fragment);
            }else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {
                processAnnotationWebFilter(className, ae, fragment);
            }else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {
                fragment.addListener(className);
            } else {
                // Unknown annotation - ignore
            }
        }
    }
}
2.1.1.1、创建StandardWrapper、设置启动项、初始化参数
  • 创建StandardWrapper并设置启动项(是否项目启动实例化和初始化)初始化参数(添加<init-param>参数到wrapper也就是以后的Servelt)
  • 最后将StandardWrapper添加到context的子容器集合中,并进行初始化和启动
// ContextConfig类方法
private void configureContext(WebXml webxml) {
    // 添加web.xml中的监听器
    for (String listener : webxml.getListeners()) {
        context.addApplicationListener(listener);
    }
    // 其余代码
    for (ServletDef servlet : webxml.getServlets().values()) {
        // 获取web.xml中配置的servlet
        Wrapper wrapper = context.createWrapper();
        // 启动项设置
        if (servlet.getLoadOnStartup() != null) {
            wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
        }
        wrapper.setName(servlet.getServletName());
        // 配置servlet初始化参数
        Map<String,String> params = servlet.getParameterMap();
        for (Entry<String, String> entry : params.entrySet()) {
            wrapper.addInitParameter(entry.getKey(), entry.getValue());
        }
        // 设置Servlet的权限定类名,以后会通过它反射获取空的Servelt对象
        wrapper.setServletClass(servlet.getServletClass());
        // 添加为context的子容器
        context.addChild(wrapper);
    }
    for (Entry<String, String> entry :
            webxml.getServletMappings().entrySet()) {
        context.addServletMappingDecoded(entry.getKey(), entry.getValue());
    }
    // 其余代码
}

// StandardContext类方法
@Override
public Wrapper createWrapper() {

    Wrapper wrapper = null;
    if (wrapperClass != null) {
        try {
            wrapper = (Wrapper) wrapperClass.getConstructor().newInstance();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error("createWrapper", t);
            return null;
        }
    } else {
        wrapper = new StandardWrapper();
    }
    ...
    return wrapper;
}
    

2.2、ServletContext的实现类

  • 根据ServletContext创建上下文ApplicationContext对象
// StandardContext类方法
@Override
public ServletContext getServletContext() {

    if (context == null) {
        context = new ApplicationContext(this);
        if (altDDName != null)
            context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
    }
    return (context.getFacade());

}
  • 所以获取ServletContext实际是获取实现类ApplicationContextFacade
  • 门面模式,对外不暴露ApplicationContext,在ApplicationContextFacade里设置对外提供的方法属性,实际调用的ApplicationContext的方法属性
// ApplicationContext类方法
private final ServletContext facade = new ApplicationContextFacade(this);
protected ServletContext getFacade() {
	return (this.facade);
}

// ApplicationContextFacade类
public class ApplicationContextFacade implements
	 org.apache.catalina.servlet4preview.ServletContext {
    private final ApplicationContext context;
}

2.3、实例化ServletContextListener监听器并执行

public boolean listenerStart() {
    // 其余代码
    // 遍历所有监听器找出ServletContextListener类别的
    for (Object instance : instances) {
        if (!(instance instanceof ServletContextListener)) {
            continue;
        }
        ServletContextListener listener = (ServletContextListener) instance;
        try {
            fireContainerEvent("beforeContextInitialized", listener);
            // 调用其contextInitialized方法
            if (noPluggabilityListeners.contains(listener)) {
                listener.contextInitialized(tldEvent);
            } else {
                listener.contextInitialized(event);
            }
            fireContainerEvent("afterContextInitialized", listener);
        } 
        // 其余代码
    }
}
  • 实现ServletContextListener接口,并添加@WebListener注解,就是一个监听器
public interface ServletContextListener extends EventListener {
	// ServletContext创建时调用(context启动时)
    public void contextInitialized(ServletContextEvent sce);
	// ServletContext销毁时调用
    public void contextDestroyed(ServletContextEvent sce);
}

在这里插入图片描述

2.4、实例化Filter并初始化

  • 获取xml和注解中解析出来的Filter的map集合遍历
  • 实例化和初始化Filter
  • 将Filter添加到filterConfigs中,以后接受对应Servlet请求时会遍历调用
// StandardContext类方法
private HashMap<String, FilterDef> filterDefs = new HashMap<>();

private HashMap<String, ApplicationFilterConfig> filterConfigs =
        new HashMap<>();
public boolean filterStart() {
	...
	for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
	    String name = entry.getKey();
	
	    try {
	        ApplicationFilterConfig filterConfig =
	                new ApplicationFilterConfig(this, entry.getValue());
	        filterConfigs.put(name, filterConfig);
	    } catch (Throwable t) {
	...
	    }
	}
	...
}
// ApplicationFilterConfig类
ApplicationFilterConfig(Context context, FilterDef filterDef)
        throws xxxException {

    super();

    this.context = context;
    this.filterDef = filterDef;
    // Allocate a new filter instance if necessary
    if (filterDef.getFilter() == null) {
        getFilter();
    } else {
        this.filter = filterDef.getFilter();
        getInstanceManager().newInstance(filter);
        initFilter();
    }
}

Filter getFilter() throws xxxException {

	...

    // 实例化Filter
    String filterClass = filterDef.getFilterClass();
    this.filter = (Filter) getInstanceManager().newInstance(filterClass);
	
	// 调用Filter的init方法
    initFilter();

    return (this.filter);
}

2.5、实例化loadOnStartup>0的Servlet并初始化

  在web.xml中我们对于需要启动时加载的servlet会配置<load-on-startup>1</load-on-startup>,这个功能的实现也是在StandardContext#startInternal中完成的。

public boolean loadOnStartup(Container children[]) {
    // 获取所有loadOnStartup>0的Wrapper放入数字为key的map,这样数字越小越先被实例化
    TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
    for (Container child : children) {
        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);
    }

    for (ArrayList<Wrapper> list : map.values()) {
        for (Wrapper wrapper : list) {
            try {
                wrapper.load();
            } 
        }
    }
    return true;

}
  • 获取所有loadOnStartup>=0的Wrapper放入数字为key的map,这样数字越小越先被实例化
  • 具体的实现是由对应wrapper的load方法完成的,下面介绍StandardWrapper再进入

3、stopInternal停止

  • 调用filter的销毁方法destroy
  • 调用监听器的销毁方法contextDestroyed
@Override
protected synchronized void stopInternal() throws LifecycleException {
	// 给正在进行的异步请求一个完成的机会
	long limit = System.currentTimeMillis() + unloadDelay;
	while (inProgressAsyncCount.get() > 0 && System.currentTimeMillis() < limit) {
	    try {
	        Thread.sleep(50);
	    } catch (InterruptedException e) {
	        log.info(sm.getString("standardContext.stop.asyncWaitInterrupted"), e);
	        break;
	    }
	}
	
	// 将状态设置为 STOPPING 后,上下文将报告自身不可用,并且任何正在进行的异步请求都将超时
	setState(LifecycleState.STOPPING);

	// 调用子容器的stop方法
    final Container[] children = findChildren();
    for (int i = 0; i < children.length; i++) {
        children[i].stop();
    }

    // 调用filter的销毁方法destroy
    filterStop();

	// 调用监听器的销毁方法contextDestroyed
    listenerStop();
	
	...
}
    

4、destroyInternal销毁

  核心内容调用父类ContainerBase的destroyInternal方法。

五、StandardWrapper

  StandardWrapper是在StandardContext的监听器ContextConfig里解析web.xml或@WebServlet注解类内容创建而来。最后再根据StandardWrapper内容创建Servlet。
  StandardWrapper没有重写initInternal方法,而startInternal方法核心内容也是调用父类方法,接着上面实例化loadOnStartup>0的Servlet并初始化内容,查看load()方法。

// StandardWrapper类方法
public synchronized void load() throws ServletException {
    // 创建当前Servlet对象,并初始化
    instance = loadServlet();
    // 其余代码
}

1、创建Servlet

  • 根据web.xml配置或@WebServlet注解获取的Servelt的权限定类名servletClass反射获取对象,并强转为Servlet类型
    • 这里说明,Servelt必须继承HttpServelt,否则强转Servelt报错
  • 调用servlet的init方法传入的参数为ServletConfig接口实现类StandardWrapperFacade
    • 与ServletContext实际是获取实现类ApplicationContextFacade一样,都采用门面模式
    • 同样使用StandardWrapperFacade类将ServletConfig接口实现类StandardWrapper包装一下
    • 这样对外只暴露ServletConfig接口的方法,具体实现类StandardWrapper的方法属性则访问不到
// StandardWrapper类方法
public synchronized Servlet loadServlet() throws ServletException {
	...
	
    Servlet servlet;
    try {
		...
        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
        	// servletClass是创建StandardWrapper时候,set进来的
        	// 反射获取Servelt对象
            servlet = (Servlet) instanceManager.newInstance(servletClass);
        } catch (ClassCastException e) {
			...
        } 

		...
		// 调用servlet的init方法
        initServlet(servlet);
		
    } finally {
		...
    }
    return servlet;

}

// StandardWrapper类属性,创建StandardWrapper时,就赋值了此属性
protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);

// 初始化方法
private synchronized void initServlet(Servlet servlet)
        throws ServletException {
    try {
        if( Globals.IS_SECURITY_ENABLED) {
			...
        } else {
        	// 传入StandardWrapperFacade调用初始化方法
            servlet.init(facade);
        }
    } catch (UnavailableException f) {
		...
    } 
}
// 门面模式,将StandardWrapper放入StandardWrapperFacade对象里
// 对外只提供ServletConfig接口相关方法
public final class StandardWrapperFacade implements ServletConfig {

    private final ServletConfig config;
    
    public StandardWrapperFacade(StandardWrapper config) {
        super();
        this.config = config;
    }
    ...
}

2、ServletConfig

  • 注解方式创建Servelt一般继承HttpServlet类,类图如下

在这里插入图片描述

  • Servlet接口中定义了init方法,GenericServlet实现init方法
  • 上面说到的servlet.init(facade);实际时调用GenericServlet抽象类的init方法,然后将StandardWrapperFacade传递进来赋值config
  • 这样看来平常使用的ServletConfig实际时StandardWrapperFacade类中的StandardWrapper
public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {

    private transient ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        // 空实现,我们自定义的Servelt可以去覆盖无参init方法
        this.init();
    }

    public void init() throws ServletException {
        // NOOP by default
    }
}

六、Mapper组件

  说起Mapper组件需要回到StandardService的启动方法startInternal。顶级容器engine启动,使用线程池异步调用多个子容器StandardHost的start方法,host的启动,调用多个子容器StandardContext的start方法(包括监听器、过滤器、Servlet的实例化和初始化)。可以说顶级容器engine的启动所有的子容器都将启动。接下来需要组装Mapper组件(请求url和Servlet映射)。

protected final MapperListener mapperListener = new MapperListener(this);

// StandardService类方法
@Override
protected void startInternal() throws LifecycleException {
	...

    // 顶级容器engine启动
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }
	
	...
	
	// 启动mapperListener,则时组装Mapper
    mapperListener.start();

	...
}

在这里插入图片描述

  • 直接查看注册核心方法
// MapperListener类方法
private void registerContext(Context context) {

	...
    List<WrapperMappingInfo> wrappers = new ArrayList<>();

    for (Container container : context.findChildren()) {
        prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerWrapper",
                    container.getName(), contextPath, service));
        }
    }

    mapper.addContextVersion(host.getName(), host, contextPath,
            context.getWebappVersion(), context, welcomeFiles, resources,
            wrappers);

	...
}

  prepareWrapperMappingInfo用于准备注册到mapper下的wrapper,这儿mapper对于wrapper的支持是wrapper的包装对象WrapperMappingInfo。而一个context可能有多个wrapper,所以WrapperMappingInfo是一个list。
  简单来说就是将映射urlwrapper名字和资源只读标记等信息组合成对象添加到wrappers中。

private void prepareWrapperMappingInfo(Context context, Wrapper wrapper, List<WrapperMappingInfo> wrappers) {
    String wrapperName = wrapper.getName();
    boolean resourceOnly = context.isResourceOnlyServlet(wrapperName);
    String[] mappings = wrapper.findMappings();
    for (String mapping : mappings) {
        boolean jspWildCard = (wrapperName.equals("jsp") && mapping.endsWith("/*"));
        wrappers.add(new WrapperMappingInfo(mapping, wrapper, jspWildCard, resourceOnly));
    }
}

  最后addContextVersion方法又各种转化嵌套,对于我们自己定义的Servlet的Mapper映射对象的位置如下。
在这里插入图片描述
  每个Service都有一个Mapper,如此看来,Mapper对象则记录了所有应用项目下的MappedWrapper(请求映射和Servelt对应的Wrapper),这样以后拿着请求mapping映射即可从Mapper中找到对应的Servelt。


总结

  至此,整个容器的启动过程就介绍完了,可以看到整个流程是由Server起步直到Wrapper结束。
  其中Server代表的是整个tomcat应用,Service代表的是server.xml中的service节点。而后续的Engine与Host都是service中的子节点。
  Context代表了webapps下的每个应用,子容器Wrapper表示web应用中的每个servlet。Context中的start方法中会创建当前web应用的ServletContext,实例化ServletContextListener监听器并执行,实例化Filter并调用初始化方法,实例化需要启动时加载的Servlet(loadOnStartup>0)并调用初始化方法。
  最后容器启动后,组装每个应用下请求url和Servlet映射Mapper组件,后续请求时候需要。

在这里插入图片描述

  • 103
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 28
    评论
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冬天vs不冷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值