探秘Tomcat——连接篇

时间:2022-08-12 17:39:16

  前两篇我们分别粗线条和细粒度的讲解了tomcat的服务是如何启动以及连接器Connector和容器Container又分别是如何被启动的.

  本篇我们主要侧重tomcat中server、service以及connector和container之间是如何相互关联起来的。在此之前,我们分别看下这个类中的一些主要方法,尤其是用于相互关联绑定的方法。

  Server:(Server代表了整个Catalina容器,一个server可以包含一个或多个Services)

   getInfo                    //获取server的版本
getGlobalNamingResources
setGlobalNamingResources
getPort //返回监听关闭server的端口
setPort
getShutdown //返回关闭server的命令字符串比如"SHUTDOWN"
setShutdown
addService //在该server上添加一个service
await //一直监听,直到出现shutdown指令
findService //返回指定名称的service
findServices //返回所有在这个server上的services集合
removeService
initialize

  Service:(Service是一组包含了一个Container和一个或多个Connector的集合)

   getContainer            //返回容器,该容器用于处理Service上的Connectors发送过来请求
setContainer
getInfo //返回Service的版本信息
getName //返回该Service的名字
setName
getServer //返回与此Service关联的Server,这与Server中的addService遥相呼应
setServer //绑定一个Server
addConnector //添加一个Connector
findConnectors //返回该Service上的所有Connector
removeConnector //删除指定的Connector,同时也以为该Connector与Container也解除联系
initialize
addExecutor //添加一个执行器
findExecutors
getExecutor
removeExecutor

  Connector:(前面已经说过,一个Service中可以包含多个Container,但是只会有一个Connector,而Container有多层实现关系,并且有自己的实现规范,所以定义成了接口,而这里的Connector就是一个类而非接口)

 Connector
Connector //构造函数,其中有设置Connector要用到的协议
getProperty //根据属性名,返回属性值
setProperty
getAttribute //也是根据属性名,返回属性值,但是getProperty返回的是String类型,这里是Object对象
setAttribute
removeProperty
getService //返回与之绑定的Service
setService //绑定Service
getAllowTrace
setAllowTrace //设置allowTrace,用于跟踪http的信息
isAvailable //判断是否可用于处理request,里面判断的标记是started,这意味着只有Connector启动了才能用于处理request
getBufferSize
setBufferSize
getContainer //返回当前Connector移交request的接收Container对象
setContainer
getEmptySessionPath
setEmptySessionPath
getEnableLookups
setEnableLookups
getInfo //返回Connector的版本信息
getMapper
getMaxHeaderCount //返回Container允许的最大headers个数
setMaxHeaderCount
getMaxParameterCount //返回GET和POST方法的最大个数
setMaxParameterCount
...
getPort //返回监听request的端口
setPort
getProtocol //返回使用到的protocol handler,有Http/1.1和AJP/1.3
setProtocol
getProtocolHandlerClassName
setProtocolHandlerClassName
getProtocolHandler
getProxyName //设置代理的名字
setProxyName
getProxyPort
setProxyPort
getRedirectPort //重定向端口,如果
setRedirectPort
getScheme
setScheme
getSecure
setSecure
getURIEncoding //返回URI编码
setURIEncoding
getUseBodyEncodingForURI
setUseBodyEncodingForURI
getXpoweredBy
setXpoweredBy
setUseIPVHosts
getUseIPVHosts
getExecutorName
createRequest //创建或指派并返回Request对象,这里的Request有和Container关联
createResponse
addLifecycleListener
findLifecycleListeners
removeLifecycleListener
createObjectName
initialize //初始化Connector对象
pause
resume
start
stop
...
init
destroy
toString

  Container:(Container可以执行来自客户端的Request请求,并返回相应的Response)

getInfo
getLoader //返回与此Container相关的Loader对象,如果没有Loader,则返回与其父Container关联的Loader
setLoader
getLogger //返回Logger对象,用于打印log,同理如果当前没有Logger对象,则寻找父级Logger
getManager //返回Manager对象
setManager
getMappingObject //返回JMX对象名字
getObjectName
getPipeline //返回与此Container相关的用于管理Valves的Pipeline
getCluster
setCluster
getBackgroundProcessorDelay
setBackgroundProcessorDelay
getName
setName
getParent //返回父级Container
setParent
getParentClassLoader //返回父级类加载器
setParentClassLoader
getRealm
setRealm
getResources
setResources
backgroundProcess
addChild //添加一个子容器,在添加之前,需要在子容器中先调用setParent方法
addContainerListener //添加事件监听器
addPropertyChangeListener //添加属性值变化监听器
findChild
findChildren
findContainerListeners
invoke //执行具体的Request,并得到具体的Response对象
removeChild
removeContainerListener
removePropertyChangeListener
logAccess

1.连接原理举例

  首先我们在Catalina类的load方法中调用了方法createStartDigester,该方法在之前几篇有介绍过,主要是对于加载的server.xml文件中定义各个组件之间的关系。

  比如方法中的片段:

digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
  • addObjectCreate就是添加一个模式,当解析server.xml遇到Server的时候,就根据Server的className实例化一个Server对象,而默认实例化的类就是org.apache.catalina.core.StandardServer;
  • addSetProperties用于设置Server的一些属性,具体属性在server.xml中有定义;
  • addSetNext用于调用Server类的setServer方法,把当前Server添加进去。

  再比如这几行代码:

digester.addObjectCreate("Server/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");

  对应在server.xml中就是这几行

 <!--APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
<Listener className="org.apache.catalina.core.JasperListener" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  • 同理addObjectCreate说的是在Server下的Listeners,根据className创建listener对象;
  • addSetProperties用于设置Server的一些属性,具体属性在server.xml中有定义;
  • addSetNext用于调用Server类的addLifecycleLisntener方法,把server.xml中定义的5个监听器都实例化并添加到server上。

  还有关于Server和Service之间关系的代码

digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");

  通过这些代码我们很容易理解Server和Service之间的关联关系(后面会详细介绍)

  除了Server和Service之间的从属关系,我们还可以看到Service和Connector之间的关系

digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");

  同理这里也是在Service下调用addConnector添加Connector(后面会详细介绍)

2.Connector和Container以及Connector和Service何时连接?

  我们从Catalina的load方法开始,当执行到load中的digester.parse(inputSource)时,即跳转到Digester类的parse方法中,之后开始解析server.xml中依次遇到的各个元素。

  当遇到server元素的时候,在代码中方法的执行顺序为Catalina.load->Digester.parse->Digester.startElement

  startElement方法如下:

 public void startElement(String namespaceURI, String localName,
String qName, Attributes list)
throws SAXException {
boolean debug = log.isDebugEnabled(); if (saxLog.isDebugEnabled()) {
saxLog.debug("startElement(" + namespaceURI + "," + localName + "," +
qName + ")");
} // Parse system properties
list = updateAttributes(list); // Save the body text accumulated for our surrounding element
bodyTexts.push(bodyText);
if (debug) {
log.debug(" Pushing body text '" + bodyText.toString() + "'");
}
bodyText = new StringBuffer(); // the actual element name is either in localName or qName, depending
// on whether the parser is namespace aware
String name = localName;
if ((name == null) || (name.length() < 1)) {
name = qName;
} // Compute the current matching rule
StringBuffer sb = new StringBuffer(match);
if (match.length() > 0) {
sb.append('/');
}
sb.append(name);
match = sb.toString();
if (debug) {
log.debug(" New match='" + match + "'");
} // Fire "begin" events for all relevant rules
List rules = getRules().match(namespaceURI, match);
matches.push(rules);
if ((rules != null) && (rules.size() > 0)) {
for (int i = 0; i < rules.size(); i++) {
try {
Rule rule = (Rule) rules.get(i);
if (debug) {
log.debug(" Fire begin() for " + rule);
}
rule.begin(namespaceURI, name, list);
} catch (Exception e) {
log.error("Begin event threw exception", e);
throw createSAXException(e);
} catch (Error e) {
log.error("Begin event threw error", e);
throw e;
}
}
} else {
if (debug) {
log.debug(" No rules found matching '" + match + "'.");
}
} }

Digester.startElement

当执行到rule.begin(namespaceURI, name, list)这行的时候,通过调试信息可以看到该rule的className为org.apache.catalina.core.StandardServer,所以最终会进入StandardServer的构造函数中。

探秘Tomcat——连接篇

  另外,当解析server.xml到5个listener的时候,就会调用StandardServer的addLifecycleListener分别将这5个监听器实例化并添加到server上。

  继续解析,当解析到

igester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");

  的时候就会跳转到StandardService的addConnector方法中

 public void addConnector(Connector connector) {

     synchronized (connectors) {
connector.setContainer(this.container);
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results; if (initialized) {
try {
connector.initialize();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.initFailed",
connector), e);
}
} if (started && (connector instanceof Lifecycle)) {
try {
((Lifecycle) connector).start();
} catch (LifecycleException e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
} // Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
} }

  首先解析到的是server.xml中的这个connector

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

  下面的两行代码交代了一个Connector是如何关联上Container和Service的:

  • connector.setContainer(this.container):说明了connector和container是如何关联的,调用connector对象的setContainer方法,而传进的值为this.Container,也就是当前StandardService的Container对象,这样就完成了Connector和Container之间的连接
  • connector.setService(this):对外这里绑定了当前的StandardService作为其从属的service。

3.Service和Container是何时连接的?

  继续解析直到Catalina.createStartDigester定义的

digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));

  这时候会调用StandardService的setContainer方法:

 public void setContainer(Container container) {

     Container oldContainer = this.container;
if ((oldContainer != null) && (oldContainer instanceof Engine))
((Engine) oldContainer).setService(null);
this.container = container;
if ((this.container != null) && (this.container instanceof Engine))
((Engine) this.container).setService(this);
if (started && (this.container != null) &&
(this.container instanceof Lifecycle)) {
try {
((Lifecycle) this.container).start();
} catch (LifecycleException e) {
;
}
}
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++)
connectors[i].setContainer(this.container);
}
if (started && (oldContainer != null) &&
(oldContainer instanceof Lifecycle)) {
try {
((Lifecycle) oldContainer).stop();
} catch (LifecycleException e) {
;
}
} // Report this property change to interested listeners
support.firePropertyChange("container", oldContainer, this.container); }

  当执行到((Engine) this.container).setService(this);这里会跳转到StandardEngine的setService交代了container是如何绑定StandardService的。

  并且在代码

 synchronized (connectors) {
for (int i = 0; i < connectors.length; i++)
connectors[i].setContainer(this.container);
}

  我们可以看到通过遍历所有的connector,将其与container绑定。

4.Server和Service又是何时连接的?

  继续解析直到Catalina.createStartDigerster中的

digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");

  会调用StandardServer的addService方法

 public void addService(Service service) {

     service.setServer(this);

     synchronized (services) {
Service results[] = new Service[services.length + 1];
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results; if (initialized) {
try {
service.initialize();
} catch (LifecycleException e) {
log.error(e);
}
} if (started && (service instanceof Lifecycle)) {
try {
((Lifecycle) service).start();
} catch (LifecycleException e) {
;
}
} // Report this property change to interested listeners
support.firePropertyChange("service", null, service);
} }

  service.setServer(this):该行会调用StandardService中的setServer为service绑定当前的StandardServer对象

5.小结

  当server.xml中的rule解析完毕后,我们起码明白了:

  • Server和Service是如何关联的;
  • Service和Connector是如何关联的;
  • Service和Container是如何关联的;
  • Connector和Container是如何关联的

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

探秘Tomcat——连接篇

友情赞助

如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。

    1. 支付宝                          2. 微信

探秘Tomcat——连接篇                      探秘Tomcat——连接篇