Skip to content

上文已经知道Container的整体结构和设计,其中Engine其实就是Servlet Engine,负责处理request的顶层容器。@anarkh

  • Tomcat - Container容器之Engine:StandardEngine
  • 理解思路
  • Engine接口设计
  • Engine接口实现:StandardEngine
  • 接口中简单方法实现
  • child, parent
  • Lifecycle的模板方法
  • LogAccess
  • JMX相关

理解思路

  • 第一:抓住StandardEngine整体类依赖结构来理解

  • 第二:结合server.xml中Engine配置来理解

见下文具体阐述。

  • 第三:结合Engine Config官方配置文档

http://tomcat.apache.org/tomcat-9.0-doc/config/engine.html

Engine接口设计

这看Engine.java接口前,先要看下相关属性

  • 支持设置的属性列表
属性描述
backgroundProcessorDelay此值表示在此引擎及其子容器(包括所有Host和Context)上调用backgroundProcess方法之间的延迟(以秒为单位)。如果子容器的延迟值不为负(则表示它们正在使用自己的处理线程),则不会调用它们。将此值设置为正值将导致产生线程。等待指定的时间后,线程将在此引擎及其所有子容器上调用backgroundProcess方法。如果未指定,则此属性的默认值为10,表示10秒的延迟。
className使用的Java类名称。此类必须实现org.apache.catalina.Engine接口。如果未指定,将使用标准值(定义如下)。
defaultHost默认的主机名,它标识Host将处理针对主机名此服务器上的请求,但在此配置文件中没有配置。此名称必须与嵌套在name 其中的Host元素之一的属性匹配。
jvmRoute必须在负载平衡方案中使用的标识符才能启用会话亲缘关系。标识符(在参与集群的所有Tomcat服务器之间必须是唯一的)将附加到生成的会话标识符上,因此允许前端代理始终将特定会话转发到同一Tomcat实例。注意,jvmRoute也可以使用jvmRoutesystem属性设置 。属性中的jvmRoute set<Engine>将覆盖任何jvmRoute系统属性。
name此引擎的逻辑名称,用于日志和错误消息。在同一台Server中使用多个Service元素时 ,必须为每个引擎分配一个唯一的名称。
startStopThreads该引擎将用来并行启动子Host元素的线程数。特殊值0将导致使用该值 Runtime.getRuntime().availableProcessors()。Runtime.getRuntime().availableProcessors() + value除非小于1,否则将使用负值, 在这种情况下将使用1个线程。如果未指定,将使用默认值1。如果使用了1个线程,那么ExecutorService将使用当前线程,而不是使用。
  • Engine的接口设计

这里你会发现,如下接口中包含上述defaultHost和jvmRoute属性设置;同时还有Service,因为Engine的上层是service。

java
public interface Engine extends Container {

    
    public String getDefaultHost();


    
    public void setDefaultHost(String defaultHost);


    
    public String getJvmRoute();


    
    public void setJvmRoute(String jvmRouteId);


    
    public Service getService();


    
    public void setService(Service service);
}
  • 其它属性支持都包含在我们上文分析的ContainerBase中
java
protected int backgroundProcessorDelay = -1;

private int startStopThreads = 1;

...

Engine接口实现:StandardEngine

接口中简单方法实现

上述接口里面的defaultHost, JvmRoute, service 很简单

java
@Override
public String getDefaultHost() {
    return defaultHost;
}



@Override
public void setDefaultHost(String host) {

    String oldDefaultHost = this.defaultHost;
    if (host == null) {
        this.defaultHost = null;
    } else {
        this.defaultHost = host.toLowerCase(Locale.ENGLISH);
    }
    if (getState().isAvailable()) {
        service.getMapper().setDefaultHostName(host);
    }
    support.firePropertyChange("defaultHost", oldDefaultHost,
                                this.defaultHost);

}



@Override
public void setJvmRoute(String routeId) {
    jvmRouteId = routeId;
}



@Override
public String getJvmRoute() {
    return jvmRouteId;
}


@Override
public Service getService() {
    return this.service;
}



@Override
public void setService(Service service) {
    this.service = service;
}

child, parent

addChild重载方法,限制只能添加Host作为子容器;

setParent直接抛出异常,因为Engine接口中已经包含了setService方法作为它的上层,而Engine的上层没有容器的概念。

java
@Override
public void addChild(Container child) {

    if (!(child instanceof Host))
        throw new IllegalArgumentException
            (sm.getString("standardEngine.notHost"));
    super.addChild(child);

}



@Override
public void setParent(Container container) {

    throw new IllegalArgumentException
        (sm.getString("standardEngine.notParent"));

}

Lifecycle的模板方法

无非就是调用上文中我们介绍ContainerBase中的方法

java
@Override
protected void initInternal() throws LifecycleException {
    
    
    getRealm();
    super.initInternal();
}



@Override
protected synchronized void startInternal() throws LifecycleException {

    
    if (log.isInfoEnabled()) {
        log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
    }

    
    super.startInternal();
}

LogAccess

这里需要补充下之前没有介绍的日志访问 ,这里介绍下。

运行Web服务器时,正常生成的输出文件之一是访问日志 ,该访问日志以标准格式为服务器处理的每个请求生成一行信息。Catalina包括一个可选的Valve实现,该实现可以创建与Web服务器创建的标准格式相同的访问日志,也可以创建任意数量的自定义格式。

需要先看下xml配置; 您可以通过嵌套如下所示的Valve元素,要求Catalina为Engine, Host或Context处理的所有请求创建访问日志:

xml
<Engine name="Standalone" ...>
  ...
  <Valve className="org.apache.catalina.valves.AccessLogValve"
         prefix="catalina_access_log" suffix=".txt"
         pattern="common"/>
  ...
</Engine>

好了看下具体的实现,使用适配器模式获取AccessLog类型的Valve:

适配器模式看这里:结构型 - 适配器(Adapter)

java
@Override
public AccessLog getAccessLog() {

    if (accessLogScanComplete) {
        return accessLog;
    }

    AccessLogAdapter adapter = null;
    Valve valves[] = getPipeline().getValves();
    for (Valve valve : valves) {
        if (valve instanceof AccessLog) { 
            if (adapter == null) {
                adapter = new AccessLogAdapter((AccessLog) valve);
            } else {
                adapter.add((AccessLog) valve);
            }
        }
    }
    if (adapter != null) {
        accessLog = adapter;
    }
    accessLogScanComplete = true;
    return accessLog;
}

AccessLog(日志记录器)主要的作用就是记录日志,这个记录的方法就是logAccess()方法

java
@Override
public void logAccess(Request request, Response response, long time,
        boolean useDefault) {

    boolean logged = false;

     
    if (getAccessLog() != null) {
        accessLog.log(request, response, time);
        logged = true;
    }

    
    if (!logged && useDefault) {
        AccessLog newDefaultAccessLog = defaultAccessLog.get();
        if (newDefaultAccessLog == null) {
            
            
            Host host = (Host) findChild(getDefaultHost()); 
            Context context = null;
            if (host != null && host.getState().isAvailable()) {
                newDefaultAccessLog = host.getAccessLog();

                if (newDefaultAccessLog != null) {
                    if (defaultAccessLog.compareAndSet(null,
                            newDefaultAccessLog)) {
                        AccessLogListener l = new AccessLogListener(this,
                                host, null);
                        l.install(); 
                    }
                } else {
                    
                    context = (Context) host.findChild(""); 
                    if (context != null &&
                            context.getState().isAvailable()) {
                        newDefaultAccessLog = context.getAccessLog();
                        if (newDefaultAccessLog != null) {
                            if (defaultAccessLog.compareAndSet(null,
                                    newDefaultAccessLog)) {
                                AccessLogListener l = new AccessLogListener(
                                        this, null, context);
                                l.install();
                            }
                        }
                    }
                }
            }

            if (newDefaultAccessLog == null) { 
                newDefaultAccessLog = new NoopAccessLog(); 
                if (defaultAccessLog.compareAndSet(null,
                        newDefaultAccessLog)) {
                    AccessLogListener l = new AccessLogListener(this, host,
                            context);
                    l.install();
                }
            }
        }

        
        newDefaultAccessLog.log(request, response, time);
    }
}

其中涉及的相关内部类如下:

java
protected static final class NoopAccessLog implements AccessLog {

    @Override
    public void log(Request request, Response response, long time) {
        
    }

    @Override
    public void setRequestAttributesEnabled(
            boolean requestAttributesEnabled) {
        

    }

    @Override
    public boolean getRequestAttributesEnabled() {
        
        return false;
    }
}

protected static final class AccessLogListener
        implements PropertyChangeListener, LifecycleListener,
        ContainerListener {

    private final StandardEngine engine;
    private final Host host;
    private final Context context;
    private volatile boolean disabled = false;

    public AccessLogListener(StandardEngine engine, Host host,
            Context context) {
        this.engine = engine;
        this.host = host;
        this.context = context;
    }

    public void install() {
        engine.addPropertyChangeListener(this);
        if (host != null) { 
            host.addContainerListener(this);
            host.addLifecycleListener(this);
        }
        if (context != null) {
            context.addLifecycleListener(this);
        }
    }

    private void uninstall() {
        disabled = true;
        if (context != null) {
            context.removeLifecycleListener(this);
        }
        if (host != null) {
            host.removeLifecycleListener(this);
            host.removeContainerListener(this);
        }
        engine.removePropertyChangeListener(this);
    }

    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        if (disabled) return;

        String type = event.getType();
        if (Lifecycle.AFTER_START_EVENT.equals(type) ||
                Lifecycle.BEFORE_STOP_EVENT.equals(type) ||
                Lifecycle.BEFORE_DESTROY_EVENT.equals(type)) {
            
            
            
            engine.defaultAccessLog.set(null);
            uninstall();
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (disabled) return;
        if ("defaultHost".equals(evt.getPropertyName())) {
            
            
            engine.defaultAccessLog.set(null);
            uninstall();
        }
    }

    @Override
    public void containerEvent(ContainerEvent event) {
        
        if (disabled) return;
        if (Container.ADD_CHILD_EVENT.equals(event.getType())) {
            Context context = (Context) event.getData();
            if (context.getPath().isEmpty()) {
                
                
                engine.defaultAccessLog.set(null);
                uninstall();
            }
        }
    }
}

JMX相关

之前已经有过相关介绍,这里不再介绍相关方法,只列出相关方法:

java
@Override
protected String getObjectNameKeyProperties() {
    return "type=Engine";
}


@Override
protected String getDomainInternal() {
    return getName();
}