Tomcat Container容器之Engine:StandardEngine 理解思路第一抓住StandardEngine整体类依赖结构来理解​编辑第二结合server.xml中Engine配置来理解见下文具体阐述。第三结合EnEngine接口设计这看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 setEngine将覆盖任何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/** * An bEngine/b is a Container that represents the entire Catalina servlet * engine. It is useful in the following types of scenarios: * ul * liYou wish to use Interceptors that see every single request processed * by the entire engine. * liYou wish to run Catalina in with a standalone HTTP connector, but still * want support for multiple virtual hosts. * /ul * In general, you would not use an Engine when deploying Catalina connected * to a web server (such as Apache), because the Connector will have * utilized the web servers facilities to determine which Context (or * perhaps even which Wrapper) should be utilized to process this request. * p * The child containers attached to an Engine are generally implementations * of Host (representing a virtual host) or Context (representing individual * an individual servlet context), depending upon the Engine implementation. * p * If used, an Engine is always the top level Container in a Catalina * hierarchy. Therefore, the implementations codesetParent()/code method * should throw codeIllegalArgumentException/code. * * author Craig R. McClanahan */ public interface Engine extends Container { /** * return the default host name for this Engine. */ public String getDefaultHost(); /** * Set the default hostname for this Engine. * * param defaultHost The new default host */ public void setDefaultHost(String defaultHost); /** * return the JvmRouteId for this engine. */ public String getJvmRoute(); /** * Set the JvmRouteId for this engine. * * param jvmRouteId the (new) JVM Route ID. Each Engine within a cluster * must have a unique JVM Route ID. */ public void setJvmRoute(String jvmRouteId); /** * return the codeService/code with which we are associated (if any). */ public Service getService(); /** * Set the codeService/code with which we are associated (if any). * * param service The service that owns this Engine */ public void setService(Service service); }其它属性支持都包含在我们上文分析的ContainerBase中java/** * The processor delay for this component. */ protected int backgroundProcessorDelay -1; /** * The number of threads available to process start and stop events for any * children associated with this container. */ private int startStopThreads 1; ...Engine接口实现StandardEngine接口中简单方法实现上述接口里面的defaultHost, JvmRoute, service 很简单java/** * Return the default host. */ Override public String getDefaultHost() { return defaultHost; } /** * Set the default host. * * param host The new default host */ 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); } /** * Set the cluster-wide unique identifier for this Engine. * This value is only useful in a load-balancing scenario. * p * This property should not be changed once it is set. */ Override public void setJvmRoute(String routeId) { jvmRouteId routeId; } /** * Retrieve the cluster-wide unique identifier for this Engine. * This value is only useful in a load-balancing scenario. */ Override public String getJvmRoute() { return jvmRouteId; } /** * Return the codeService/code with which we are associated (if any). */ Override public Service getService() { return this.service; } /** * Set the codeService/code with which we are associated (if any). * * param service The service that owns this Engine */ Override public void setService(Service service) { this.service service; }child, parentaddChild重载方法限制只能添加Host作为子容器setParent直接抛出异常因为Engine接口中已经包含了setService方法作为它的上层而Engine的上层没有容器的概念。java/** * Add a child Container, only if the proposed child is an implementation * of Host. * * param child Child container to be added */ Override public void addChild(Container child) { if (!(child instanceof Host)) throw new IllegalArgumentException (sm.getString(standardEngine.notHost)); super.addChild(child); } /** * Disallow any attempt to set a parent for this Container, since an * Engine is supposed to be at the top of the Container hierarchy. * * param container Proposed parent Container */ Override public void setParent(Container container) { throw new IllegalArgumentException (sm.getString(standardEngine.notParent)); }Lifecycle的模板方法无非就是调用上文中我们介绍ContainerBase中的方法javaOverride protected void initInternal() throws LifecycleException { // Ensure that a Realm is present before any attempt is made to start // one. This will create the default NullRealm if necessary. getRealm(); super.initInternal(); } /** * Start this component and implement the requirements * of {link org.apache.catalina.util.LifecycleBase#startInternal()}. * * exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ Override protected synchronized void startInternal() throws LifecycleException { // Log our server identification information if (log.isInfoEnabled()) { log.info(sm.getString(standardEngine.start, ServerInfo.getServerInfo())); } // Standard container startup super.startInternal(); }LogAccess这里需要补充下之前没有介绍的日志访问这里介绍下。运行Web服务器时正常生成的输出文件之一是访问日志该访问日志以标准格式为服务器处理的每个请求生成一行信息。Catalina包括一个可选的Valve实现该实现可以创建与Web服务器创建的标准格式相同的访问日志也可以创建任意数量的自定义格式。需要先看下xml配置; 您可以通过嵌套如下所示的Valve元素要求Catalina为Engine Host或Context处理的所有请求创建访问日志xmlEngine nameStandalone ... ... Valve classNameorg.apache.catalina.valves.AccessLogValve prefixcatalina_access_log suffix.txt patterncommon/ ... /Engine好了看下具体的实现使用适配器模式获取AccessLog类型的Valve适配器模式看这里结构型 - 适配器(Adapter)javaOverride 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 the default implementation. If no access log is defined for the * Engine, look for one in the Engines default host and then the default * hosts ROOT context. If still none is found, return the default NoOp * access log. */ Override public void logAccess(Request request, Response response, long time, boolean useDefault) { boolean logged false; // 如果有accessLog则记录日志 if (getAccessLog() ! null) { accessLog.log(request, response, time); logged true; } // 没找到且使用useDefault表示从下层容器中获取accessLog if (!logged useDefault) { AccessLog newDefaultAccessLog defaultAccessLog.get(); if (newDefaultAccessLog null) { // If we reached this point, this Engine cant have an AccessLog // Look in the defaultHost Host host (Host) findChild(getDefaultHost()); // 如果没有默认的accessLog则获取默认Host的accessLog 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(); // 注册AccessLog监听器至当前Engine } } else { // Try the ROOT context of default host context (Context) host.findChild(); // 如果仍然没有找到则获取默认host的ROOT Context的accessLog 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); } }其中涉及的相关内部类如下javaprotected static final class NoopAccessLog implements AccessLog { Override public void log(Request request, Response response, long time) { // NOOP }