• 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中顶层容器的使用方法。那么就先搞这些吧

  • 相关阅读:
    trueStudio笔记
    C笔记
    printf打印输出
    DB9针和DB25针串口的引脚定义
    通信单位
    简单工厂
    不同进程之间发送消息将指定界面置顶
    Delegate event 委托事件---两个From窗体使用委托事件
    Winfrom窗体无法关闭问题--检查是否存在重写
    自定义控件添加事件
  • 原文地址:https://www.cnblogs.com/ChenD/p/10125124.html
Copyright © 2020-2023  润新知