Tomcat中的Host和Engine级别的servlet容器


这边文章主要介绍的是Host容器 和 Engine容器。如果你想在同一个Tomcat上部署运行多个Context容器的话,你就需要使用Host容器,从理论上来讲,如果你的Tomcat只想要部署一个Context容器的话,你可以不使用Host容器。

在org.apache.catalina.Context接口的描述有下一段话:

Context容器的父容器通常是Host容器,也有可能是其他实现,或者如果不是必要的话,就可以不使用父容器。

但是 在tomcat的实际部署中,总会使用一个Host容器,在下面在解释原因,

Engine容器表示Catalina的整个Servlet引擎,如果使用了Engine容器,那么它总是处于容器层级的最顶层,添加到Enginer容器中的子容器通常是org.apache.catalina.Host 或者 org.apahce.catalina.Context的实现,默认情况下Tomcat会使用一个Engine容器并且使用一个Host容器作为其子容器,

Host接口

host容器是 org.apahce.catalina.Host接口的实例,Host接口继承自Container接口

package org.apache.catalina;

/**
 * 
 * <p>
 * <b>Title:Host.java</b>
 * </p>
 * <p>
 * Copyright:ChenDong 2018
 * </p>
 * <p>
 * Company:仅学习时使用
 * </p>
 * <p>
 * 类功能描述:Host是表示Catalina servlet引擎中的虚拟主机的容器。它在以下类型的场景中很有用:
 * 
 * 
 * 
 * 您希望使用拦截器来查看此特定虚拟主机处理的每个请求。
 * 
 * 您希望使用独立的HTTP连接器运行Catalina,但是仍然希望支持多个虚拟主机。
 * 
 * 通常,在部署连接到Web服务器(如Apache)的Catalina时,您不会使用主机,因为连接器将利用Web服务器的设施来确定应该使用哪个上下文(
 * 或者甚至哪个包装器)来处理这个请求。
 * 
 * 附加到主机的父容器通常是一个引擎,但是可以是一些其他的实现,或者如果不必要的话可以省略。
 * 
 * 
 * 
 * 附加到主机的子容器通常是上下文的实现(表示单个servlet上下文)。
 * </p>
 * 
 * @author 
 * @date 2018年12月15日 下午9:28:58
 * @version 1.0
 */
public interface Host extends Container {

    // ----------------------------------------------------- Manifest Constants

    /**
     *
     * 
     * 当使用<code>addAlias()</code>方法添加新的别名时发送的 {@code ContainerEvent}事件类型。
     */
    public static final String ADD_ALIAS_EVENT = "addAlias";

    /**
     * 当使用<code>removeAlias()</code>移除一个旧的别名时 触发的 {@code ContainerEvent}事件类型
     */
    public static final String REMOVE_ALIAS_EVENT = "removeAlias";

    // ------------------------------------------------------------- Properties

    /**
     * 返回此{@code Host}容器的 根路径,它可以是 绝对路径、相对路径、或者URL
     */
    public String getAppBase();

    /**
     * 
     * 为这个{@code Host}容器 设置一个根路径,它可以是 绝对路径、相对路径、或者URL
     *
     * @param appBase
     *            新的容器根路径
     */
    public void setAppBase(String appBase);

    /**
     * Return the value of the auto deploy flag. If true, it indicates that this
     * host's child webapps should be discovred and automatically deployed.
     */
    public boolean getAutoDeploy();

    /**
     * Set the auto deploy flag value for this host.
     * 
     * @param autoDeploy
     *            The new auto deploy flag
     */
    public void setAutoDeploy(boolean autoDeploy);

    /**
     * 
     * 为新的web应用程序设置 {@code DefaultContext}。
     * 
     * @param defaultContext
     *            新的 DefaultContext
     */
    public void addDefaultContext(DefaultContext defaultContext);

    /**
     * 为新的web应用程序检索 并返回 DefaultContext.
     */
    public DefaultContext getDefaultContext();

    /**
     * 返回此容器表示的虚拟主机的规范、完全限定的名称
     */
    public String getName();

    /**
     * 设置此容器表示的虚拟主机的规范、完全限定的名称
     *
     * @param name
     *            虚拟主机的名称
     *
     * @exception IllegalArgumentException
     *                如果这个名字是 {@code null}
     */
    public void setName(String name);

    // --------------------------------------------------------- Public Methods

    /**
     * 
     * 将DefaultContext 的 config 导入到web应用程序上下文中。
     * 
     * @param context
     *            导入默认Context的web应用程序Context
     */
    public void importDefaultContext(Context context);

    /**
     * 添加应该映射到同一主机的别名
     *
     * @param alias
     *            要被添加的别名
     */
    public void addAlias(String alias);

    /**
     * 
     * 返回此主机的别名集。如果没有定义,则返回一个零长度数组
     */
    public String[] findAliases();

    /**
     * 
     * 返回一个用来处理引用Http请求的 Context 根据 请求的URI 若果不存在则返回
     *
     * @param uri
     *            Request URI to be mapped
     */
    public Context map(String uri);

    /**
     * 从此主机的别名中删除指定的别名
     *
     * @param alias
     *            要被删除的别名
     */
    public void removeAlias(String alias);

}

下面说下它在Tomat中的标准实现

StandardHost类

在Catalina中的 org.apache.catalina.core.StandardHost类 是 org.apache.catalin.Host接口的标准实现,该类继承自 org.apache.catalina.core.ContainerBase类 ,实现了 Host 和 Deployer接口。

与StandardContext 和 StandardWrapper 类 相似,StandardHost类的构造器函数会将一个基础阀的实例 添加到其管道对相中。

/**
     * 
     * 创建一个带有基础阀的 {@code  StandardHost}实例
     */
    public StandardHost() {

        super();
        pipeline.setBasic(new StandardHostValve());

    }

那么 它的基础阀 就是 org.apahce.catalina.core.StandardHostValue类的实例,

当调用 StandardHost 类的 start()方法时,StandardHost实例 会新添加两个阀,分别是 ErrorReportValue类 和 ErrorDispatcherValue类的实例,这个两个阀均位于org.apahce.catalina.values包下,

1 /**
 2      * 启动这个Host.
 3      *
 4      * @exception LifecycleException
 5      *                如果此组件检测到阻止其启动的致命错误
 6      * 
 7      */
 8     public synchronized void start() throws LifecycleException {
 9         // 如果 errorReportValveClass 阀的 完全限定名 不为空 的话 
10         if ((errorReportValveClass != null) && (!errorReportValveClass.equals(""))) {
11             try {
12                 Valve valve = (Valve) Class.forName(errorReportValveClass).newInstance();
13                 //添加这个ErrorReportValve阀
14                 addValve(valve);
15             } catch (Throwable t) {
16                 log(sm.getString("standardHost.invalidErrorReportValveClass", errorReportValveClass));
17             }
18         }
19 
20         //添加一个ErrorDispatcherValve阀
21         addValve(new ErrorDispatcherValve());
22 
23         super.start();
24 
25     }

变量 errorReportValueClass的值 定义在StandardHost类中;

private String errorReportValveClass = "org.apache.catalina.valves.ErrorReportValve";

每当引入一个Http请求的时候,都会调用StandardHost实例的 invoke方法,由于StandardHost类并没有提供invoke方法的实现,因此它会调用父类 ContainerBase 类的 invoke方法,而ContainerBase类 的invoke方法将会调用StandardHost类的 基础阀StandardHostValue实例的invoke方法,StandardHostValue的invoke方法将会调用StandardHost类的map方法来获取响应的Context实例来处理Http请求。

1 /**
 2      * 
 3      * 返回一个Context实例 来处理这个 相对于Host容器的 相对URI所代表的请求,如果没有则返回 <code>null</code>
 4      *
 5      * @param uri
 6      *            要被映射的请求URI
 7      */
 8     public Context map(String uri) {
 9 
10         if (debug > 0)
11             log("Mapping request URI '" + uri + "'");
12         if (uri == null)
13             return (null);
14 
15         // Match on the longest possible context path prefix
16         // 匹配可能是最长的Context路径前缀
17         if (debug > 1)
18             log("  Trying the longest context path prefix");
19         Context context = null;
20         String mapuri = uri;
21         while (true) {
22             // 不断尝试根据路径去子容器中找对应的Context
23             context = (Context) findChild(mapuri);
24             if (context != null)
25                 break;
26             int slash = mapuri.lastIndexOf('/');
27             if (slash < 0)
28                 break;
29             // 不断截取路径最后一个/之前的路径 做匹配路径
30             mapuri = mapuri.substring(0, slash);
31         }
32 
33         
34         // 如果没有匹配到Context 则选择 默认的Context
35         if (context == null) {
36             if (debug > 1)
37                 log("  Trying the default context");
38             context = (Context) findChild("");
39         }
40 
41         //如果还是没有选中的 Context 直接返回null 并返回 错误信息
42         if (context == null) {
43             log(sm.getString("standardHost.mappingError", uri));
44             return (null);
45         }
46 
47         // 返回映射的上下文(如果有的话)
48         if (debug > 0)
49             log(" Mapped to context '" + context.getPath() + "'");
50         return (context);
51 
52     }

在Tomcat 5 和之后的版本中,映射器组件已经移除,Context实例是通过request对象获取。

StandardHostMapper类

在Tomcat4 中,ContainerBase类 (也就是StandardHost的父类),会调用其addDefaultMapper()方法创建一个默认的映射器,默认的映射器的类型 mapperClass属性的值决定,下面是ContainerBase类的addDefaultMapper实现

1 /**
 2      * 如果没有显式配置,则添加默认Mapper实现
 3      *
 4      * @param mapperClass
 5      *            Mapper实现的java完全限定类名
 6      */
 7     protected void addDefaultMapper(String mapperClass) {
 8 
 9         //  若限定名为null 则证明我们不需要映射器 直接返回
10         if (mapperClass == null)
11             return;
12         //如果已经存在了mapper 则也直接返回
13         if (mappers.size() >= 1)
14             return;
15 
16         // 根据指定的限定名 初始化并添加一个 映射器默
17         try {
18             Class clazz = Class.forName(mapperClass);
19             Mapper mapper = (Mapper) clazz.newInstance();
20             //固定http协议
21             mapper.setProtocol("http");
22             addMapper(mapper);
23         } catch (Exception e) {
24             log(sm.getString("containerBase.addDefaultMapper", mapperClass), e);
25         }
26 
27     }

变量 mapperClass的值定义在StandardHost类中;

private String mapperClass = "org.apache.catalina.core.StandardHostMapper";

Standardhost类的start方法 在方法的默认会调用父类的start方法确保默认映射器的创建完成。

注意:学习到这里的朋友 也可能会和我有同样的 疑问 StandardContext 在start方法的结尾难道也调用了 其父类ContainerBase的start方法了么,答案是不是的,在Tomcat4中,StandardContext类创建默认映射器的方法略有不同,它的start方法并不会调用其父类的Start方法,StandardContext类的Start方法会自己调用addDefaultMapper方法 来创建默认的映射器。

当然StandardHostMapper类中最重要的方法 还是map方法,这个map方法 与StandardContextMapper类的map方法相比就要简单的多的多了

StandardHostMapper类的map方法展示

1 public Container map(Request request, boolean update) {
 2         // 如果这个request已经获得了映射的Context对象则直接返回
 3         if (update && (request.getContext() != null))
 4             return (request.getContext());
 5 
 6         // 对我们的请求URI执行映射
 7         String uri = ((HttpRequest) request).getDecodedRequestURI();
 8         //还调用host的map 可见中重点逻辑在于host的map方法
 9         Context context = host.map(uri);
10 
11         //需要更新请求中的映射Context对象么 并返回所选的上下文
12         if (update) {
13             request.setContext(context);
14             if (context != null)
15                 ((HttpRequest) request).setContextPath(context.getPath());
16             else
17                 ((HttpRequest) request).setContextPath(null);
18         }
19         return (context);
20 
21     }

在来一个StandardContextMapper对象的map方法 做一下对比

1 /**
  2      * 
  3      * 根据指的request 从 StandardContext对象的子容器中 找到 匹配的 Wrapper容器,若无则返回null
  4      * 
  5      * @param request
  6      *            要被处理的request
  7      * @param update
  8      *            是否更新request中的Wrapper
  9      *
 10      * @exception IllegalArgumentException
 11      *                如果路径的相对部分不能被URL解码
 12      */
 13     public Container map(Request request, boolean update) {
 14 
 15 
 16         int debug = context.getDebug();
 17 
 18         // 这个请求已经被映射了一个wrapper对象了么?
 19         if (update && (request.getWrapper() != null))
 20             return (request.getWrapper());
 21 
 22         //先获取到相对于Context的URI 就是将请求的整个URI截掉Context的URI 后剩下的URI,
 23         String contextPath =
 24             ((HttpServletRequest) request.getRequest()).getContextPath();
 25         String requestURI = ((HttpRequest) request).getDecodedRequestURI();
 26         String relativeURI = requestURI.substring(contextPath.length());
 27 
 28 
 29         if (debug >= 1)
 30             context.log("Mapping contextPath='" + contextPath +
 31                         "' with requestURI='" + requestURI +
 32                         "' and relativeURI='" + relativeURI + "'");
 33 
 34         // 应用规范中的标准请求URI映射规则
 35         Wrapper wrapper = null;
 36         String servletPath = relativeURI;
 37         String pathInfo = null;
 38         String name = null;
 39 
 40         // 规则 1 -- 精确匹配
 41         if (wrapper == null) {
 42             if (debug >= 2)
 43                 context.log("  Trying exact match(试着精确匹配)");
 44             if (!(relativeURI.equals("/")))
 45                 //根据相对于Context的URI 从Context容器的serveletMapping集合中找到对应wrapper的名字
 46                 name = context.findServletMapping(relativeURI);
 47             if (name != null)
 48                 //如果扎到了名字 则利用Context的 findChild方法 从其子容器中根据名字 找到对应wrapper
 49                 wrapper = (Wrapper) context.findChild(name);
 50             if (wrapper != null) {
 51                 servletPath = relativeURI;
 52                 pathInfo = null;
 53             }
 54         }
 55 
 56         // 规则 2 -- 前缀匹配
 57         if (wrapper == null) {
 58             if (debug >= 2)
 59                 context.log("  Trying prefix match(试着前缀匹配)");
 60             servletPath = relativeURI;
 61             while (true) {
 62                 //前缀匹配 就是 把 相对Context的URI 作为前缀 后面加上/*看能不能找到name
 63                 name = context.findServletMapping(servletPath + "/*");
 64                 if (name != null)
 65                     wrapper = (Wrapper) context.findChild(name);
 66                 if (wrapper != null) {
 67                     pathInfo = relativeURI.substring(servletPath.length());
 68                     if (pathInfo.length() == 0)
 69                         pathInfo = null;
 70                     break;
 71                 }
 72                 int slash = servletPath.lastIndexOf('/');
 73                 if (slash < 0)
 74                     break;
 75                 //逐一减掉最后的/之后的URI
 76                 servletPath = servletPath.substring(0, slash);
 77             }
 78         }
 79 
 80         // Rule 3 -- 扩展匹配
 81         if (wrapper == null) {
 82             if (debug >= 2)
 83                 context.log("  Trying extension match(试着扩展匹配)");
 84             //最后一个斜杠的位置
 85             int slash = relativeURI.lastIndexOf('/');
 86             //如果存在一个斜杠
 87             if (slash >= 0) {
 88                 //截取最后一个斜杠之后的URI
 89                 String last = relativeURI.substring(slash);
 90                 //斜杠之后URI中最后一个.的位置
 91                 int period = last.lastIndexOf('.');
 92                 //如果斜杠之后URI存在 .
 93                 if (period >= 0) {
 94                     //匹配字符串 = * + 斜杠URI 最后一个.之后的URI
 95                     String pattern = "*" + last.substring(period);
 96                     //根据 扩展匹配规则 寻找name
 97                     name = context.findServletMapping(pattern);
 98                     if (name != null)
 99                         wrapper = (Wrapper) context.findChild(name);
100                     if (wrapper != null) {
101                         servletPath = relativeURI;
102                         pathInfo = null;
103                     }
104                 }
105             }
106         }
107 
108         // 规则 4 -- 默认匹配规则
109         if (wrapper == null) {
110             if (debug >= 2)
111                 context.log("  Trying default match(试着默认匹配)");
112             //直接斜杠匹配
113             name = context.findServletMapping("/");
114             if (name != null)
115                 wrapper = (Wrapper) context.findChild(name);
116             if (wrapper != null) {
117                 servletPath = relativeURI;
118                 pathInfo = null;
119             }
120         }
121 
122         // 更新请求中的Wrapper(如果请求 update为true ),然后返回此包装器
123         if ((debug >= 1) && (wrapper != null))
124             context.log(" Mapped to servlet '" + wrapper.getName() +
125                         "' with servlet path '" + servletPath +
126                         "' and path info '" + pathInfo +
127                         "' and update=" + update);
128         //如果需要更新则 将新匹配到的wrpper 更新到request中
129         if (update) {
130             request.setWrapper(wrapper);
131             ((HttpRequest) request).setServletPath(servletPath);
132             ((HttpRequest) request).setPathInfo(pathInfo);
133         }
134         return (wrapper);
135 
136     }

可以看到host的映射器的map方法只是简单的调用了 host的map方法而已。

StandardHostValue(StandardHost的基础阀)

org.apache.catalina.core.StandardHostValue类时StandardHost实例的基础阀,当有引入的HTTp请求时,会调用StandardHost的involve方法 继而 调用ContainerBase类的invoke方法 继而在调用 其管道的invoke方法 继而在调用 StandardHostValue的invoke方法

对其进行处理,

public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {

        // 验证请求和响应对象类型是否有效
        if (!(request.getRequest() instanceof HttpServletRequest) ||
            !(response.getResponse() instanceof HttpServletResponse)) {
            return;     // NOTE - Not much else we can do generically
        }

        // 选择一个Context来处理这个请求
        StandardHost host = (StandardHost) getContainer();
        Context context = (Context) host.map(request, true);
        if (context == null) {
            ((HttpServletResponse) response.getResponse()).sendError
                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                 sm.getString("standardHost.noContext"));
            return;
        }


        //将上Context的类加载器绑定到当前线程

        Thread.currentThread().setContextClassLoader
            (context.getLoader().getClassLoader());

        // 更新会话的最后访问时间(如果有的话)
        HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
        String sessionId = hreq.getRequestedSessionId();
        if (sessionId != null) {
            Manager manager = context.getManager();
            if (manager != null) {
                Session session = manager.findSession(sessionId);
                if ((session != null) && session.isValid())
                    session.access();
            }
        }

        // 请求此Context处理此请求
        context.invoke(request, response);

    }

注意:在获取Context实例的时候有一个往复的过程,上面的map方法需要两个参数,该方法定义在ContainerBase类中,ContainerBase类中的map方法会找到其子容器的映射器,在本例中是StandardHost实例,并调用映射器的 map方法,

然后invoke方法会获取与该request对象相互关联的session对象,并调用其access方法 access方法会修改session对象的最后访问时间,下面是 org.apahce.catalina.session.StandardSession类中access方法的实现

1 public void access() {
2 
3         this.isNew = false;
4         this.lastAccessedTime = this.thisAccessedTime;
5         this.thisAccessedTime = System.currentTimeMillis();
6 
7     }

最后 invoke方法调用Context实例的invoke方法来处理HTTp请求。

为什么必须要有一个Host容器

在tomcat4 和 tomcat 5 中实际部署中,若一个Context实例使用ContextConfig对象进行配置,就必须要使用一个Host对象,原因如下:

使用ContextConfig对象需要知道应用程序web.xml文件的位置,在其 applicationConfig方法中它会试图打开web.xml文件,下面是 applicationConfig方法的片段;

synchronized (webDigester) {
            try {
                URL url = servletContext.getResource(Constants.ApplicationWebXml);

                InputSource is = new InputSource(url.toExternalForm());
                is.setByteStream(stream);
                webDigester.setDebug(getDebug());
                if (context instanceof StandardContext) {
                    ((StandardContext) context).setReplaceWelcomeFiles(true);
                }
                webDigester.clear();
                webDigester.push(context);
                webDigester.parse(is);
            } catch (SAXParseException e) {

其中,Constants.ApplicationWebXml的值为

public static final String ApplicationWebXml = "/WEB-INF/web.xml";

web.xml文件的相对路径,servletContext是一个 org.apache.catalina.core.ApplicationContext类型(实现了javax.servlet.servletContext接口)的对象;

下面是 ApplicationContext类的getResource方法的部分实现代码

1 public URL getResource(String path)
2         throws MalformedURLException {
3 
4         DirContext resources = context.getResources();
5         if (resources != null) {
6             String fullPath = context.getName() + path;
7 
8             // this is the problem. Host must not be null
9             String hostName = context.getParent().getName();

注意最后一行到代码 是需要 使用到Context对象的父容器的名字的,如果要使用ContexConfig实例来进行配置的话,Context实例必须有一个Host实例作为其父容器,简单的来说,除非你自已实现一个ContextConfig类,替换掉配置StandardContext对象的ContextConfig对象,否则你必须使用一个Host容器。

咱们搞一个 实例 试一下 重点看下 怎么使用Host容器的 两个类 一个 简单的ContextConfig 的实现 SimpleContextConfig 主要是为了 在StandardContext在启动时 将 其Configured属性赋值为true,

第二 就是咱们启动的 类 ,话不多说 搞起

第一个简单的ContextConfig类实现 simpleContextConfig

1 package myex13.pyrmont.core;
 2 
 3 import org.apache.catalina.Context;
 4 import org.apache.catalina.Lifecycle;
 5 import org.apache.catalina.LifecycleEvent;
 6 import org.apache.catalina.LifecycleListener;
 7 
 8 /**
 9  * <p>
10  * <b>Title:SimpleContextConfig.java</b>
11  * </p>
12  * <p>
13  * Copyright:ChenDong 2018
14  * </p>
15  * <p>
16  * Company:仅学习时使用
17  * </p>
18  * <p>
19  * 类功能描述:简单的ContextConfig配置类,本例中的功能只是为了在StandardContext
20  * 在start方法执行时,为了让StandardContext可用,必须将其属性 configured 正确配置标志
21  * 设置为true;StandContext才能被标记为可用且启动成功
22  * </p>
23  * 
24  * @author 陈东
25  * @date 2018年12月16日 下午1:16:34
26  * @version 1.0
27  */
28 public class SimpleContextConfig implements LifecycleListener {
29 
30     /**
31      * 
32      * <p>
33      * Title: lifecycleEvent
34      * </p>
35      * 
36      * @date 2018年12月16日 下午1:16:34
37      * 
38      *       <p>
39      *       功能描述:
40      *       </p>
41      * 
42      * @param event
43      * 
44      */
45     @Override
46     public void lifecycleEvent(LifecycleEvent event) {
47         if (Lifecycle.START_EVENT.equals(event.getType())) {
48             Context context = (Context) event.getLifecycle();
49             context.setConfigured(true);
50         }
51 
52     }
53 
54 }

第二个 就是咱们的启动类了

package myex13.pyrmont.startup;

import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;

import myex13.pyrmont.core.SimpleContextConfig;

/**
 * <p>
 * <b>Title:Bootstrap1.java</b>
 * </p>
 * <p>
 * Copyright:ChenDong 2018
 * </p>
 * <p>
 * Company:仅学习时使用
 * </p>
 * <p>
 * 类功能描述: 该示例 重点在于说明Host容器最为顶层容器的使用方法,
 * </p>
 * 
 * @author 陈东
 * @date 2018年12月16日 下午1:27:11
 * @version 1.0
 */
public class Bootstrap1 {

    /**
     * 
     * <p>
     * Title: main
     * </p>
     * 
     * @date 2018年12月16日 下午1:27:11
     * 
     *       <p>
     *       功能描述:
     *       </p>
     * 
     * @param args
     * 
     */
    public static void main(String[] args) {
        // 设置系统属性
        System.setProperty("catalina.base", System.getProperty("user.dir"));
        // 实例化一个连接器
        Connector connector = new HttpConnector();

        // ---------------------------实例化 对应Servlet的Wrapper
        // 对应 PrimitiveServlet
        Wrapper wrapper1 = new StandardWrapper();
        wrapper1.setName("Primitive");
        wrapper1.setServletClass("PrimitiveServlet");

        // 对应 ModernServlet

        Wrapper wrapper2 = new StandardWrapper();
        wrapper2.setName("Modern");
        wrapper2.setServletClass("ModernServlet");

        // -----------------------------实例化 Context 示例
        Context context = new StandardContext();
        context.setPath("/app1");
        context.setDocBase("app1");
        context.addChild(wrapper1);
        context.addChild(wrapper2);
        // 为Context配置一个监听事件 配置器 在咱们这个例子中 不做任何配置 直接将 Context的configured属性配置为ture
        LifecycleListener listener = new SimpleContextConfig();
        ((Lifecycle) context).addLifecycleListener(listener);

        // 重点来了。。。。重点来了,,,,,,,重点来了 ,,,,,期盼已经的Host 在下面即将登场
        Host host = new StandardHost();
        host.addChild(context);
        host.setName("localhost");
        host.setAppBase("webapps");
        Loader loader = new WebappLoader();
        context.setLoader(loader);
        // -------给context增加 servletMapping
        context.addServletMapping("/Primitive", "Primitive");
        context.addServletMapping("/Modern", "Modern");
        connector.setContainer(host);

        try {

            connector.initialize();
            ((Lifecycle) connector).start();
            ((Lifecycle) host).start();

            // 方便随时停止
            System.in.read();
            ((Lifecycle) host).stop();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

注意 为了 Host 需要为其创建根路径的 文件夹滴

看下我的文件结构

为Host容器 设置的根路径就是 webapps,然后为其子容器Context 设置的根路径为 app1.

Engine接口

Engine容器是org.apache.catalina.Engine接口的实例。Engine容器也就是Tomat的Servlet引擎,当部署Tomcat时要支持多个虚拟机的话,就需要使用Engine容器,事实上,一般情况下,部署的Tomcat都会使用一个Engine容器

1 package org.apache.catalina;
 2 
 3 /**
 4  * 
 5  * <p>
 6  * <b>Title:Engine.java</b>
 7  * </p>
 8  * <p>
 9  * Copyright:ChenDong 2018
10  * </p>
11  * <p>
12  * Company:仅学习时使用
13  * </p>
14  * <p>
15  * 类功能描述:
16  * <h1>Engine</h1> 表示整个Container Servlet 引擎,标准实现是
17  * <code>org.apache.catalina.core.StandardEngine</code>
18  * </p>
19  * 
20  * @author 
21  * @date 2018年12月16日 下午3:30:08
22  * @version 1.0
23  */
24 public interface Engine extends Container {
25 
26     // ------------------------------------------------------------- Properties
27 
28     /**
29      * 
30      * 从引擎中返回一个默认的虚拟机容器名
31      */
32     public String getDefaultHost();
33 
34     /**
35      * 
36      * 为引擎设置一个默认的虚拟机容器名
37      *
38      * @param defaultHost
39      *            新的默认的虚拟机容器名
40      */
41     public void setDefaultHost(String defaultHost);
42 
43     /**
44      * 检索此引擎的 {@code JvmRouteId}
45      */
46     public String getJvmRoute();
47 
48     /**
49      * Set the JvmRouteId for this engine. 设置此引擎的 {@code JvmRouteId}
50      *
51      * @param jvmRouteId
52      *            (新的)JVM路由ID(JvmRouteId)。集群中的每个引擎必须具有唯一的JVM路由ID(JvmRouteId)
53      */
54     public void setJvmRoute(String jvmRouteId);
55 
56     /**
57      * 
58      * 返回与我们相关联的 <code>Service</code> (如果有的话)
59      */
60     public Service getService();
61 
62     /**
63      * 设置与该引擎相关的 <code>Service</code> ,如果有的话
64      *
65      * @param service
66      *            引擎拥有的服务
67      */
68     public void setService(Service service);
69 
70     /**
71      * 为新的web应用程序设置DefaultContext.
72      *
73      * @param defaultContext
74      *            新的 DefaultContext
75      */
76     public void addDefaultContext(DefaultContext defaultContext);
77 
78     /**
79      * 检索新Web应用程序的DefaultContext。
80      */
81     public DefaultContext getDefaultContext();
82 
83     // --------------------------------------------------------- Public Methods
84 
85     /**
86      * 将DefaultContext配置导入到web应用程序Context文中。
87      *
88      * @param context
89      *            导入DefaultContext的web应用程序Context
90      */
91     public void importDefaultContext(Context context);
92 
93 }

在Engine容器中,可以设置一个默认的Host容器或者一个默认的Context容器,注意,Engine容器可以与一个服务器实例相关联,服务器咱们之后的文章再说。那么下面介绍一下 Catalina中 Engine的默认标准实现

StandardEngine

org.apache.catalina.core.StandardEngine类时 Engine接口的标准实现,相比于StandardContext类和StandardHost类,StandardEngine类相对小一些,在实例化的时候,StandardEngine类会添加一个基础阀,如其默认的构造器

1     /**
 2      * 
 3      * 使用默认无参数构造器创建一个 设置了基础阀{@link StandardEngineValve}的{@link StandardEngine}实例
 4      */
 5     public StandardEngine() {
 6 
 7         super();
 8         pipeline.setBasic(new StandardEngineValve());
 9 
10     }

作为一个顶层容器,Engine容器可以由子容器,而它的子容器只能是Host容器,所以,若是给它设置了一个非Host类型的子容器,就会抛出异常,下面是StandardEngine类的addChild方法的实现代码;

1 /**
 2      * 
 3      * 添加一个子容器,但是它的子容器仅限为 Host类型的Container
 4      * 
 5      * @param child
 6      *           要被添加的子容器
 7      */
 8     public void addChild(Container child) {
 9         //子容器非Host直接抛出异常
10         if (!(child instanceof Host))
11             throw new IllegalArgumentException(sm.getString("standardEngine.notHost"));
12         super.addChild(child);
13 
14     }

此外,因为Engine容器已经是顶层容器了 所有是不可能也不允许在拥有父容器了。如果调用StandardEngine类的setParent方法,为其添加一个父容器时,就会抛出异常

1 /**
 2      *
 3      * 因为StandardEngine已经贵为顶层容器,不可能在有父容器了,所以若setParent方法被触发 直接抛出错误
 4      *
 5      * @param container
 6      *            建议父容器
 7      */
 8     public void setParent(Container container) {
 9 
10         throw new IllegalArgumentException(sm.getString("standardEngine.notParent"));
11 
12     }

StandardEngineValue

org.apache.catalina.core.StandardEngineValue类时StandardEngine容器的基础阀,那么套路那还是那个老一套 ,这里就不啰嗦了, 直接看invoke方法

1 public void invoke(Request request, Response response, ValveContext valveContext)
 2             throws IOException, ServletException {
 3         //看看 请求 和 响应 是不是有效滴
 4         if (!(request.getRequest() instanceof HttpServletRequest)
 5                 || !(response.getResponse() instanceof HttpServletResponse)) {
 6             return; // NOTE - Not much else we can do generically
 7         }
 8 
 9         // 验证任何HTTP/1.1的请求 是否包括主机头
10         HttpServletRequest hrequest = (HttpServletRequest) request;
11         if ("HTTP/1.1".equals(hrequest.getProtocol()) && (hrequest.getServerName() == null)) {
12             ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST,
13                     sm.getString("standardEngine.noHostHeader", request.getRequest().getServerName()));
14             return;
15         }
16 
17         // 选择用于此请求的host容器
18         StandardEngine engine = (StandardEngine) getContainer();
19         Host host = (Host) engine.map(request, true);
20         if (host == null) {
21             ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST,
22                     sm.getString("standardEngine.noHost", request.getRequest().getServerName()));
23             return;
24         }
25 
26         //让 host容器的invoke方法 处理请求
27         host.invoke(request, response);
28 
29     }

有点老生常谈的感觉,老套路,在验证了request和response对象的类型之后,invoke反方得到Host实例,用于处理请求,invoke方法会通过调用Engine的map方法获取host对象,但是StandardEngine类没有实现Map反方 所以该是 去ContainerBase的map方法

基础阀invoke 调用 ContainerBase的map方法 ,然后ContainerBase的map方法 根据 请求的协议 找到对应映射器,所以下面咱们该看StandardEngineMapper的map方法了

1 public Container map(Request request, boolean update) {
 2 
 3         int debug = engine.getDebug();
 4 
 5         // 提取请求的服务器名称
 6         String server = request.getRequest().getServerName();
 7         if (server == null) {
 8             // 没有就搞个默认的
 9             server = engine.getDefaultHost();
10             // 需要更新就让request更新一下
11             if (update)
12                 request.setServerName(server);
13         }
14         // 没有server 的不用玩了 直接返回null
15         if (server == null)
16             return (null);
17         server = server.toLowerCase();
18         if (debug >= 1)
19             engine.log("Mapping server name '" + server + "'");
20 
21         // 直接查找匹配的子子容器
22         if (debug >= 2)
23             engine.log(" Trying a direct match");
24         Host host = (Host) engine.findChild(server);
25 
26         // 通过别名查找匹配的host。
27         if (host == null) {
28             if (debug >= 2)
29                 engine.log(" Trying an alias match");
30             Container children[] = engine.findChildren();
31             for (int i = 0; i < children.length; i++) {
32                 String aliases[] = ((Host) children[i]).findAliases();
33                 for (int j = 0; j < aliases.length; j++) {
34                     if (server.equals(aliases[j])) {
35                         host = (Host) children[i];
36                         break;
37                     }
38                 }
39                 if (host != null)
40                     break;
41             }
42         }
43 
44         // 尝试使用“默认”Host
45         if (host == null) {
46             if (debug >= 2)
47                 engine.log(" Trying the default host");
48             host = (Host) engine.findChild(engine.getDefaultHost());
49         }
50 
51         // Update the Request if requested, and return the selected Host
52         ; // No update to the Request is required
53         return (host);
54 
55     }

关于Engine容器 的一些重要的点 上面基本都展示l 下面来一个示例

1 package myex13.pyrmont.startup;
  2 
  3 import org.apache.catalina.Connector;
  4 import org.apache.catalina.Context;
  5 import org.apache.catalina.Engine;
  6 import org.apache.catalina.Host;
  7 import org.apache.catalina.Lifecycle;
  8 import org.apache.catalina.LifecycleListener;
  9 import org.apache.catalina.Loader;
 10 import org.apache.catalina.Wrapper;
 11 import org.apache.catalina.connector.http.HttpConnector;
 12 import org.apache.catalina.core.StandardContext;
 13 import org.apache.catalina.core.StandardEngine;
 14 import org.apache.catalina.core.StandardHost;
 15 import org.apache.catalina.core.StandardWrapper;
 16 import org.apache.catalina.loader.WebappLoader;
 17 
 18 import ex20.pyrmont.standardmbeantest.StandardAgent;
 19 import myex13.pyrmont.core.SimpleContextConfig;
 20 
 21 /**
 22  * <p>
 23  * <b>Title:Bootstrap2.java</b>
 24  * </p>
 25  * <p>
 26  * Copyright:ChenDong 2018
 27  * </p>
 28  * <p>
 29  * Company:仅学习时使用
 30  * </p>
 31  * <p>
 32  * 类功能描述: 重点在于说明如何使用作为顶层容器Engine的实例,
 33  * </p>
 34  * 
 35  * @author 陈东
 36  * @date 2018年12月16日 下午4:10:27
 37  * @version 1.0
 38  */
 39 public class Bootstrap2 {
 40 
 41     /**
 42      * 
 43      * <p>
 44      * Title: main
 45      * </p>
 46      * 
 47      * @date 2018年12月16日 下午4:10:27
 48      * 
 49      *       <p>
 50      *       功能描述:
 51      *       </p>
 52      * 
 53      * @param args
 54      * 
 55      */
 56     public static void main(String[] args) {
 57         System.setProperty("catalina.base", System.getProperty("user.dir"));
 58         // -----------实例化连接器
 59         Connector connector = new HttpConnector();
 60 
 61         // 实例化Wrapepr
 62         Wrapper wrapper1 = new StandardWrapper();
 63         wrapper1.setName("Primitive");
 64         wrapper1.setServletClass("PrimitiveServlet");
 65 
 66         Wrapper wrapper2 = new StandardWrapper();
 67 
 68         wrapper2.setName("Modern");
 69         wrapper2.setServletClass("ModernServlet");
 70 
 71         // 实例化 Context
 72         Context context = new StandardContext();
 73         // 设置根路径
 74         context.setPath("/app1");
 75         // 设置根文件夹
 76         context.setDocBase("app1");
 77         context.addChild(wrapper1);
 78         context.addChild(wrapper2);
 79         // 添加配置监听器
 80         LifecycleListener listener = new SimpleContextConfig();
 81         ((Lifecycle) context).addLifecycleListener(listener);
 82 
 83         // 实例化一个host容器
 84         Host host = new StandardHost();
 85         host.addChild(context);
 86         host.setName("localhost");
 87         // 设置host的根路径文件
 88         host.setAppBase("webapps");
 89         Loader loader = new WebappLoader();
 90         context.setLoader(loader);
 91 
 92         context.addServletMapping("/Primitive", "Primitive");
 93         context.addServletMapping("/Modern", "Modern");
 94 
 95         // 实例化一个 Engine容器
 96         Engine engine = new StandardEngine();
 97 
 98         engine.addChild(host);
 99         // 设置默认的Host容器 对应上文的 Host设置的名字
100         engine.setDefaultHost("localhost");
101 
102         connector.setContainer(engine);
103 
104         try {
105             connector.initialize();
106             ((Lifecycle) connector).start();
107             ((Lifecycle) engine).start();
108 
109             System.in.read();
110             ((Lifecycle) engine).stop();
111 
112         } catch (Exception e) {
113             e.printStackTrace();
114         }
115 
116     }
117 
118 }

上面已经将Host 和 Engine容器 分别介绍了与其相关的类,也粉别展示了Host 和 Enginer容器作为 Tomcat中顶层容器的使用方法。那么就先搞这些吧


原文链接:https://www.cnblogs.com/ChenD/p/10125124.html