Tomcat源码解析-容器组件之StandardHost

1 Container容器

Container容器用来表示tomcat中servlet容器,负责servelt的加载和管理,处理请求ServletRequest,并返回标准的 ServletResponse 对象给连接器。

image

Container容器组件

tomcat 将Container容器按功能分为4个组件,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。

  • Wrapper:表示一个 Servlet

  • Context:表示一个 Web 应用程序,一个 Web 应用程序中可能会有多个 Servlet

  • Host:表示的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序

  • Engine:表示引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine。

image

2 StandardHost类结构

image
2.1 Lifecycle接口
public interface Lifecycle {
    ....
    // 初始化方法
    public void init() throws LifecycleException;
    // 启动方法
    public void start() throws LifecycleException;
    // 停止方法,和start对应
    public void stop() throws LifecycleException;
    // 销毁方法,和init对应
    public void destroy() throws LifecycleException;
    // 获取生命周期状态
    public LifecycleState getState();
    // 获取字符串类型的生命周期状态
    public String getStateName();
}

Lifecycle接口定义tomcat中所有组件的生命周期相关接口方法。Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中实现。而子类就负责实现自己的初始化、启动和停止等模板方法。

详见 Tomcat架构设计-组件生命周期 Lifecycle

2.2 Container接口
public interface Container extends Lifecycle {
    
    public static final String ADD_CHILD_EVENT = "addChild";

    public static final String ADD_VALVE_EVENT = "addValve";

    public static final String REMOVE_CHILD_EVENT = "removeChild";
    
    public static final String REMOVE_VALVE_EVENT = "removeValve";
    
    //返回日志组件
    public Log getLogger();
    
    //返回日志名称
    public String getLogName();
    
    //返回容器注册到JMX bean ObjectName
    public ObjectName getObjectName();
    
    //返回容器注册到JMX bean 命名空间
    public String getDomain();
    
    //返回容器注册到JMX bean 属性
    public String getMBeanKeyProperties();
    
    //返回容器依赖Pipeline组件
    public Pipeline getPipeline();
    
    //返回容器依赖Cluster组件
    public Cluster getCluster();
    
    //设置容器依赖Cluster组件
    public void setCluster(Cluster cluster);
    
    //返回周期性任务执行间隔事件
    public int getBackgroundProcessorDelay();
    
    //设置周期性任务执行间隔事件
    public void setBackgroundProcessorDelay(int delay);
    
    //返回容器名称
    public String getName();
    
    //设置容器名称
    public void setName(String name);
    
    //返回父容器
    public Container getParent();
    
    //设置父容器
    public void setParent(Container container);
    
    //返回父类加载器
    public ClassLoader getParentClassLoader();
    
    //设置父类加载器
    public void setParentClassLoader(ClassLoader parent);
    
    //返回容器依赖Realm组件
    public Realm getRealm();
    
    // 设置容器依赖Realm组件
    public void setRealm(Realm realm);
    
    //容器默认周期性任务处理调用方法
    public void backgroundProcess();
    
    //为当前容器组件添加子容器组件
    public void addChild(Container child);
    
    //添加容器事件监听器
    public void addContainerListener(ContainerListener listener);
    
    //添加属性变更监听器
    public void addPropertyChangeListener(PropertyChangeListener listener);
    
    //查找指定名称的子容器
    public Container findChild(String name);
    
    //获取所有子容器组件
    public Container[] findChildren();
    
    //返回所有容器事件监听器
    public ContainerListener[] findContainerListeners();

    //删除子容器
    public void removeChild(Container child);
    
    //当前容器删除容器事件监听器
    public void removeContainerListener(ContainerListener listener);
    
    //当前容器删除属性变更监听器
    public void removePropertyChangeListener(PropertyChangeListener listener);
    
    //处理容器事件
    public void fireContainerEvent(String type, Object data);
    
    //使用AccessLog组件打印请求日志
    public void logAccess(Request request, Response response, long time,
            boolean useDefault);
    
    //返回访问日志组件AccessLog
    public AccessLog getAccessLog();
    
    //返回设置处理子容器启动关闭线程池核心线程数。
    public int getStartStopThreads();

    //设置处理子容器启动关闭线程池核心线程数。
    public void setStartStopThreads(int startStopThreads);

    //返回tomcat工作目录
    public File getCatalinaBase();

    //返回tomcat安装目录
    public File getCatalinaHome();
}

Container接口定义tomcat中所有容器组件的通用接口方法。Tomcat 定义一个基类ContainerBase 来实现Container 接口,把一些公共的逻辑放到基类中实现。

详见 Tomcat架构设计-容器组件基类 ContainerBase

3 Tomcat中虚拟主机Host

在tomcat中最核心功能就是将一个静态资源目录或一个应用程序部署到容器中。而这个容器就是指得Host容器组件。而静态资源或一个应用程序通过Context容器组件来表示。所谓部署就是加载到Host容器的子组件中。当然虚拟主机除了部署外还又其他功能,包括热部署,懒加载,别名等。

3.1 部署静态资源

如果想要将一个静态资源目录部署到Tomcat服务器上,tomcat提供了多种部署方式

在server.xml中配置

<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true" copyXML="true" xmlBase="conf\Catalina\localhost">
         <Context path="/JavaWebApp" docBase="F:\JavaWebDemoProject" />
 </Host>

path表示Context根路径,docBase表示映射静态资源目录

在xmlBase路径下配置xml文件

在$CATALINA_BASE/xmlBase 路径下创建 JavaWebApp.xml,xmlBase配置在Host标签属性中

<Context docBase="F:\JavaWebDemoProject" />

文件名称表示Context根路径,docBase表示映射静态资源目录

将资源文件拷贝到appBase路径下

appBase路径在Host标签属性中定义,文件名称表示Context根路径。

3.2 部署应用程序

部署应用程序到appBase目录

appBase是在server.xml文件Host标签appBase属性来定义,appBase可以填写相对路径或者绝对路径,如果是相对路径那么完整路径为CATALINA_BASE/appBase,这里CATALINA_BASE表示tomcat的工作目录

 <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true" copyXML="true" xmlBase="conf\Catalina\localhost">
3.3 懒部署

虚拟主机Host可以在设置在使用时在部署静态资源或应用程序。

3.4 热部署

虚拟主机Host会定期检查appBase和xmlBase目录下新Web应用程序或静态资源,如果发生更新则会触发对应context组件的重新加载

3.5 别名

虚拟主机Host可以定义别名。

3.6 管理子容器组件

StandardHost并管理子容器Context组件,以及从父类ContainerBase,LifecycleBase 继承的通用组件。

4 StandardHost职能&属性

StandardHost实现了Host接口,在了解StandardHost功能之前我们需要了解
Host接口.

Host接口

public interface Host extends Container {

    public static final String ADD_ALIAS_EVENT = "addAlias";

    public static final String REMOVE_ALIAS_EVENT = "removeAlias";

    /**
     * 返回配置文件相对路径,配置文件完整路径为$catalinaBase/xmlBase
     */
    public String getXmlBase();

    /**
     * 设置配置文件相对路径
     */
    public void setXmlBase(String xmlBase);

    /**
     * 返回配置文件对象,对应完整路径为$catalinaBase/xmlBase
     */
    public File getConfigBaseFile();

    /**
     * 返回扫描的应用程序相对路径或绝对路径,如果是相对路径则完整路径为$catalinaHome/appBase
     */
    public String getAppBase();

    /**
     * 设置扫描的应用程序相对路径或绝对路径
     */
    public void setAppBase(String appBase);

    /**
     * 返回扫描应用程序目录的文件对象
     */
    public File getAppBaseFile();

    /**
     * 是否开启热部署
     */
    public boolean getAutoDeploy();

    /**
     * 设置是否支持热部署
     */
    public void setAutoDeploy(boolean autoDeploy);

    /**
     * 获取子组件Context配置实现类,默认org.apache.catalina.startup.ContextConfig
     */
    public String getConfigClass();

    /**
     * 设置子组件Context配置实现类
     */
    public void setConfigClass(String configClass);

    /**
     * 返回是否在启动Host组件时是否应自动部署Host组件的Web应用程序
     */
    public boolean getDeployOnStartup();

    /**
     * 设置是否在启动Host组件时是否应自动部署Host组件的Web应用程序
     */
    public void setDeployOnStartup(boolean deployOnStartup);

    /**
     * 返回正则表达式,用String表示,用来定义自动部署哪些应用程序
     */
    public String getDeployIgnore();

    /**
     * 返回正则表达式,用来定义自动部署哪些应用程序
     */
    public Pattern getDeployIgnorePattern();

    /**
     * 设置正则表达式,用String表示,用来定义自动部署哪些应用程序
     */
    public void setDeployIgnore(String deployIgnore);

    /**
     * 返回处理子容器启动关闭线程池
     */
    public ExecutorService getStartStopExecutor();

    /**
     * 返回是否需要在启动时创建appbase和xmlbase目录
     */
    public boolean getCreateDirs();

    /**
     * 设置是否需要在启动时创建appbase和xmlbase目录
     */
    public void setCreateDirs(boolean createDirs);

    /**
     * 返回是否检查现在可以取消部署的旧版本的应用程序
     */
    public boolean getUndeployOldVersions();

    /**
     * 设置是否检查现在可以取消部署的旧版本的应用程序
     */
    public void setUndeployOldVersions(boolean undeployOldVersions);

    /**
     * 给Host组件添加别名
     */
    public void addAlias(String alias);

    /**
     * 返回Host组件所有别名
     */
    public String[] findAliases();

    /**
     * 给Host组件删除别名
     */
    public void removeAlias(String alias);
}

StandardHost实现Host接口,Host接口用来对Tomcat中虚拟主机功能配置提供了访问方法。

4.1 StandardHost职能

StandardHost只对虚拟机功能配置做了定义,其具体实现由HostConfig来实现。同时负责管理子容器Context组件(下图蓝色),以及从父类ContainerBase(下图红色),LifecycleBase(下图黄色) 继承的通用组件。

image
4.2 核心属性
    /**
     * Host组件别名
     */
    private String[] aliases = new String[0];

    /**
     * 处理Host组件别名同步锁对象
     */
    private final Object aliasesLock = new Object();

    /**
     * appBase表示扫描的应用程序相对路径或绝对路径,如果是相对路径则完整路径为$catalinaHome/appBase
     */
    private String appBase = "webapps";

    /**
     * appBaseFile表示扫描的应用程序目录的文件对象
     */
    private volatile File appBaseFile = null;

    /**
     * xmlBase表示配置文件相对路径,配置文件路径为$catalinaBase/xmlBase
     */
    private String xmlBase = null;

    /**
     * hostConfigBase表示配置文件对象,对应路径为$catalinaBase/xmlBase
     */
    private volatile File hostConfigBase = null;

    /**
     * 是否支持热部署
     * 如果为true,虚拟主机Host会定期检查appBase和xmlBase目录下新Web应用程序或静态资源,如果发生更新则会触发对应context组件的重新加载
     */
    private boolean autoDeploy = true;

    /**
     * 子组件Context配置实现类
     */
    private String configClass =
        "org.apache.catalina.startup.ContextConfig";

    /**
     * 子组件Context实现类
     */
    private String contextClass =
        "org.apache.catalina.core.StandardContext";

    /**
     * 标识在启动Host组件时是否应自动部署Host组件的Web应用程序。标志的值默认为true。
     */
    private boolean deployOnStartup = true;

    /**
     * 是否要禁止应用程序中定义/META-INF/context.xml
     */
    private boolean deployXML = !Globals.IS_SECURITY_ENABLED;

    /**
     * 如果在应用程序中定义了/META-INF/context.xml,是否要拷贝到$catalinaBase/xmlBase目录下
     */
    private boolean copyXML = false;

    /**
     * Host组件子组件Pilpline组件内处理异常Valve实现类
     */
    private String errorReportValveClass =
        "org.apache.catalina.valves.ErrorReportValve";

    /**
     * 是否解压war包种应用程序在执行,默认为true
     */
    private boolean unpackWARs = true;

    /**
     * 标识host组件工作的临时目录
     * $catalinaBase/workDir
     */
    private String workDir = null;

    /**
     * 标识是否需要在启动时创建appbase和xmlbase目录
     */
    private boolean createDirs = true;

5 StandardHost运行流程

tomcat中所有组件都需要经历如下流程。

image
5.1 构建StandardHost

Tomcat使用Digester解析server.xml,Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。相对于SAX,Digester可以针对每一个xml标签设置对应的解析规则。详见 Tomcat相关技术-Digester(二)

Tomcat在Catalina组件初始化阶段调用createStartDigester()创建Digester对象,Digester对象内包含解析server.xml规则,接着通过Digester对象解析server.xml实例化StandardHost,并对部分属性设置值.

server.xml配置

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>

解析<Host>标签及子标签tomcat使用规则组HostRuleSet,其中定义了解析规则。

5.1.1 解析<Host>标签
        //解析<Server><Service><Engine><Host>标签
        /** 解析<Host>标签实例化StandardHost对象,并push到操作栈中 **/
        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");

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


        /** 解析<Host>标签,使用CopyParentClassLoaderRule规则,负责调用次栈顶对象getParentClassLoader获取父类加载,设置到栈顶对象parentClassLoader属性上 **/
        digester.addRule(prefix + "Host",
                         new CopyParentClassLoaderRule());

        /** 解析<Host>标签,使用LifecycleListenerRule规则,负责给栈顶对象添加一个生命周期监听器. 默认为hostConfigClass,或者在标签指定org.apache.catalina.startup.HostConfig属性**/
        digester.addRule(prefix + "Host",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.HostConfig",
                          "hostConfigClass"));

        /** 解析<Host>标签将操作栈栈顶对象作为次栈顶对象StandardService.addChild方法调用的参数,即将实例化StandardHost对象添加StandardServer.child子容器列表属性中**/
        digester.addSetNext(prefix + "Host",
                            "addChild",
                            "org.apache.catalina.Container");
5.1.2 解析<Listener>标签
        /** 解析<Listener>标签实例化标签中className属性定义的对象,并push到操作栈中 **/
        digester.addObjectCreate(prefix + "Host/Listener",
                                 null, // MUST be specified in the element
                                 "className");
        /** 解析<Listener>标签将标签中属性值映射到其实例化对象中**/
        digester.addSetProperties(prefix + "Host/Listener");
        /** 解析</Listener>标签将操作栈栈顶对象作为次栈顶对象StandardHost.addLifecycleListener方法调用的参数,设置到StandardHost属性中**/
        digester.addSetNext(prefix + "Host/Listener",
                            "addLifecycleListener",
                            "org.apache.catalina.LifecycleListener");
5.1.3 解析<Valve>标签
        /** 解析<Valve>标签实例化标签中className属性定义的对象,并push到操作栈中 **/
        digester.addObjectCreate(prefix + "Host/Valve",
                                 null,
                                 "className");
        /** 解析<Valve>标签将标签中属性值映射到其实例化对象中**/
        digester.addSetProperties(prefix + "Host/Valve");
        /** 解析</Valve>标签将操作栈栈顶对象作为次栈顶对象StandardHost.addValve方法调用的参数,设置到StandardHost属性中**/
        digester.addSetNext(prefix + "Host/Valve",
                            "addValve",
                            "org.apache.catalina.Valve");
5.1.4 解析<Alias>标签,
/** 解析<Alias>标签,将标签中数据<Alias>test<Alias>做为参数调用栈顶对象StandardHost.addAlias方法调用的参数,设置到StandardHost属性中 **/
        digester.addCallMethod(prefix + "Host/Alias",
                               "addAlias", 0);
5.1.5 解析<Realm>标签
    //解析<Server><Service><Engine><Host><Realm>标签
    digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
        
        
    /**
     * 解析Realm标签添加到栈顶对象属性中
     */
    @SuppressWarnings("deprecation")
    public class RealmRuleSet extends RuleSetBase {  
    
        ...省略代码

        @Override
        public void addRuleInstances(Digester digester) {
            StringBuilder pattern = new StringBuilder(prefix);
            for (int i = 0; i < MAX_NESTED_REALM_LEVELS; i++) {
                if (i > 0) {
                    pattern.append('/');
                }
                pattern.append("Realm");
                addRuleInstances(digester, pattern.toString(), i == 0 ? "setRealm" : "addRealm");
            }
        }
    
        private void addRuleInstances(Digester digester, String pattern, String methodName) {
            digester.addObjectCreate(pattern, null /* MUST be specified in the element */,
                    "className");
            digester.addSetProperties(pattern);
            digester.addSetNext(pattern, methodName, "org.apache.catalina.Realm");
            digester.addRuleSet(new CredentialHandlerRuleSet(pattern + "/"));
        }
5.1.6 重要的规则

CopyParentClassLoaderRule规则

CopyParentClassLoaderRule规则,负责调用次栈顶对象getParentClassLoader获取父类加载,设置到栈顶对象parentClassLoader属性上

public class CopyParentClassLoaderRule extends Rule {


    public CopyParentClassLoaderRule() {
    }
    
    @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

        if (digester.getLogger().isDebugEnabled())
            digester.getLogger().debug("Copying parent class loader");
        Container child = (Container) digester.peek(0);
        Object parent = digester.peek(1);
        Method method =
            parent.getClass().getMethod("getParentClassLoader", new Class[0]);
        ClassLoader classLoader =
            (ClassLoader) method.invoke(parent, new Object[0]);
        child.setParentClassLoader(classLoader);

    }
}

LifecycleListenerRule规则

LifecycleListenerRule 规则负责给栈顶对象添加一个生命周期监听器.

/**
 *  解析标签给栈顶对象添加一个生命周期监听器
 */
public class LifecycleListenerRule extends Rule {
    
    public LifecycleListenerRule(String listenerClass, String attributeName) {
        this.listenerClass = listenerClass;
        this.attributeName = attributeName;
    }
    /**
     * 标准中指定属性,用来设置监听器处理类
     */
    private final String attributeName;

    /**
     * 默认监听器处理类
     */
    private final String listenerClass;
    
    @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

        /** 获取栈顶原始对象 **/
        Container c = (Container) digester.peek();

        /** 获取次栈顶元素对象 **/
        Container p = null;
        Object obj = digester.peek(1);

        /** 如果栈顶元素对象是容器设置给p **/
        if (obj instanceof Container) {
            p = (Container) obj;
        }

        String className = null;

        /** 获取标签attributeName值赋值给className **/
        if (attributeName != null) {
            String value = attributes.getValue(attributeName);
            if (value != null)
                className = value;
        }

        /** 获取次栈顶对象attributeName属性值赋值给className **/
        if (p != null && className == null) {
            String configClass =
                (String) IntrospectionUtils.getProperty(p, attributeName);
            if (configClass != null && configClass.length() > 0) {
                className = configClass;
            }
        }

        /** 如果className == null使用listenerClass作为className默认值**/
        if (className == null) {
            className = listenerClass;
        }


        /** 实例化className添加栈顶对象生命周期监听器列表中*/
        Class<?> clazz = Class.forName(className);
        LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
        c.addLifecycleListener(listener);
    }
}

组件生命周期

接下来初始化开始则进入tomcat组件的生命周期,对于tomcat中所有组件都必须实现Lifecycle,Tomcat 定义一个基类LifecycleBase 来实现 Lifecycle 接口,把一些公共的逻辑放到基类中实现,比如生命状态的转变与维护、生命事件的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等模板方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上 Internal,叫 initInternal、startInternal 等。

StandardHost父类对容器的初始化、启动和停止等模板方法进行的了默认实现。子类容器只需要重写父类实现即可实现扩展。

5.2 启动StandardHost
    /**
     * 启动组件模板实现
     */
    @Override
    protected synchronized void startInternal() throws LifecycleException {

        /** 将errorReportValveClass 类对象添加到Host组件Pipeline组件内 **/
        String errorValve = getErrorReportValveClass();
        if ((errorValve != null) && (!errorValve.equals(""))) {
            try {
                boolean found = false;
                Valve[] valves = getPipeline().getValves();
                for (Valve valve : valves) {
                    if (errorValve.equals(valve.getClass().getName())) {
                        found = true;
                        break;
                    }
                }
                if(!found) {
                    Valve valve =
                        (Valve) Class.forName(errorValve).getConstructor().newInstance();
                    getPipeline().addValve(valve);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString(
                        "standardHost.invalidErrorReportValveClass",
                        errorValve), t);
            }
        }
        super.startInternal();
    }

StandardEngine其他生命周期实现均从父类ContainerBase继承。

6 核心方法

6.1 重写addChild

为添加的子容器设置生命周期监听器MemoryLeakTrackingListener

    /**
     * 添加一个子容器
     */
    @Override
    public void addChild(Container child) {
        /** 给子容器组件添加MemoryLeakTrackingListener监听器 **/
        child.addLifecycleListener(new MemoryLeakTrackingListener());

        if (!(child instanceof Context))
            throw new IllegalArgumentException
                (sm.getString("standardHost.notContext"));
        super.addChild(child);
    }


    /**
     * 处理AFTER_START_EVENT生命周期事件,设置childClassLoaders属性
     */
    private class MemoryLeakTrackingListener implements LifecycleListener {
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
                if (event.getSource() instanceof Context) {
                    Context context = ((Context) event.getSource());
                    childClassLoaders.put(context.getLoader().getClassLoader(),
                            context.getServletContext().getContextPath());
                }
            }
        }
    }
6.2 管理别名
/**
     * 给host组件添加别名
     */
    @Override
    public void addAlias(String alias) {
        alias = alias.toLowerCase(Locale.ENGLISH);
        synchronized (aliasesLock) {
            // Skip duplicate aliases
            for (int i = 0; i < aliases.length; i++) {
                if (aliases[i].equals(alias))
                    return;
            }
            // Add this alias to the list
            String newAliases[] = new String[aliases.length + 1];
            for (int i = 0; i < aliases.length; i++)
                newAliases[i] = aliases[i];
            newAliases[aliases.length] = alias;
            aliases = newAliases;
        }
        /** 触发属性变更 **/
        fireContainerEvent(ADD_ALIAS_EVENT, alias);
    }

    /**
     * 返回host组件所有别名
     */
    @Override
    public String[] findAliases() {
        synchronized (aliasesLock) {
            return (this.aliases);
        }
    }


    /**
     * 删除host组件别名
     */
    @Override
    public void removeAlias(String alias) {

        alias = alias.toLowerCase(Locale.ENGLISH);

        synchronized (aliasesLock) {

            int n = -1;
            for (int i = 0; i < aliases.length; i++) {
                if (aliases[i].equals(alias)) {
                    n = i;
                    break;
                }
            }
            if (n < 0)
                return;

            int j = 0;
            String results[] = new String[aliases.length - 1];
            for (int i = 0; i < aliases.length; i++) {
                if (i != n)
                    results[j++] = aliases[i];
            }
            aliases = results;
        }

        // Inform interested listeners
        fireContainerEvent(REMOVE_ALIAS_EVENT, alias);
    }

7 处理流程

每一个容器组件都有一个 Pipeline 对象,Pipeline 中维护了 Valve 链表,默认时每一个Pipeline存放了一个默认的BasicValue,
这里每一个Value表示一个处理点,当调用addValve 方法时会将添加Vaule添加到链表头部,Pipeline 中没有 invoke
方法,请求处理时Pipeline只需要获取链表中第一个Valve调用incoke去执行,执行完毕后当前Value会调用
getNext.invoke() 来触发下一个 Valve 调用

每一个容器在执行到最后一个默认BasicValue时,会负责调用下层容器的 Pipeline 里的第一个 Valve

image

对于StandardHost容器来说默认情况存在三个Value(阀门),分别是AccessLogValve(构建时读取server.xml时),StandardHostValve(构建实例化时),ErrorReportValve(启动时)。

7.1 ErrorReportValve

public class ErrorReportValve extends ValveBase {

    private boolean showReport = true;

    private boolean showServerInfo = true;

    Constructor
    public ErrorReportValve() {
        super(true);
    }

@Override
    public void invoke(Request request, Response response) throws IOException, ServletException {

        // 调用下一个Value处理
        getNext().invoke(request, response);

        /** 是否已提交此响应的输出 **/
        if (response.isCommitted()) {
            /** 未处理以提交设置响应错误**/
            if (response.setErrorReported()) {
                /** 清理response缓冲区 **/
                try {
                    response.flushBuffer();
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                }
                /** response 设置关闭连接发出错误**/
                response.getCoyoteResponse().action(ActionCode.CLOSE_NOW,
                        request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
            }
            return;
        }
        /** 获取异常 **/
        Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

        /** 如果是异步处理直接返回 **/
        if (request.isAsync() && !request.isAsyncCompleting()) {
            return;
        }

        /** 发生一次设置http响应编码 500**/
        if (throwable != null && !response.isError()) {
            response.reset();
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }

        /** 设置挂起的标志。**/
        response.setSuspended(false);

        /** 打印错误报告 **/
        try {
            report(request, response, throwable);
        } catch (Throwable tt) {
            ExceptionUtils.handleThrowable(tt);
        }
    }
    ...省略代码

7.2 AccessLogValve

记录访问日志,这里是一个通用组件,后续会由专题讲解

7.3 StandardHostValve

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容