スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

コードリーディング Tomcatのソースを読んでみよう 起動編

今回のエントリでは前回に引き続き、Tomcatのコードリーディングを行います。
今回はTomcatの起動部分の処理を見る「起動編」となります。

前回:コードリーディング Tomcatのソースを読んでみよう 準備編

エントリーポイントはどこにある?

まずは起点であるエントリーポイント(mainメソッドだと思ってください)を探しましょう。
前回のエントリで地味にBootstrapクラスであると明かしてしまっていたり、
Tomcatの公式ページに起動シーケンスの図が載っていたりするのですが、
敢えて探してみます。

どうやって探すかですが、それは簡単でTomcatの起動に使っている
バッチやらシェルスクリプトの中を見ればいいのです。
例えば Tomcatのフォルダ/bin/startup.bat の中を見ると
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"
中略
call "%EXECUTABLE%" start %CMD_LINE_ARGS%
のように書いてあって、catalina.bat を呼んでいることがわかります。
そして catalina.bat の中には
中略
set MAINCLASS=org.apache.catalina.startup.Bootstrap
中略
%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS%
-Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%"
-classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%"
-Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%"
%MAINCLASS% %CMD_LINE_ARGS% %ACTION%
と書いてあり、変数MAINCLASSにセットしている部分からメインクラスが
Bootstrapであることがわかります。

起動処理概要

Bootstrapクラスのコードを見る前に、起動処理のおおざっぱなシーケンスを以下に示しておきます。
  1. クラスローダーの初期化
  2. server.xmlの読み込み
  3. server.xmlの情報をもとにしたコンポーネントの組み立て
  4. サーバーの初期化
  5. サーバーの開始

1.クラスローダーの初期化とセット

さあBootstrapクラスのコードを見てみましょう。

public final class Bootstrap {

private Object catalinaDaemon = null;
protected ClassLoader commonLoader = null;
protected ClassLoader catalinaLoader = null;
protected ClassLoader sharedLoader = null;

public static void main(String args[]) {
if (daemon == null) {
Bootstrap bootstrap = new Bootstrap();
// 初期化
bootstrap.init();
daemon = bootstrap;
}
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
// 説明に不要な部分は略
if (command.equals("startd")) {
} else if (command.equals("stopd")) {
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
} else if (command.equals("configtest")) {
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
}

public void init() throws Exception {

// ...中略
initClassLoaders();// クラスローダーの初期化

// ...中略
// Catalinaクラスのインスタンスを生成
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();

// CatalinaクラスのsetParentClassLoaderメソッドを呼び出す
String methodName = "setParentClassLoader";
...paramTypesとparamValuesのセット部分は略
Class<?> paramTypes[] = new Class[1];
Object paramValues[] = new Object[1];
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);

catalinaDaemon = startupInstance;// フィールドにセット

}

private void initClassLoaders() {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
}

private void load(String[] arguments) throws Exception {
// Catalinaクラスのloadメソッドを呼び出す
String methodName = "load";
Object param[];
Class<?> paramTypes[];
// ...paramTypesのセット部分は略
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
method.invoke(catalinaDaemon, param);
}

public void start() throws Exception {
// Catalinaクラスのstartメソッドを呼び出す
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}

}
まずフィールドを見てみましょう。
クラスローダーが3つ定義されています。このクラスローダーたちがinitClassLoadersで初期化されます。

■このクラスローダーは何ものか
このクラスローダたちは、Tomcatのフォルダ/conf/catalina.propertiesに記述されている場所から
クラスをロードするクラスローダーです。(URLClassLoaderのサブクラス)

catalina.propertiesの中には次のような設定が記述されています。(これはTomcat7)

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=

Tomcat5のcatalina.propertiesはこんなでした(7と比較すると7で大分整理されたことがわかります)

common.loader=${catalina.home}/common/classes,${catalina.home}/common/i18n/*.jar,${catalina.home}/common/endorsed/*.jar,${catalina.home}/common/lib/*.jar
server.loader=${catalina.home}/server/classes,${catalina.home}/server/lib/*.jar
shared.loader=${catalina.base}/shared/classes,${catalina.base}/shared/lib/*.jar
クラスローダーフィールドとプロパティの対応関係などを下表に示します。

クラスローダー プロパティのキー プロパティに値がない場合 親クラスローダー どう使われるか
commonLoader  common.loader Bootstrapクラスの
クラスローダーが
commonローダーになる。
システムクラスローダーが親となる catalinaLoaderやsharedLoaderの親クラスローダーとして使われる。もしくはcatalinaLoaderやsharedLoaderそのものになる。
catalinaLoader  server.loader commonLoaderがcatalinaLoaderとして使われる プロパティに値があったらcommonLoaderが親になる 現在実行中Thread のコンテキスト クラスローダーに設定される。
sharedLoader  shared.loader commonLoaderがsharedLoaderとして使われる 同上 CatalinaクラスのsetParentClassLoaderメソッドでセットされる。
結果としてWebアプリのクラスローダーの親となる。


余談ですが、TomcatはWebアプリごとにWebappClassLoaderというクラスローダーを持っており
Webアプリがクラスのロードを必要とした時、以下の順にクラスロードを行っています。
  1. ローカルのキャッシュ※からロード
    ※/WEB-INF/lib/ や/WEB-INF/lib/classes からロードした時のキャッシュ
  2. ローカル以外のキャッシュ※からロード
    ※/WEB-INF/lib/ や /WEB-INF/lib/classes 以外からロードした時のキャッシュ
  3. システムクラスローダーを使ってロード
    これはJ2SEのクラスのオーバーライドを許さないようにするためです
    (Webアプリの中でJava標準ライブラリのクラスと同名のクラスが定義されている場合を考慮している)
  4. 親クラスローダーに処理を委譲する設定※がある場合、親クラスローダーを使ってロード
    ※server.xmlのContext要素の属性で指定 => <Context delegate="true" ...>
  5. /WEB-INF/lib/ や /WEB-INF/lib/classes からクラスを探してロード
  6. 親のクラスローダーを使ってロード
クラスローダーの初期化処理の後は以下処理を行ってBootstrapでの処理は終わっています。
  1. リフレクションでCatalinaクラスのインスタンスを生成する
  2. リフレクションでCatalinaクラスのloadメソッドを呼び出す
  3. リフレクションでCatalinaクラスのstartメソッドを呼び出す

2.server.xmlの読み込みと主要コンポーネントの組み立て

次はBootstrapから生成されたCatalinaの処理です。
public class Catalina {

protected String configFile = "conf/server.xml";

public void load(String args[]) {
if (arguments(args)) {
load();
}
}

public void load() {
// ...中略
// Digesterの生成
Digester digester = createStartDigester();

// inputSourceとinputStreamの生成部分は略(server.xmlを読み込んでいる)
InputSource inputSource = null;
InputStream inputStream = null;

// Digesterを使ってTomcatの主要部品を組み立てる
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);

getServer().setCatalina(this);

// ...中略
// サーバーの初期化
getServer().init();
}

public void start() {
// サーバーの処理開始
getServer().start();
...中略
}

}
Bootstrapから呼ばれたloadの中で、Digesterを生成して、parseメソッドで何かを解析させていますね。

ここがTomcat起動時のキモと言っても過言ではありません。


■Digesterとは何か
Digesterは、XMLをJavaBeansへマッピングすることができるクラスです。
マッピングはDigesterに設定したルールに従って行われます。
(かつてTomcatがjakartaプロジェクトで開発されていた時はcommons-digesterという
ライブラリ※を使っていましたが、Apacheプロジェクトに移ってからは、自前で用意しているようです。
※元を辿るとStrutsでstruts-config.xmlを読むのに使われていたクラス群です。
他にも使えそうなのでcommonsプロジェクトとして独立して今に至ります。)


TomcatはこのDigesterにserver.xmlを読み込ませて主要コンポーネントの組み立てを行います。
server.xml はこんな感じでしたね。
組み立てられるコンポーネント(オブジェクト)の構造もこのXMLの構造と同じものになります。
(Serverコンポーネントの子にServiceコンポーネントがいる、というような)

<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="/TestApp" reloadable="true"
docBase="C:\eclipse\workspace\TestApp"/>
</Host>
</Engine>
</Service>
</Server>

次はDigesterを生成してルールを設定しているところを見てみます。
後述しますが、ここのコードとserver.xmlを見れば、
どのクラスが主要コンポーネントとして使われているのかがわかるようになります。

protected Digester createStartDigester() {
Digester digester = new Digester();
// ...中略
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer", "className");
digester.addSetProperties("Server");
digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");
// ...中略
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService", "className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService", "org.apache.catalina.Service");
// ...中略
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");
// ...中略
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// ...中略
return (digester);
}

続いて軽くDigesterのルールの説明をします。


/**
* 指定したpatternにマッチした場合に、オブジェクトの生成を行うルールです。
* attributeで指定した属性の値を元にクラスを生成します。
* 指定した属性が未定義だったらclassNameを生成します。
* 例:("Server", "org.apache.catalina.core.StandardServer", "className")
* この例の場合、Server要素にclassName属性があったらその値のクラスを生成し、
* なかったらStandardServerを生成します。
*/
public void addObjectCreate(String pattern,
String className, String attribute);

/**
* 指定したpatternにマッチした場合に、プロパティの設定を行うルールです。
* 例:addSetProperties("Server");
* Server要素が<Server port="8005" shutdown="SHUTDOWN">のようになっていたら、
* portフィールドに8005が、shutdownフィールドにSHUTDOWNが設定されることになります。
*/
public void addSetProperties(String pattern);

/**
* 指定したpatternにマッチした場合に、paramTypeで指定した型のオブジェクトを
* methodNameで指定した設定メソッドを使って、親にセットするルールです。
* 例:addSetNext("Server/Service","addService", "org.apache.catalina.Service");
* この例の場合、Serviceの親要素であるServerのオブジェクトに、
* addServiceメソッドを使ってServiceオブジェクトを設定することなります。
* (ServiceインタフェースにはaddServiceメソッドが定義されています)
*/
public void addSetNext(String pattern, String methodName, String paramType)

簡単な説明でしたが、このルールを元にオブジェクトの生成やプロパティのセット、
及びオブジェクト間の階層構造の構築が行えることがわかって頂けたかと思います。

server.xmlの要素を見て、生成クラス名が指定されていなかったら、
addObjectCreateメソッドに指定しているクラスが生成されていると考えればOKです。
これで簡単に使われているクラスを探すことができます。

例えば<Server port="8005" shutdown="SHUTDOWN">はクラスの指定がないので、
ルールに定義されたStandardServerが使われているとか。
<Valve className="org.apache.catalina.valves.AccessLogValve" ...略>は
AccessLogValveが使われているとか。

表:要素と、それに対応するデフォルト生成クラス(一部のみ)
要素 生成クラス 親への設定可能個数 備考
Server StandardServer 1つ XML上親要素はありませんが、ServerはCatalinaに設定されます。
(Digester#parseの前にCatalinaがセットされて
トップのオブジェクトになっているため)
// コードを抜粋
digester.push(this);
digester.parse(inputSource);
Service StandardService 複数 複数個設定可能だが、ほとんど1つで運用されていると思われる
Connector Connector 複数
Engine StandardEngine 1つ
Host StandardHost 複数
Context StandardContext 複数

Tomcatのデフォルト設定では主要素の生成クラスは何も指定されていません。
ですので、あとはデフォルト生成クラスのコンストラクタの中の処理を見れば
主要コンポーネント組み立て部分の処理はすべて理解できます。

3.サーバーの初期化

次は Catalina#load() の中の、
getServer().init();
の部分です。

CatalinaにセットされているServerはStandardServerなので、そのinit()を見てみましょう。

public final class StandardServer extends LifecycleMBeanBase
implements Server {
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// ...中略
// サービスの初期化
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
}

/** LifecycleMBeanBaseの親クラス */
public abstract class LifecycleBase implements Lifecycle {
@Override
public final synchronized void init() throws LifecycleException {
// リスナに初期化中であることを通知
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 抽象メソッドの呼び出し
initInternal();
// リスナに初期化終了を通知
setStateInternal(LifecycleState.INITIALIZED, null, false);
}
protected abstract void initInternal() throws LifecycleException;
}
StandardServer自身にはinitメソッドはなく、親(の親)クラスのinitメソッドから、
前処理と後処理に挟まれた内部的初期化メソッドが呼ばれていることがわかります。
これはテンプレートメソッドパターンですね。
で、やってることは状態の通知と、サービスの初期化メソッドの呼び出しです。
ということで次は StandardService のinit を見てみましょう。
public class StandardService extends LifecycleMBeanBase implements Service {
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// コンテナの初期化
container.init();
// ...中略
// Connectorの初期化
for (Connector connector : connectors) {
connector.init();
}
}
}
init経由でinitInternalが呼ばれているのはこいつも同じなので、そこは割愛しました。
中でやってるのはコンテナの初期化とConnectorの初期化です。
ConnectorはXMLに定義されていたから何かわかるとして、コンテナとは何でしょう。
答えはDigesterのルールをセットしているコードを丹念に見ていくと見つかります。

public class Catalina {
protected Digester createStartDigester() {
// ...中略
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
// ...中略
}
}

public class EngineRuleSet extends RuleSetBase {
protected String prefix = null;
public EngineRuleSet(String prefix) {
this.prefix = prefix;
}
@Override
public void addRuleInstances(Digester digester) {
// ...中略
digester.addSetNext(prefix + "Engine",
"setContainer", "org.apache.catalina.Container");
// ...中略
}
}
親の要素であるServiceのsetContainerメソッドを呼び出して、自身を設定しているわけですね。
ちなみにEngineはContainerを継承するインタフェースです。
これでServiceが持っているコンテナとはStandardEngineのことだとわかりました。
じゃあStandardEngineのinitは何をやっているのかというと、説明するべきことはやっていません。
(StandardEngineはinitもinitInternalも実装していない)

なので次、Connectorのinitに行ってしまいましょう。
public class Connector extends LifecycleMBeanBase {

protected ProtocolHandler protocolHandler = null;
protected Adapter adapter = null;

@Override
protected void initInternal() throws LifecycleException {

// アダプタの初期化
adapter = new CoyoteAdapter(this);
// プロトコルハンドラにアダプタを設定
protocolHandler.setAdapter(adapter);

// ...中略
// プロトコルハンドラの初期化
protocolHandler.init();

// ...中略
}

}
init経由でinitInternalが呼び出されているのはこれまでと一緒です。
今度はアダプタやらプロトコルハンドラなるクラスが登場しました。
簡単に説明しましょう。
  • アダプタ
    Tomcatにリクエストがあった時に、Serviceの処理を呼び出すクラスです。
  • プロトコルハンドラ
    HTTP/1.1やAJP/1.3などのプロトコルごとにリクエストをハンドリングするクラスです。
    プロトコル分存在します。Connectorのコンストラクタの中で生成されます。
    DigesterのConnectorCreateRuleを見ると、server.xmlのConnector要素の
    protocol属性値をコンストラクタに渡して生成していることがわかります。
    public class ConnectorCreateRule extends Rule {
    @Override
    public void begin(String namespace, String name, Attributes attributes)
    throws Exception {
    Service svc = (Service)digester.peek();
    // protocol属性から値を取得してコンストラクタに渡す
    Connector con = new Connector(attributes.getValue("protocol"));
    digester.push(con);
    }
    }

    public class Connector extends LifecycleMBeanBase {
    public Connector(String protocol) {
    // プロトコルを設定
    setProtocol(protocol);
    Class<?> clazz = Class.forName(protocolHandlerClassName);
    // プロトコルハンドラを生成
    this.protocolHandler = (ProtocolHandler) clazz.newInstance();
    }
    public void setProtocol(String protocol) {
    // プロトコルに対応したクラスを設定する
    if (AprLifecycleListener.isAprAvailable()) {
    if ("HTTP/1.1".equals(protocol)) {
    setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
    } else if ("AJP/1.3".equals(protocol)) {
    setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
    } else if (protocol != null) {
    setProtocolHandlerClassName(protocol);
    } else {
    setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
    }
    } else {
    if ("HTTP/1.1".equals(protocol)) {
    setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");
    } else if ("AJP/1.3".equals(protocol)) {
    setProtocolHandlerClassName("org.apache.coyote.ajp.AjpProtocol");
    } else if (protocol != null) {
    setProtocolHandlerClassName(protocol);
    }
    }
    }
    }

Serverのinitから続く処理がようやく 終わりました。

4.サーバーの開始

今度はサーバーを開始する処理です。これが終了するとリクエストを受け取れるようになります。
Catalina#start() の中の getServer().start(); で StandardServerの start() が呼ばれるところから見てみましょう。
public final class StandardServer extends LifecycleMBeanBase implements Server {
@Override
protected void startInternal() throws LifecycleException {
// ...中略
// serviceのstartを呼び出す
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
}
またまたテンプレートメソッドです。startInternalは親のstartから呼び出されます。
initInternalの時と同じでServiceのstartを呼び出しています。
public class StandardService extends LifecycleMBeanBase implements Service {
@Override
protected void startInternal() throws LifecycleException {
// ...中略
// Connectorのstartを呼び出す
for (Connector connector: connectors) {
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
ServiceもinitInternal同様にConnectorのstartを呼び出しています。
public class Connector extends LifecycleMBeanBase {
@Override
protected void startInternal() throws LifecycleException {
// ...中略
protocolHandler.start();
// ...中略
}
}
public abstract class AbstractProtocol implements ProtocolHandler,
MBeanRegistration {

protected AbstractEndpoint endpoint = null;

@Override
public void start() throws Exception {
endpoint.start();
}

}

public abstract class AbstractEndpoint {
public final void start() throws Exception {
// ...中略
startInternal();
}
public abstract void startInternal() throws Exception;
}
まとめて複数クラスを見ましたが、結局最後に呼び出されているのは、
抽象クラスAbstractEndpointの具象クラスのstartInternalメソッドです。

で、AbstractEndpointの具象クラスがどこで決定されるかというと、
ProtocolHandlerの具象クラスのコンストラクタの中で決定されています。

ProtocolHandlerの具象クラスを決定しているのは、
たしかConnectorのコンストラクタから呼ばれるsetProtocolの処理でしたね。
ためしに、<Connector protocol="HTTP/1.1" ...> とされた場合の分岐を見てみましょう。
public class Connector extends LifecycleMBeanBase {
public void setProtocol(String protocol) {
// ...中略
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");
}
// ...中略
}
}

org.apache.coyote.http11.Http11Protocolというクラスのようですね。
中を見てみましょう。
public class Http11Protocol extends AbstractHttp11JsseProtocol {
public Http11Protocol() {
endpoint = new JIoEndpoint();
cHandler = new Http11ConnectionHandler(this);
((JIoEndpoint) endpoint).setHandler(cHandler);
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
}
プロトコルがHTTP1.1の場合、AbstractEndpointの具象クラスがJIoEndpointになることがわかりました。
このままプロトコルをHTTP1.1と仮定し、JIoEndpointのstartInternalを見てみましょう。
public class JIoEndpoint extends AbstractEndpoint {
@Override
public void startInternal() throws Exception {
if (!running) {
// ...中略
if (getExecutor() == null) {
createExecutor();// スレッドプールを生成
}
// ...中略
startAcceptorThreads();
// ...中略
}
}

@Override
protected AbstractEndpoint.Acceptor createAcceptor() {
return new Acceptor();
}

protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run() {

while (running) {
// ...中略
// 要求を待ち受ける
Socket socket = serverSocketFactory.acceptSocket(serverSocket);
if (running && !paused && setSocketOptions(socket)) {
// ソケットを渡して要求処理
if (!processSocket(socket)) {
closeSocket(socket);
}
} else {
closeSocket(socket);
}
// ...中略
}
state = AcceptorState.ENDED;
}
}

protected boolean processSocket(Socket socket) {
SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
// ...中略
// 別スレッドで処理を実行する
getExecutor().execute(new SocketProcessor(wrapper));
return true;
}

protected class SocketProcessor implements Runnable {
// ...中略
}

}
public abstract class AbstractEndpoint {

public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(
getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(),
getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}

/** 要求を待ち受けるスレッドを開始する */
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
Thread t = new Thread(acceptors[i], getName() + "-Acceptor-" + i);
// ...中略
t.start();
}
}

protected abstract Acceptor createAcceptor();
/** リクエストを待ち受けるRunnable(中身は略) */
public abstract static class Acceptor implements Runnable {}

}

まずcreateExecutorメソッドでスレッドプールを生成しています。
中で使っているThreadPoolExecutorは標準ライブラリに入っているクラスです。
(かつてスレッドプールはTomcatが自前で実装していました。)

次に親クラスのstartAcceptorThreadsメソッドで要求を待ち受けるスレッドを開始させています。
スレッドから実行される処理は、JIoEndpointの内部クラス Acceptor です。
(親から呼ばれる createAcceptor メソッドで生成しています)

Acceptor の中の処理は見ての通り無限ループになっており、
acceptSocket メソッドで待ち受け手に入れたソケットを processSocket メソッドに渡しています。
processSocket メソッドは、その先の処理を行う実行クラス(Runnable)であるSocketProcessorを
生成して先ほど生成したスレッドプールへ渡して別スレッドで実行させています。

何故別のスレッドに処理を行わせるかというと、要求を受けるスレッド自身が処理を行ってしまうと、
一度に複数の要求があった時に対応しきれないからです。

このように「待ちうけ」と「要求に対する処理」を行うスレッドを分ける実装方法は
サーバ型アプリケーションではよく使われるパターンです。

長かった起動処理ですが、Tomcatが要求を受け付けられるようになったところで終了です。

■待ちうけスレッドと要求処理のスレッドを視覚的に確認する
Eclipseのデバッグビューを見ると、スレッドが起動されていることがわかります。
createExecutorの中の getName() + "-exec-" で指定した名前のスレッドです。
(Acceptor スレッドもいますね)
tomcat-read-stack.jpg  
要求が沢山くると、このスレッドが増えます。

次回:リクエスト処理編その1
関連記事
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

Kenji Suzuki

Author:Kenji Suzuki
IT技術に関するあれこれを書いているブログです。
Pujohead Softの方では開発したソフトを公開しています。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
タグ

プログラミング デザインパターン コードリーディング bat 

検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
メールフォーム

名前:
メール:
件名:
本文:

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。