Tomcat7启动分析

Tomcat7启动分析(一)启动脚本

在之前的环境搭建那篇文章里是通过直接运行BootStarp的main函数来启动的,只是加了一个catalina.home的系统属性。而正常情况下启动Tomcat是通过运行脚本的方式,这个就涉及到建立工程时拷贝过来的script目录下的一堆脚本文件了。



以我的windows系统举例,实际上最终是执行startup.bat这个批处理文件来启动tomcat的。

那么启动分析就冲这个文件开始吧:

 

一、分析startup.bat文件

 1@echooff 2rem Licensed to theApache Software Foundation (ASF) under one or more 3rem contributor license agreements.  See the NOTICE file distributed with 4rem this work foradditional information regarding copyright ownership. 5rem The ASF licenses this file to You underthe Apache License, Version 2.0 6rem (the "License"); you may not use this file except incompliance with 7rem the License.  You may obtain acopy of the License at 8rem 9rem    http://www.apache.org/licenses/LICENSE-2.010rem11rem Unless required by applicable law or agreed to in writing, software12rem distributed underthe License is distributed on an "AS IS" BASIS,13rem WITHOUTWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14rem See the Licensefor the specific language governing permissions and15rem limitations under the License.1617if "%OS%" == "Windows_NT"setlocal18rem ---------------------------------------------------------------------------19rem Start script forthe CATALINA Server20rem21rem $Id: startup.bat 895392 2010-01-03 14:02:31Z kkolinko $22rem---------------------------------------------------------------------------2324rem Guess CATALINA_HOME if not defined25set "CURRENT_DIR=%cd%"26ifnot "%CATALINA_HOME%" =="" goto gotHome27set "CATALINA_HOME=%CURRENT_DIR%"28ifexist"%CATALINA_HOME%/bin/catalina.bat"gotookHome29cd ..30set "CATALINA_HOME=%cd%"31cd "%CURRENT_DIR%"32:gotHome33ifexist"%CATALINA_HOME%/bin/catalina.bat"gotookHome34echo The CATALINA_HOME environment variable isnot defined correctly35echo This environment variable is needed torun this program36gotoend37:okHome3839set "EXECUTABLE=%CATALINA_HOME%/bin/catalina.bat"4041rem Check that target executable exists42ifexist "%EXECUTABLE%" goto okExec43echo Cannotfind"%EXECUTABLE%"44echo This file is needed torun this program45gotoend46:okExec4748rem Get remaining unshifted command line arguments and save them in the49set CMD_LINE_ARGS=50:setArgs51if ""%1""==""""goto doneSetArgs52set CMD_LINE_ARGS=%CMD_LINE_ARGS%%153shift54goto setArgs55:doneSetArgs5657call "%EXECUTABLE%"start %CMD_LINE_ARGS%5859:end

第1行@echo off的是关闭显示命令行自身的意思,接下来一大堆rem是注释,第17行if"%OS%" == "Windows_NT" setlocal是要临时修改系统变量,方便批处理运行。第26到33行的作用是设置CATALINA_HOME环境变量,如果操作系统中没设置,则将当前运行脚本的目录作为该环境变量的值,最终都会跳转到37行。39到46行验证CATALINA_HOME变量所示目录下的catalina.bat文件是否存在,不存在则批处理直接结束。48到55行将运行脚本前如果设置了其他参数,将参数保存到CMD_LINE_ARGS变量中。最后第57行执行catalina.bat批处理文件,注意,该行后面跟着两个参数,第1个参数是start

 

二、分析catalina.bat文件

  1@echooff  2rem Licensed to the Apache Software Foundation (ASF) under one or more  3rem contributor license agreements. See the NOTICE file distributed with  4rem this work for additional information regarding copyright ownership.  5rem The ASF licenses this file to You under the Apache License, Version2.0  6rem (the "License"); you may not use this file except in compliancewith  7rem the License.  You may obtain acopy of the License at  8rem  9rem    http://www.apache.org/licenses/LICENSE-2.0 10rem11rem Unless required by applicable law or agreed to inwriting, software 12rem distributed under the License is distributed on an "AS IS"BASIS, 13rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied. 14rem See the License for the specific language governing permissions and 15rem limitations underthe License. 16 17if "%OS%" == "Windows_NT"setlocal 18rem--------------------------------------------------------------------------- 19rem Start/Stop Scriptfor the CATALINA Server 20rem 21rem Environment Variable Prerequisites 22rem23rem   Do not setthe variables in this script. Instead put them into a script 24rem   setenv.bat in CATALINA_BASE/bin to keep yourcustomizations separate. 25rem 26rem   CATALINA_HOME   May point at your Catalina "build"directory. 27rem 28rem   CATALINA_BASE   (Optional) Base directory for resolvingdynamic portions 29rem                   of a Catalinainstallation.  If not present, resolvesto 30rem                   the same directory thatCATALINA_HOME points to. 31rem 32rem   CATALINA_OPTS   (Optional) Java runtime options used whenthe "start", 33rem                  "run" or "debug" command is executed. 34rem                   Include here and not inJAVA_OPTS all options, that should35rem                  only be used by Tomcat itself, not by the stop process, 36rem                   the version command etc. 37rem                   Examples are heap size, GClogging, JMX ports etc. 38rem 39rem   CATALINA_TMPDIR (Optional)Directory path location of temporary directory 40rem                   the JVM should use(java.io.tmpdir).  Defaults to 41rem                   %CATALINA_BASE%/temp. 42rem 43rem   JAVA_HOME       Must point at your Java Development Kitinstallation. 44rem                   Required torun the with the "debug" argument. 45rem46rem   JRE_HOME        Must point at your Java Runtimeinstallation. 47rem                   Defaults toJAVA_HOME if empty. If JRE_HOME and JAVA_HOME 48rem                   are both set, JRE_HOME isused. 49rem 50rem   JAVA_OPTS       (Optional) Java runtime options usedwhen any command 51rem                   is executed. 52rem                   Include here and not inCATALINA_OPTS all options, that 53rem                   should be usedby Tomcat and also by the stop process, 54rem                   the version command etc. 55rem                   Most options should go intoCATALINA_OPTS. 56rem 57rem   JAVA_ENDORSED_DIRS (Optional)Lists of of semi-colon separated directories 58rem                   containing some jars inorder to allow replacement of APIs59rem                  created outside of the JCP (i.e. DOM and SAX from W3C). 60rem                   It can also be used toupdate the XML parser implementation.61rem                  Defaults to $CATALINA_HOME/endorsed. 62rem63rem  JPDA_TRANSPORT  (Optional) JPDAtransport used when the "jpda start" 64rem                   command is executed. Thedefault is "dt_socket". 65rem 66rem   JPDA_ADDRESS    (Optional) Java runtime options used whenthe "jpda start" 67rem                   command isexecuted. The default is 8000. 68rem 69rem   JPDA_SUSPEND    (Optional) Java runtime options used whenthe "jpda start" 70rem                   command isexecuted. Specifies whether JVM should suspend 71rem                   execution immediately after startup.Default is "n". 72rem 73rem   JPDA_OPTS       (Optional) Java runtime options usedwhen the "jpda start" 74rem                   command isexecuted. If used, JPDA_TRANSPORT, JPDA_ADDRESS, 75rem                   and JPDA_SUSPEND are ignored. Thus, allrequired jpda 76rem                   options MUSTbe specified. The default is: 77rem 78rem                  -agentlib:jdwp=transport=%JPDA_TRANSPORT%, 79rem                      address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND% 80rem 81rem   LOGGING_CONFIG  (Optional) Override Tomcat's logging configfile 82rem                   Example (all one line) 83rem                   setLOGGING_CONFIG="-Djava.util.logging.config.file=%CATALINA_BASE%/conf/logging.properties" 84rem 85rem   LOGGING_MANAGER (Optional) Override Tomcat'slogging manager 86rem                   Example (allone line) 87rem                   setLOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager" 88rem 89rem   TITLE           (Optional) Specify the title ofTomcat window. The default 90rem                   TITLE isTomcat if it's not specified. 91rem                   Example (allone line) 92rem                   setTITLE=Tomcat.Cluster#1.Server#1 [%DATE% %TIME%] 93rem94rem 95rem 96rem $Id: catalina.bat 1344732 2012-05-31 14:08:02Z kkolinko $ 97rem--------------------------------------------------------------------------- 98 99rem SuppressTerminate batch job on CTRL+C100ifnot ""%1""== ""run"" gotomainEntry101if "%TEMP%" == ""goto mainEntry102ifexist "%TEMP%/%~nx0.run"goto mainEntry103echo Y>"%TEMP%/%~nx0.run"104ifnotexist "%TEMP%/%~nx0.run" goto mainEntry105echo Y>"%TEMP%/%~nx0.Y"106call "%~f0" %*<"%TEMP%/%~nx0.Y"107rem Use provided errorlevel108set RETVAL=%ERRORLEVEL%109del /Q "%TEMP%/%~nx0.Y" >NUL2>&1110exit /B %RETVAL%111:mainEntry112del /Q "%TEMP%/%~nx0.run" >NUL2>&1113114rem Guess CATALINA_HOME if not defined115set "CURRENT_DIR=%cd%"116ifnot "%CATALINA_HOME%" ==""goto gotHome117set "CATALINA_HOME=%CURRENT_DIR%"118ifexist"%CATALINA_HOME%/bin/catalina.bat"gotookHome119cd ..120set "CATALINA_HOME=%cd%"121cd "%CURRENT_DIR%"122:gotHome123124ifexist"%CATALINA_HOME%/bin/catalina.bat"gotookHome125echo The CATALINA_HOME environment variable isnot defined correctly126echo This environment variable is needed torun this program127gotoend128:okHome129130rem Copy CATALINA_BASE from CATALINA_HOME if not defined131ifnot "%CATALINA_BASE%" ==""goto gotBase132set "CATALINA_BASE=%CATALINA_HOME%"133:gotBase134135rem Ensure that anyuser defined CLASSPATH variables are not used on startup,136rem but allow them tobe specified in setenv.bat, in rare case when it is needed.137set CLASSPATH=138139rem Get standardenvironment variables140ifnotexist "%CATALINA_BASE%/bin/setenv.bat"goto checkSetenvHome141call "%CATALINA_BASE%/bin/setenv.bat"142goto setenvDone143:checkSetenvHome144ifexist "%CATALINA_HOME%/bin/setenv.bat"call "%CATALINA_HOME%/bin/setenv.bat"145:setenvDone146147rem Get standard Java environment variables148ifexist"%CATALINA_HOME%/bin/setclasspath.bat"gotookSetclasspath149echo Cannotfind"%CATALINA_HOME%/bin/setclasspath.bat"150echo This file is needed torun this program151gotoend152:okSetclasspath153call"%CATALINA_HOME%/bin/setclasspath.bat"%1154iferrorlevel 1gotoend155156rem Add on extra jar file to CLASSPATH157rem Note that there are no quotes as we donot want to introduce random158rem quotes into the CLASSPATH159if "%CLASSPATH%" == "" goto emptyClasspath160set "CLASSPATH=%CLASSPATH%;"161:emptyClasspath162set"CLASSPATH=%CLASSPATH%%CATALINA_HOME%/bin/bootstrap.jar"163164ifnot "%CATALINA_TMPDIR%" =="" goto gotTmpdir165set "CATALINA_TMPDIR=%CATALINA_BASE%/temp"166:gotTmpdir167168rem Addtomcat-juli.jar to classpath169rem tomcat-juli.jar can be over-ridden per instance170ifnotexist"%CATALINA_BASE%/bin/tomcat-juli.jar"gotojuliClasspathHome171set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%/bin/tomcat-juli.jar"172goto juliClasspathDone173:juliClasspathHome174set "CLASSPATH=%CLASSPATH%;%CATALINA_HOME%/bin/tomcat-juli.jar"175:juliClasspathDone176177ifnot "%LOGGING_CONFIG%" =="" goto noJuliConfig178set LOGGING_CONFIG=-Dnop179ifnotexist"%CATALINA_BASE%/conf/logging.properties"gotonoJuliConfig180setLOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%/conf/logging.properties"181:noJuliConfig182set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%183184ifnot "%LOGGING_MANAGER%" ==""goto noJuliManager185set LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager186:noJuliManager187set JAVA_OPTS=%JAVA_OPTS%%LOGGING_MANAGER%188189rem ----- Execute The Requested Command ---------------------------------------190191echo Using CATALINA_BASE:   "%CATALINA_BASE%"192echo Using CATALINA_HOME:   "%CATALINA_HOME%"193echo Using CATALINA_TMPDIR:"%CATALINA_TMPDIR%"194if ""%1""== ""debug""gotouse_jdk195echo Using JRE_HOME:        "%JRE_HOME%"196goto java_dir_displayed197:use_jdk198echo Using JAVA_HOME:      "%JAVA_HOME%"199:java_dir_displayed200echo Using CLASSPATH:       "%CLASSPATH%"201202set _EXECJAVA=%_RUNJAVA%203set MAINCLASS=org.apache.catalina.startup.Bootstrap204set ACTION=start205set SECURITY_POLICY_FILE=206set DEBUG_OPTS=207set JPDA=208209ifnot ""%1""== ""jpda""goto noJpda210set JPDA=jpda211ifnot "%JPDA_TRANSPORT%" =="" goto gotJpdaTransport212set JPDA_TRANSPORT=dt_socket213:gotJpdaTransport214ifnot "%JPDA_ADDRESS%" == ""goto gotJpdaAddress215set JPDA_ADDRESS=8000216:gotJpdaAddress217ifnot "%JPDA_SUSPEND%" == ""goto gotJpdaSuspend218set JPDA_SUSPEND=n219:gotJpdaSuspend220ifnot "%JPDA_OPTS%" == "" goto gotJpdaOpts221set JPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%222:gotJpdaOpts223shift224:noJpda225226if ""%1""== ""debug""gotodoDebug227if ""%1""== ""run""goto doRun228if ""%1""== ""start""gotodoStart229if ""%1""== ""stop""goto doStop230if ""%1"" ==""configtest""goto doConfigTest231if ""%1"" ==""version""goto doVersion232233echo Usage: catalina ( commands ... )234echo commands:235echo  debug            StartCatalina in a debugger236echo  debug -security  Debug Catalina with a security manager237echo  jpdastart       Start Catalina under JPDA debugger238echo  run              Start Catalina in the current window239echo  run -security    Start in the current window with security manager240echo  start            StartCatalina in a separate window241echo  start -security  Start in a separate window with security manager242echo   stop              Stop Catalina243echo  configtest       Run a basic syntax checkon server.xml244echo  version           What version oftomcat are you running?245gotoend246247:doDebug248shift249set _EXECJAVA=%_RUNJDB%250set DEBUG_OPTS=-sourcepath"%CATALINA_HOME%/../../java"251ifnot ""%1""== ""-security""goto execCmd252shift253echo Using Security Manager254set"SECURITY_POLICY_FILE=%CATALINA_BASE%/conf/catalina.policy"255goto execCmd256257:doRun258shift259ifnot ""%1""== ""-security""goto execCmd260shift261echo Using Security Manager262set "SECURITY_POLICY_FILE=%CATALINA_BASE%/conf/catalina.policy"263goto execCmd264265:doStart266shift267ifnot "%OS%" == "Windows_NT"goto noTitle268if "%TITLE%" == ""set TITLE=Tomcat269set _EXECJAVA=start"%TITLE%" %_RUNJAVA%270goto gotTitle271:noTitle272set _EXECJAVA=start %_RUNJAVA%273:gotTitle274ifnot ""%1""== ""-security""goto execCmd275shift276echo Using Security Manager277set"SECURITY_POLICY_FILE=%CATALINA_BASE%/conf/catalina.policy"278goto execCmd279280:doStop281shift282set ACTION=stop283set CATALINA_OPTS=284goto execCmd285286:doConfigTest287shift288set ACTION=configtest289set CATALINA_OPTS=290goto execCmd291292:doVersion293 %_EXECJAVA% -classpath"%CATALINA_HOME%/lib/catalina.jar" org.apache.catalina.util.ServerInfo294gotoend295296297:execCmd298rem Get remaining unshifted command line arguments and save them in the299set CMD_LINE_ARGS=300:setArgs301if ""%1""==""""goto doneSetArgs302set CMD_LINE_ARGS=%CMD_LINE_ARGS%%1303shift304goto setArgs305:doneSetArgs306307rem Execute Java withthe applicable properties308ifnot "%JPDA%" == ""goto doJpda309ifnot "%SECURITY_POLICY_FILE%" ==""goto doSecurity310%_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%311gotoend312:doSecurity313 %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%"-classpath "%CLASSPATH%" -Djava.security.manager-Djava.security.policy=="%SECURITY_POLICY_FILE%"-Dcatalina.base="%CATALINA_BASE%"-Dcatalina.home="%CATALINA_HOME%"-Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%314gotoend315:doJpda316ifnot "%SECURITY_POLICY_FILE%" =="" goto doSecurityJpda317 %_EXECJAVA%%JAVA_OPTS% %CATALINA_OPTS% %JPDA_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%318gotoend319:doSecurityJpda320 %_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %JPDA_OPTS% %DEBUG_OPTS%-Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath"%CLASSPATH%" -Djava.security.manager-Djava.security.policy=="%SECURITY_POLICY_FILE%"-Dcatalina.base="%CATALINA_BASE%"-Dcatalina.home="%CATALINA_HOME%"-Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%321gotoend322323:end

 

1到99行一大堆注释说明就不看了,100到110行一堆run文件校验之类的一般用不到,直接跳到111行,接下来先删除上面可能产生的中间文件,接着按照正常流程,在上面的startup.bat文件中已经设置过CATALINA_HOME变量,并且校验了改变量下bin/catalina.bat文件已经存在(我猜作者写这一段是防止用户直接运行catalina.bat文件)。经过这些判断后跳转到128行,130到133行设置CATALINA_BASE变量,中间几行注释略过,139到144行如果有setenv.bat文件则执行一下该文件。147到153行执行setclasspath.bat文件(该文件在同级目录下已经提供,具体脚本下面已经贴出来了,可以读一下,这里不再做分析。主要功能校验是否设置JAVA_HOME变量,没有则在控制台显示出提示信息。注意该文件中第79行将会设置_RUNJAVA变量,该变量的值即JAVA_HOME或JRE_HOME的bin目录下的java.exe文件)。从156到224行校验并设置一堆变量(注意第202、203和204行,最后启动分析时将会用到)。226到231行将根据调用catalina.bat时设置的参数决定跳转,按照上一段的最后的说明,此时将会跳转到doStart标记行。从265行开始,校验和设置一些变量之后(注意第269)将会跳转到execCmd标记行,即297行。297到305行作用是将系统参数保存到CMD_LINE_ARGS变量中。

 

接下来便是谜底解开的时候,正常情况下将会执行到310行,这里单独贴出来分析一下:

%_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%

 

按照202行的提示_EXECJAVA一开始的值等于_RUNJAVA,但在269行将该值改成了start "%TITLE%" %_RUNJAVA%,这里的作用是另启动一个命令行窗口,并给窗口起名,最终还是执行java.exe文件。从%JAVA_OPTS%开始到%MAINCLASS%之前都是在设置运行时参数和系统属性,%MAINCLASS%即203行的org.apache.catalina.startup.Bootstrap,后面是其它的命令行参数,最后一个参数%ACTION%在204行时被设置成start。归结起来一句话,即执行Bootstrap类的main方法。

 

setclasspath.bat:

 1@echooff 2rem Licensed to theApache Software Foundation (ASF) under one or more 3rem contributor license agreements.  See the NOTICE file distributed with 4rem this work foradditional information regarding copyright ownership. 5rem The ASF licenses this file to You underthe Apache License, Version 2.0 6rem (the "License"); you may not use this file except incompliance with 7rem the License.  You may obtain acopy of the License at 8rem 9rem    http://www.apache.org/licenses/LICENSE-2.010rem11rem Unless required by applicable law or agreed to in writing, software12rem distributed underthe License is distributed on an "AS IS" BASIS,13rem WITHOUTWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14rem See the Licensefor the specific language governing permissions and15rem limitations under the License.1617rem ---------------------------------------------------------------------------18rem Set JAVA_HOME orJRE_HOME if not already set, ensure any provided settings19rem are valid andconsistent with the selected start-up options and set up the20rem endorsed directory.21rem22rem $Id:setclasspath.bat 1202062 2011-11-15 06:50:02Z mturk $23rem---------------------------------------------------------------------------2425rem Make sure prerequisite environment variables are set2627rem In debug mode we need a real JDK (JAVA_HOME)28if ""%1"" == ""debug""goto needJavaHome2930rem Otherwise eitherJRE or JDK are fine31ifnot "%JRE_HOME%" == ""goto gotJreHome32ifnot "%JAVA_HOME%" == ""goto gotJavaHome33echo Neither the JAVA_HOME nor the JRE_HOMEenvironment variable is defined34echo At least one of these environment variableis needed torun this program35gotoexit3637:needJavaHome38rem Check if we have a usable JDK39if "%JAVA_HOME%" == ""goto noJavaHome40ifnotexist "%JAVA_HOME%/bin/java.exe"goto noJavaHome41ifnotexist "%JAVA_HOME%/bin/javaw.exe"goto noJavaHome42ifnotexist "%JAVA_HOME%/bin/jdb.exe"goto noJavaHome43ifnotexist "%JAVA_HOME%/bin/javac.exe"goto noJavaHome44set "JRE_HOME=%JAVA_HOME%"45goto okJava4647:noJavaHome48echo The JAVA_HOME environment variable isnot defined correctly.49echo It is needed torunthis program indebugmode.50echo NB: JAVA_HOME should point to a JDKnot a JRE.51gotoexit5253:gotJavaHome54rem No JRE given, use JAVA_HOME as JRE_HOME55set "JRE_HOME=%JAVA_HOME%"5657:gotJreHome58rem Check if we have a usable JRE59ifnotexist "%JRE_HOME%/bin/java.exe"goto noJreHome60ifnotexist "%JRE_HOME%/bin/javaw.exe"goto noJreHome61goto okJava6263:noJreHome64rem Needed at least aJRE65echo The JRE_HOME environment variable isnot defined correctly66echo This environment variable is needed torun this program67gotoexit6869:okJava70rem Don't override the endorsed dir if the user has set it previously71ifnot "%JAVA_ENDORSED_DIRS%" ==""goto gotEndorseddir72rem Set the default -Djava.endorsed.dirsargument73set"JAVA_ENDORSED_DIRS=%CATALINA_HOME%/endorsed"74:gotEndorseddir7576rem Set standardcommand for invoking Java.77rem Note that NT requires a window name argument when using start.78rem Also note thequoting as JAVA_HOME may contain spaces.79set _RUNJAVA="%JRE_HOME%/bin/java"80set _RUNJDB="%JAVA_HOME%/bin/jdb"8182gotoend8384:exit85exit /b 18687:end88exit /b 0

三、总结

以上即tomcat的启动脚本中的总体流程,只是讲了默认的脚本运行方式,但在脚本阅读中可以看到能根据catalina.bat里可以根据不同参数以不同方式运行tomcat:

debug            Start Catalina in a debugger

debug -security   Debug Catalina with asecurity manager

jpda start       Start Catalina under JPDA debugger

run              Start Catalina in the current window

run -security     Start in thecurrent window with security manager

start            Start Catalina in a separate window

start -security   Start in a separate windowwith security manager

stop             Stop Catalina

configtest        Runa basic syntax check on server.xml

 

只要能看懂基本dos命令,这几个脚本的阅读其实很简单,其他bat文件可以大体浏览一下,会发现转了半天,最终都会执行catalina.bat文件,而catalina.bat文件里最终会执行Bootstrap文件的main方法,不同的是调用main方法时会各自添加不同的入参而已。bat文件用于windows操作系统下启动tomcat,而sh文件则用于unix环境下的启动,原理是一致的。

Tomcat7启动分析(二)Bootstrap类中的main方法

之前分析了Tomcat的启动脚本,如果从startup.bat开始启动Tomcat的话会发现最后会调用org.apache.catalina.startup.Bootstrap里的main方法,并且传过来的最后一个命令行参数是start,接下来的启动代码分析就从这里开始。

 

先看下这个main方法的代码:

 1/** 2     * Main method and entry point when starting Tomcat via the provided 3     * scripts. 4     * 5     * @param args Command line arguments to be processed 6      */ 7     publicstaticvoid main(String args[]) { 8 9         if (daemon == null) {10             // Don't set daemon until init() has completed11             Bootstrap bootstrap = new Bootstrap();12             try {13                bootstrap.init();14             } catch (Throwable t) {15                handleThrowable(t);16                t.printStackTrace();17                 return;18            }19             daemon = bootstrap;20         } else {21             // When running as a service the call to stop will be on a new22             // thread so make sure the correct class loader is used to prevent23             // a range of class not found exceptions.24            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);25        }2627         try {28             String command = "start";29             if (args.length > 0) {30                 command = args[args.length - 1];31            }3233             if (command.equals("startd")) {34                 args[args.length - 1] = "start";35                daemon.load(args);36                daemon.start();37             } elseif (command.equals("stopd")) {38                 args[args.length - 1] = "stop";39                daemon.stop();40             } elseif (command.equals("start")) {41                 daemon.setAwait(true);42                daemon.load(args);43                daemon.start();44             } elseif (command.equals("stop")) {45                daemon.stopServer(args);46             } elseif (command.equals("configtest")) {47                daemon.load(args);48                 if (null==daemon.getServer()) {49                     System.exit(1);50                }51                 System.exit(0);52             } else {53                 log.warn("Bootstrap: command /"" + command + "/" does not exist.");54            }55         } catch (Throwable t) {56             // Unwrap the Exception for clearer error reporting57             if (t instanceof InvocationTargetException &&58                     t.getCause() != null) {59                 t = t.getCause();60            }61            handleThrowable(t);62            t.printStackTrace();63             System.exit(1);64        }6566     }

这里的daemon是Bootstrap类中的一个静态成员变量,类型就是Bootstrap,第10行的注释已经说明在调用过init方法之后才会给该变量赋值,初始时将是null,所以首先将实例化一个Bootstrap对象,接着调用init方法,该方法代码如下:

 1/** 2     * Initialize daemon. 3      */ 4     publicvoid init() 5         throws Exception 6    { 7 8         // Set Catalina path 9        setCatalinaHome();10        setCatalinaBase();1112        initClassLoaders();1314        Thread.currentThread().setContextClassLoader(catalinaLoader);1516        SecurityClassLoad.securityClassLoad(catalinaLoader);1718         // Load our startup class and call its process() method19         if (log.isDebugEnabled())20             log.debug("Loading startup class");21         Class<?> startupClass =22            catalinaLoader.loadClass23             ("org.apache.catalina.startup.Catalina");24         Object startupInstance = startupClass.newInstance();2526         // Set the shared extensions class loader27         if (log.isDebugEnabled())28             log.debug("Setting startup class properties");29         String methodName = "setParentClassLoader";30         Class<?> paramTypes[] = new Class[1];31         paramTypes[0] = Class.forName("java.lang.ClassLoader");32         Object paramValues[] = new Object[1];33         paramValues[0] = sharedLoader;34         Method method =35            startupInstance.getClass().getMethod(methodName, paramTypes);36        method.invoke(startupInstance, paramValues);3738         catalinaDaemon = startupInstance;3940     }

 这里不再逐句解释代码的作用,总的来说这个方法主要做了一下几件事:1.设置catalina.home、catalina.base系统属性,2.创建commonLoader、catalinaLoader、sharedLoader三个类加载器(建议看看createClassLoader方法,里面做的事情还挺多,比如装载catalina.properties里配置的目录下的文件和jar包,后两个加载器的父加载器都是第一个,最后注册了MBean,可以用于JVM监控该对象),3.实例化一个org.apache.catalina.startup.Catalina对象,并赋值给静态成员catalinaDaemon,以sharedLoader作为入参通过反射调用该对象的setParentClassLoader方法。

 

接下来去命令行最后一个参数,按文章开头所说是start,所以将执行34行到36行的代码,将会执行Bootstrap类中的load、start方法。

 

load方法代码如下:

 1     /** 2     * Load daemon. 3      */ 4     privatevoid load(String[] arguments) 5         throws Exception { 6 7         // Call the load() method 8         String methodName = "load"; 9        Object param[];10         Class<?> paramTypes[];11         if (arguments==null || arguments.length==0) {12             paramTypes = null;13             param = null;14         } else {15             paramTypes = new Class[1];16             paramTypes[0] = arguments.getClass();17             param = new Object[1];18             param[0] = arguments;19        }20         Method method =21            catalinaDaemon.getClass().getMethod(methodName, paramTypes);22         if (log.isDebugEnabled())23             log.debug("Calling startup class " + method);24        method.invoke(catalinaDaemon, param);2526     }

就是通过反射调用catalinaDaemon对象的load方法,catalinaDaemon对象在上面的init方法中已经实例化过了。

 

start方法与load方法相似,也是通过反射调用catalinaDaemon对象上的start方法:

 1     /** 2     * Start the Catalina daemon. 3      */ 4     publicvoid start() 5         throws Exception { 6         if( catalinaDaemon==null ) init(); 7 8         Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null); 9         method.invoke(catalinaDaemon, (Object [])null);1011     }

下面一篇文章将分析catalinaDaemon对象中的load、start两个方法,里面会涉及一个有趣的话题——Digester的使用。

 

Tomcat7启动分析(三)Digester的使用

前一篇文章里最后看到Bootstrap的main方法最后会调用org.apache.catalina.startup.Catalina对象的load和start两个方法,那么就来看看这两个方法里面到底做了些什么。

 

load方法:

  1    /**  2     * Start a new server instance.  3     */  4     publicvoid load() {  5  6        long t1= System.nanoTime();  7  8        initDirs();  9 10        // Before digester - it may be needed 11 12        initNaming(); 13 14        // Create and executeour Digester 15        Digester digester =createStartDigester(); 16 17         InputSource inputSource = null; 18        InputStream inputStream =null;19        File file =null;20        try {21             file = configFile();22             inputStream = new FileInputStream(file); 23            inputSource =newInputSource(file.toURI().toURL().toString()); 24         }catch(Exception e) { 25            if (log.isDebugEnabled()) { 26                log.debug(sm.getString("catalina.configFail", file), e); 27            } 28        } 29        if(inputStream ==null) {30            try { 31                 inputStream = getClass().getClassLoader() 32                    .getResourceAsStream(getConfigFile()); 33                 inputSource =new InputSource 34                    (getClass().getClassLoader() 35                     .getResource(getConfigFile()).toString()); 36             } catch (Exception e) { 37                if(log.isDebugEnabled()) { 38                    log.debug(sm.getString("catalina.configFail",39                            getConfigFile()),e); 40                } 41            } 42        }43 44        // This should beincluded in catalina.jar 45        // Alternative: don'tbother with xml, just create it manually. 46        if(inputStream==null ) {47            try { 48                 inputStream = getClass().getClassLoader() 49                        .getResourceAsStream("server-embed.xml");50                 inputSource =new InputSource 51                (getClass().getClassLoader() 52                        .getResource("server-embed.xml").toString());53             } catch(Exception e) { 54                if(log.isDebugEnabled()) { 55                    log.debug(sm.getString("catalina.configFail",56                            "server-embed.xml"), e); 57                } 58            } 59        } 60 61 62        if(inputStream ==null || inputSource ==null) { 63            if  (file == null) { 64                log.warn(sm.getString("catalina.configFail",65                         getConfigFile() +"] or [server-embed.xml]")); 66            }else {67                log.warn(sm.getString("catalina.configFail",68                        file.getAbsolutePath())); 69                 if (file.exists() && !file.canRead()){ 70                    log.warn("Permissions incorrect, read permission is not allowed onthe file."); 71                } 72            } 73            return; 74        } 75 76        try {77            inputSource.setByteStream(inputStream); 78             digester.push(this); 79            digester.parse(inputSource); 80         } catch(SAXParseException spe) { 81            log.warn("Catalina.start using " + getConfigFile() + ":" + 82                    spe.getMessage()); 83             return; 84        }catch (Exception e) { 85             log.warn("Catalina.startusing " + getConfigFile() + ": " , e); 86            return;87        }finally {88            try { 89                inputStream.close(); 90             }catch (IOException e) { 91                // Ignore 92            } 93        } 94 95        getServer().setCatalina(this);96 97        // Stream redirection 98        initStreams(); 99100        // Start the new server101        try {102            getServer().init();103         }catch(LifecycleException e) {104            if(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {105                thrownew java.lang.Error(e);106            }else {107                log.error("Catalina.start", e);108            }109110        }111112        long t2= System.nanoTime();113        if(log.isInfoEnabled()){114            log.info("Initialization processed in " + ((t2 - t1) /1000000) + " ms");115        }116117     }

这个110行的代码看起来东西挺多,把注释、异常抛出、记录日志、流关闭、非空判断这些放在一边就会发现实际上真正做事的就这么几行代码:

1     Digester digester = createStartDigester();2    inputSource.setByteStream(inputStream);3     digester.push(this);4    digester.parse(inputSource);5     getServer().setCatalina(this);6    getServer().init();

做的事情就两个,一是创建一个Digester对象,将当前对象压入Digester里的对象栈顶,根据inputSource里设置的文件xml路径及所创建的Digester对象所包含的解析规则生成相应对象,并调用相应方法将对象之间关联起来。二是调用Server接口对象的init方法。

 

这里碰到了Digester,有必要介绍一下Digester的一些基础知识。一般来说Java里解析xml文件有两种方式:一种是Dom4J之类将文件全部读取到内存中,在内存里构造一棵Dom树的方式来解析。一种是SAX的读取文件流,在流中碰到相应的xml节点触发相应的节点事件回调相应方法,基于事件的解析方式,优点是不需要先将文件全部读取到内存中。

Digester本身是采用SAX的解析方式,在其上提供了一层包装,对于使用者更简便友好罢了。最早是在struts1里面用的,后来独立出来成为apache的Commons下面的一个单独的子项目。Tomcat里又把它又封装了一层,为了描述方便,直接拿Tomcat里的Digester建一个单独的Digester的例子来介绍。

  1package org.study.digester;  2  3import java.io.IOException;  4import java.io.InputStream;  5import java.util.ArrayList;  6import java.util.HashMap;  7import java.util.List;  8  9import junit.framework.Assert; 10 11import org.apache.tomcat.util.digester.Digester; 12import org.xml.sax.InputSource; 13 14publicclass MyDigester { 15 16    private MyServer myServer; 17 18    public MyServer getMyServer() { 19        returnmyServer; 20    } 21 22    publicvoid setMyServer(MyServer myServer) { 23        this.myServer= myServer; 24    } 25 26    privateDigester createStartDigester() { 27        Digester digester =new Digester(); 28         29        //是否进行DTD校验 30         digester.setValidating(false); 31         32        //是否进行节点设置规则校验 33        digester.setRulesValidation(true);34         35        //className作为假属性,不需要设置到相应对象的setter方法中 36        HashMap<Class<?>, List<String>> fakeAttributes =new HashMap<Class<?>, List<String>>(); 37        ArrayList<String> attrs =newArrayList<String>(); 38        attrs.add("className"); 39        fakeAttributes.put(Object.class, attrs); 40        digester.setFakeAttributes(fakeAttributes); 41 42        digester.addObjectCreate("Server", 43                "org.study.digester.MyStandardServer", "className");44        digester.addSetProperties("Server"); 45        digester.addSetNext("Server", "setMyServer",46                "org.study.digester.MyServer"); 47         48         digester.addObjectCreate("Server/Listener",null, "className"); 49         digester.addSetProperties("Server/Listener");50         digester.addSetNext("Server/Listener","addLifecycleListener", 51         "org.apache.catalina.LifecycleListener");52         53         digester.addObjectCreate("Server/Service",54         "org.study.digester.MyStandardService", "className");55         digester.addSetProperties("Server/Service");56         digester.addSetNext("Server/Service", "addMyService",57         "org.study.digester.MyService"); 58         59         digester.addObjectCreate("Server/Service/Listener",null, 60         "className"); 61         digester.addSetProperties("Server/Service/Listener");62         digester.addSetNext("Server/Service/Listener",63         "addLifecycleListener", 64         "org.apache.catalina.LifecycleListener");65        returndigester; 66    } 67 68     public MyDigester() {69        Digester digester =createStartDigester(); 70 71         InputSource inputSource = null; 72        InputStream inputStream =null;73        try {74             String configFile ="myServer.xml"; 75             inputStream =getClass().getClassLoader().getResourceAsStream( 76                    configFile); 77             inputSource =new InputSource(getClass().getClassLoader() 78                    .getResource(configFile).toString()); 79 80            inputSource.setByteStream(inputStream); 81             digester.push(this); 82            digester.parse(inputSource); 83         } catch(Exception e) { 84            e.printStackTrace(); 85         }finally { 86             try {87                inputStream.close(); 88             }catch (IOException e) { 89                // Ignore 90            } 91        } 92 93        getMyServer().setMyDigester(this);94    } 95 96     publicstaticvoid main(String[] agrs) { 97         MyDigester md =new MyDigester(); 98        Assert.assertNotNull(md.getMyServer()); 99    }100 }

上面是我自己写的一个拿Tomcat里的Digester的工具类解析xml文件的例子,解析的是项目源文件发布的根目录下的myServer.xml文件。

 1<?xml version='1.0' encoding='utf-8'?>2 3<Serverport="8005" shutdown="SHUTDOWN"> 4 5    <Listener 6        className="org.apache.catalina.core.JasperListener"/> 7 8    <Listener 9        className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>1011    <Servicename="Catalina">1213        <Listener14            className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>15    </Service>16</Server>

Digester的使用一般来说有4步:1.实例化一个Digester对象,并在对象里设置相应的节点解析规则。2.设置要解析的文件作为输入源(InputSource),这里InputSource与SAX里的一样,用以表示一个xml实体。3.将当前对象压入栈,4.调用Digester的parse方法解析xml,生成相应的对象。第3步中将当前对象压入栈中的作用是可以保存一个到Digester生成的一系列对象直接的引用,方便后续使用而已,所以不必是当前对象,只要有一个地方存放这个引用即可。

再说下上面例子中的createStartDigester方法,第27行实例化一个Digester对象,第30行设置为false表示解析xml时不需要进行DTD的规则校验,第33行的意思是如果xml中相应节点没有设置解析规则会在控制台显示提示信息,第36到40行是将xml节点中的className作为假属性,不必调用默认的setter方法(一般的节点属性在解析时将会以属性值作为入参调用该节点相应对象的setter方法,而className属性的作用是提示解析器用该属性的值来实例化对象),第42行addObjectCreate方法的意思是碰到xml文件中的Server节点则创建一个org.study.digester.MyStandardServer对象,第44行是根据Server节点中的属性信息调用相应属性的setter方法,以上面的xml文件为例则会调用setPort、setShutdown方法,入参分别是8005、SHUTDOWN,第45行的意思是将Server节点对应的对象作为入参调用栈顶对象的setMyServer方法,这里的栈顶对象即第81行所设置的当前类的对象this,就是说调用MyDigester类的setMyServer方法。下面几段代码的作用与42到46类似,只是在碰到xml文件的不同节点时产生不同的对象,并调用相应的方法,如第48行在碰到xml的Server节点下的Listener节点时取className属性的值作为实例化类实例化一个对象等等,不再赘述。

这里有必要说明的是很多文章里按照代码顺序来描述Digester的所谓栈模型来讲述addSetNext方法时的调用对象,实际上我的理解addSetNext方法具体哪个调用对象与XML文件里的节点树形结构相关,当前节点的父节点是哪个对象该对象就是调用对象。可以试验一下把这里的代码顺序打乱仍然可以按规则解析出来,createStartDigester方法只是在定义解析规则,具体解析与addObjectCreate、addSetProperties、addSetNext这些方法的调用顺序无关。Digester自己内部在解析xml的节点元素时增加了一个rule的概念,addObjectCreate、addSetProperties、addSetNext这些方法内部实际上是在添加rule,在最后解析xml时将会根据读取到的节点匹配相应节点路径下的rule,调用内部的方法。关于Tomcat内的Digester的解析原理以后可以单独写篇文章分析一下。

该示例的代码在附件中,将其放入tomcat7的源代码环境中即可直接运行。

 

看懂了上面的例子Catalina的createStartDigester方法应该就可以看懂了,它只是比示例要处理的节点类型更多,并且增加几个自定义的解析规则,如384行在碰到Server/GlobalNamingResources/节点时将会调用org.apache.catalina.startup.NamingRuleSet类中的addRuleInstances方法添加解析规则。

 

要解析的XML文件默认会先找conf/server.xml,如果当前项目找不到则通过其他路径找xml文件来解析,这里以默认情况为例将会解析server.xml

  1<?xml version='1.0' encoding='utf-8'?>  2<!--  3  Licensed to the Apache SoftwareFoundation (ASF) under one or more  4  contributor license agreements.  See the NOTICE file distributed with  5  this work for additionalinformation regarding copyright ownership.  6  The ASF licenses this file to Youunder the Apache License, Version 2.0  7  (the "License"); you may not usethis file except in compliance with  8  the License. You may obtain a copy of the License at  9 10      http://www.apache.org/licenses/LICENSE-2.0 11 12  Unless required by applicable law or agreedto in writing, software 13  distributed under the License isdistributed on an "AS IS" BASIS, 14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,either express or implied. 15  See the License for the specificlanguage governing permissions and16  limitations underthe License. 17--> 18<!-- Note:  A "Server"is not itself a "Container", so you may not 19     define subcomponents such as"Valves" at this level. 20     Documentation at/docs/config/server.html 21 --> 22<Serverport="8005" shutdown="SHUTDOWN"> 23  <!-- Security listener. Documentation at/docs/config/listeners.html 24  <ListenerclassName="org.apache.catalina.security.SecurityListener" /> 25   --> 26  <!--APR library loader. Documentation at/docs/apr.html --> 27  <ListenerclassName="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/> 28   <!--Initialize Jasper prior to webapps are loaded.Documentation at /docs/jasper-howto.html --> 29  <ListenerclassName="org.apache.catalina.core.JasperListener"/> 30  <!-- Prevent memory leaks due to use of particularjava/javax APIs--> 31  <ListenerclassName="org.apache.catalina.core.JreMemoryLeakPreventionListener"/> 32  <ListenerclassName="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/> 33   <ListenerclassName="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/> 34 35   <!-- Global JNDI resources 36       Documentation at/docs/jndi-resources-howto.html 37  --> 38  <GlobalNamingResources>39    <!-- Editable userdatabase that can also be used by 40         UserDatabaseRealm toauthenticate users 41    --> 42    <Resourcename="UserDatabase" auth="Container" 43              type="org.apache.catalina.UserDatabase" 44              description="User database that can be updated andsaved" 45              factory="org.apache.catalina.users.MemoryUserDatabaseFactory" 46              pathname="conf/tomcat-users.xml"/> 47  </GlobalNamingResources>48 49  <!-- A "Service" is a collectionof one or more "Connectors" that share 50       a single "Container"Note:  A "Service" is notitself a "Container", 51       so you may not definesubcomponents such as "Valves" at this level. 52       Documentation at/docs/config/service.html 53   --> 54  <Servicename="Catalina"> 55 56    <!--The connectors can use a shared executor, you candefine one or more named thread pools--> 57    <!-- 58    <Executorname="tomcatThreadPool" namePrefix="catalina-exec-" 59        maxThreads="150"minSpareThreads="4"/>60    --> 61 62 63    <!-- A "Connector" represents an endpoint bywhich requests are received 64         and responses are returned.Documentation at : 65         Java HTTP Connector:/docs/config/http.html (blocking & non-blocking) 66         Java AJP  Connector: /docs/config/ajp.html 67         APR (HTTP/AJP) Connector:/docs/apr.html 68         Define a non-SSL HTTP/1.1Connector on port 8080 69    --> 70    <Connectorport="8080" protocol="HTTP/1.1" 71               connectionTimeout="20000" 72               redirectPort="8443"/> 73    <!-- A "Connector" using the shared threadpool--> 74    <!-- 75    <Connectorexecutor="tomcatThreadPool"76               port="8080"protocol="HTTP/1.1" 77               connectionTimeout="20000" 78               redirectPort="8443"/> 79    --> 80    <!-- Define a SSL HTTP/1.1 Connector on port 8443 81         This connector uses the JSSEconfiguration, when using APR, the82         connectorshould be using the OpenSSL style configuration 83         described in the APR documentation--> 84    <!-- 85    <Connector port="8443"protocol="HTTP/1.1" SSLEnabled="true" 86               maxThreads="150"scheme="https" secure="true" 87               clientAuth="false"sslProtocol="TLS" /> 88    --> 89 90    <!-- Define an AJP 1.3 Connector on port8009 --> 91    <Connectorport="8009" protocol="AJP/1.3" redirectPort="8443"/> 92 93 94    <!-- An Engine represents the entry point (withinCatalina) that processes 95         every request.  The Engine implementation for Tomcat standalone 96         analyzes the HTTP headers includedwith the request, and passes them 97         on to the appropriate Host(virtual host). 98         Documentation at/docs/config/engine.html --> 99100    <!-- You should set jvmRoute to supportload-balancing via AJP ie :101    <Enginename="Catalina" defaultHost="localhost"jvmRoute="jvm1">102    -->103    <Enginename="Catalina" defaultHost="localhost">104105      <!--For clustering, please take a look at documentationat:106          /docs/cluster-howto.html  (simple how to)107          /docs/config/cluster.html (referencedocumentation) -->108      <!--109      <ClusterclassName="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>110      -->111112      <!-- Use theLockOutRealm to prevent attempts to guess user passwords113           via a brute-force attack -->114      <RealmclassName="org.apache.catalina.realm.LockOutRealm">115        <!-- This Realm uses the UserDatabase configured inthe global JNDI116             resources under the key"UserDatabase".  Any edits117             that are performed against thisUserDatabase are immediately118             available for use bythe Realm.  -->119        <RealmclassName="org.apache.catalina.realm.UserDatabaseRealm"120               resourceName="UserDatabase"/>121      </Realm>122123      <Hostname="localhost"  appBase="webapps"124            unpackWARs="true" autoDeploy="true">125126        <!-- SingleSignOnvalve, share authentication between web applications127             Documentation at:/docs/config/valve.html -->128        <!--129        <ValveclassName="org.apache.catalina.authenticator.SingleSignOn" />130        -->131132        <!-- Access logprocesses all example.133             Documentation at:/docs/config/valve.html134             Note: The pattern usedis equivalent to using pattern="common" -->135        <ValveclassName="org.apache.catalina.valves.AccessLogValve" directory="logs"136               prefix="localhost_access_log." suffix=".txt"137               pattern="%h %l %u %t &quot;%r&quot; %s %b"/>138139      </Host>140    </Engine>141  </Service>142</Server>

这样经过对xml文件的解析将会产生org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等一系列对象,这些对象从前到后前一个包含后一个对象的引用(一对一或一对多的关系)。

 

解析完xml之后关闭文件流(第627行),接着设置StandardServer对象(该对象在上面解析xml时)的catalina的引用为当前对象,这种对象间的双向引用在Tomcat的很多地方都会碰到。

 

接下来将调用StandardServer对象的init方法。

 

上面分析的是Catalina的load方法,上一篇文章里看到Bootstrap类启动时还会调用Catalina对象的start方法,代码如下:

 1    /**2     * Start a newserver instance. 3     */ 4     publicvoid start() { 5 6         if (getServer() == null) { 7            load(); 8        } 910        if(getServer() ==null) {11            log.fatal("Cannot start server. Server instance is notconfigured.");12            return;13        }1415        long t1= System.nanoTime();1617        // Start the newserver18        try {19            getServer().start();20         }catch(LifecycleException e) {21            log.fatal(sm.getString("catalina.serverStartFail"), e);22            try {23                getServer().destroy();24             }catch (LifecycleException e1) {25                 log.debug("destroy()failed for failed Server ", e1);26            }27            return;28        }2930        long t2= System.nanoTime();31        if(log.isInfoEnabled()){32             log.info("Serverstartup in " + ((t2 - t1) / 1000000) + " ms");33        }3435        // Register shutdown hook36        if(useShutdownHook) {37            if (shutdownHook ==null) {38                 shutdownHook =new CatalinaShutdownHook();39            }40            Runtime.getRuntime().addShutdownHook(shutdownHook);4142            // If JULI is beingused, disable JULI's shutdown hook since43            // shutdown hooks run in parallel and log messages may belost44            // if JULI's hook completes before theCatalinaShutdownHook()45            LogManager logManager =LogManager.getLogManager();46            if (logManager instanceofClassLoaderLogManager) {47                ((ClassLoaderLogManager)logManager).setUseShutdownHook(48                        false);49            }50        }5152        if(await) {53            await();54            stop();55        }56     }

这里最主要的是调用StandardServer对象的start方法。

 

经过以上分析发现,在解析xml产生相应一系列对象后会顺序调用StandardServer对象的init、start方法,这里将会涉及到Tomcat的容器生命周期(Lifecycle),关于这点留到下一篇文章中分析。

Tomcat7启动分析(四)各组件init、start方法调用

在正常启动Tomcat7的情况下,上篇文章分析到了执行org.apache.catalina.core.StandardServer的init和start方法这儿,那么就来看看这两个方法里面到底干了些什么。

但是在StandardServer类里面并没有发现这两个方法:

由此推知这两方法必定是在该类的父类中已实现了,在StandardServer类的父类LifecycleMBeanBase类的父类LifecycleBase类里面终于找到了这两个方法的实现,下面先来看下init方法:

 1    @Override 2     publicfinalsynchronizedvoid init() throws LifecycleException { 3         if (!state.equals(LifecycleState.NEW)) { 4            invalidTransition(Lifecycle.BEFORE_INIT_EVENT); 5        } 6         setStateInternal(LifecycleState.INITIALIZING, null, false); 7 8         try { 9            initInternal();10         } catch (Throwable t) {11            ExceptionUtils.handleThrowable(t);12             setStateInternal(LifecycleState.FAILED, null, false);13             thrownew LifecycleException(14                     sm.getString("lifecycleBase.initFail",toString()), t);15        }1617         setStateInternal(LifecycleState.INITIALIZED, null, false);18    }19     20     21     protectedabstractvoid initInternal() throws LifecycleException;

先将干扰程序阅读视线的setStateInternal方法调用忽略掉(下一篇文章会详细讲解该方法),发现这里面就做了一件事情,调用了一下接下来定义的抽象方法initInternal()(第21行)。实际上看下LifecycleBase的实现类就会发现,所有的组件类几乎都继承了LifecycleBase类,所以这些组件类一般只会有initInternal方法的定义。(这里所说的组件类就是前面我们分析Digester解析时发现的StandardServer、StandardService、StandardEngine、StandardHost、StandardContext等类)

 

这里所说的组件可以将其理解为我们最开始分析server.xml时xml文件里的各个节点,父子关系也即xml文件里的父子节点。浏览下LifecycleBase的子类就会发现节点的实现类都是这个类的子类(记住这点,后面会提到)。

同样分析start方法:

 

 1     /** 2     * {@inheritDoc} 3      */ 4    @Override 5     publicfinalsynchronizedvoid start() throws LifecycleException { 6          7         if (LifecycleState.STARTING_PREP.equals(state) || 8                 LifecycleState.STARTING.equals(state) || 9                LifecycleState.STARTED.equals(state)) {10             11             if (log.isDebugEnabled()) {12                 Exception e = new LifecycleException();13                 log.debug(sm.getString("lifecycleBase.alreadyStarted",14                        toString()), e);15             } elseif (log.isInfoEnabled()) {16                 log.info(sm.getString("lifecycleBase.alreadyStarted",17                        toString()));18            }19             20             return;21        }22         23         if (state.equals(LifecycleState.NEW)) {24            init();25         } elseif (state.equals(LifecycleState.FAILED)){26            stop();27         } elseif (!state.equals(LifecycleState.INITIALIZED) &&28                 !state.equals(LifecycleState.STOPPED)) {29            invalidTransition(Lifecycle.BEFORE_START_EVENT);30        }3132         setStateInternal(LifecycleState.STARTING_PREP, null, false);3334         try {35            startInternal();36         } catch (Throwable t) {37            ExceptionUtils.handleThrowable(t);38             setStateInternal(LifecycleState.FAILED, null, false);39             thrownew LifecycleException(40                     sm.getString("lifecycleBase.startFail",toString()), t);41        }4243         if (state.equals(LifecycleState.FAILED) ||44                state.equals(LifecycleState.MUST_STOP)) {45            stop();46         } else {47             // Shouldn't be necessary but acts as a check that sub-classes are48             // doing what they are supposed to.49             if (!state.equals(LifecycleState.STARTING)) {50                invalidTransition(Lifecycle.AFTER_START_EVENT);51            }52             53             setStateInternal(LifecycleState.STARTED, null, false);54        }55    }565758     /**59     * Sub-classes must ensure that the state is changed to60     * {@link LifecycleState#STARTING} during the execution of this method.61     * Changing state will trigger the {@link Lifecycle#START_EVENT} event.62     * 63     * If a component fails to start it may either throw a64     * {@link LifecycleException} which will cause it's parent to fail to start65     * or it can place itself in the error state in which case {@link #stop()}66     * will be called on the failed component but the parent component will67     * continue to start normally.68     * 69     * @throws LifecycleException70      */71     protectedabstractvoid startInternal() throws LifecycleException;

第7到21行是start功能的前置校验,这里如果发现start方法已经调用过了,将会记录日志并直接返回。第23到30行如果发现start放的需要做的前置方法没有调用完,或者调用出错,将会先调用这些前置方法。第32行暂时先不管,不影响程序阅读,第35行是该方法的实质,将会调用本类中定义的抽象方法startInternal()(第71行)。下面的代码同上述一样,都是一些start方法调用过程中可能出现的错误的错误处理。


从以上init和start方法的定义可以看到这两个方法最终将会调用子类中定义的initInternal和startInternal。


接回本文开头,一开始在找StandardServer类中init和start方法的定义,看完了上面的分析发现最终会调用StandardServer类的initInternal和startInternal两个方法。那这两个方法里面干了些什么?

initInternal方法:

 1     /** 2     * Invoke a pre-startup initialization. This is used to allow connectors 3     * to bind to restricted ports under Unix operating environments. 4      */ 5    @Override 6     protectedvoid initInternal() throws LifecycleException { 7          8         super.initInternal(); 910         // Register global String cache11         // Note although the cache is global, if there are multiple Servers12         // present in the JVM (may happen when embedding) then the same cache13         // will be registered under multiple names14         onameStringCache = register(new StringCache(), "type=StringCache");1516         // Register the MBeanFactory17         MBeanFactory factory = new MBeanFactory();18         factory.setContainer(this);19         onameMBeanFactory = register(factory, "type=MBeanFactory");20         21         // Register the naming resources22        globalNamingResources.init();23         24         // Populate the extension validator with JARs from common and shared25         // class loaders26         if (getCatalina() != null) {27             ClassLoader cl = getCatalina().getParentClassLoader();28             // Walk the class loader hierarchy. Stop at the system class loader.29             // This will add the shared (if present) and common class loaders30             while (cl != null && cl != ClassLoader.getSystemClassLoader()) {31                 if (cl instanceof URLClassLoader) {32                     URL[] urls = ((URLClassLoader) cl).getURLs();33                     for (URL url : urls) {34                         if (url.getProtocol().equals("file")) {35                             try {36                                 File f = new File (url.toURI());37                                 if (f.isFile() &&38                                         f.getName().endsWith(".jar")) {39                                    ExtensionValidator.addSystemResource(f);40                                }41                             } catch (URISyntaxException e) {42                                 // Ignore43                             } catch (IOException e) {44                                 // Ignore45                            }46                        }47                    }48                }49                 cl = cl.getParent();50            }51        }52         // Initialize our defined Services53         for (int i = 0; i < services.length; i++) {54            services[i].init();55        }56     }

init方法里面做了好几件事情,牵涉的话题比较多,这里重点关注最后第53到55行的代码,这里将循环调用Server类里内置的Service数组的init方法。

 

startInternal方法:

 1     /** 2     * Start nested components ({@link Service}s) and implement the requirements 3     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. 4     * 5     * @exception LifecycleException if this component detects a fatal error 6     *  that prevents this component from being used 7      */ 8    @Override 9     protectedvoid startInternal() throws LifecycleException {1011         fireLifecycleEvent(CONFIGURE_START_EVENT, null);12        setState(LifecycleState.STARTING);1314        globalNamingResources.start();15         16         // Start our defined Services17         synchronized (services) {18             for (int i = 0; i < services.length; i++) {19                services[i].start();20            }21        }22     }

重点关注第17到21行,同上一段所分析的代码类似,将循环调用Sever类里内置的Service数组的start方法。

 

那么这里提到的Service的对象到底是什么?

上篇文章分析Digester时提到“经过对xml文件的解析将会产生org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等一系列对象,这些对象从前到后前一个包含后一个对象的引用(一对一或一对多的关系)。”

所以正常情况下这里的Service将会是org.apache.catalina.core.StandardService的对象(该代码见org.apache.catalina.startup.Catalina类的339到341行)。

 

所以按上面的分析,接下来将会调用StandardService类的init和start方法,实际上这个类也是LifecycleBase类的子类,所以最终的也会调用本类中的initInternal和startInternal方法。

initInternal方法:

 1     /** 2     * Invoke a pre-startup initialization. This is used to allow connectors 3     * to bind to restricted ports under Unix operating environments. 4      */ 5    @Override 6     protectedvoid initInternal() throws LifecycleException { 7 8         super.initInternal(); 9         10         if (container != null) {11            container.init();12        }1314         // Initialize any Executors15         for (Executor executor : findExecutors()) {16             if (executor instanceof LifecycleMBeanBase) {17                ((LifecycleMBeanBase) executor).setDomain(getDomain());18            }19            executor.init();20        }2122         // Initialize our defined Connectors23         synchronized (connectors) {24             for (Connector connector : connectors) {25                 try {26                    connector.init();27                 } catch (Exception e) {28                     String message = sm.getString(29                             "standardService.connector.initFailed", connector);30                    log.error(message, e);3132                     if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))33                         thrownew LifecycleException(message);34                }35            }36        }37     }

这里将会调用Service下的各类子组件中的init方法。

 

startInternal方法:

 1     /** 2     * Start nested components ({@link Executor}s, {@link Connector}s and 3     * {@link Container}s) and implement the requirements of 4     * {@link org.apache.catalina.util.LifecycleBase#startInternal()}. 5     * 6     * @exception LifecycleException if this component detects a fatal error 7     *  that prevents this component from being used 8      */ 9    @Override10     protectedvoid startInternal() throws LifecycleException {1112         if(log.isInfoEnabled())13             log.info(sm.getString("standardService.start.name", this.name));14        setState(LifecycleState.STARTING);1516         // Start our defined Container first17         if (container != null) {18             synchronized (container) {19                container.start();20            }21        }2223         synchronized (executors) {24             for (Executor executor: executors) {25                executor.start();26            }27        }2829         // Start our defined Connectors second30         synchronized (connectors) {31             for (Connector connector: connectors) {32                 try {33                     // If it has already failed, don't try and start it34                     if (connector.getState() != LifecycleState.FAILED) {35                        connector.start();36                    }37                 } catch (Exception e) {38                    log.error(sm.getString(39                             "standardService.connector.startFailed",40                            connector), e);41                }42            }43        }44     }

同理,将会调用service下各类子组件中的start方法。

 

这里不再继续向下分析,建议有兴趣的同学逐一分析Review一遍,碰到组件里面嵌套的变量不知道具体实现类的就从上篇文章里面提到的createStartDigester那边开始找起,这里不能直接找到的就在里面提到的new*RuleSet的addRuleInstances方法里面找。通过这种调用将会最终执行完所有在server.xml里配置的节点的实现类中initInternal和startInternal方法。

最后上面提到的org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext等等组件的这两个方法都会调用到。

 

就这样,Tomcat7在内存中为这一连串组件产生对象,建立对象调用关系,调用它们各自的初始化和启动方法,启动的总体过程就介绍完了,这些对象start之后将会响应客户端的请求,为用户服务了。当然,这里还没有涉及到对于具体的发布到tomcat里面的没有应用的载入过程,web应用中配置的servlet、filter、listener等的载入、启动服务过程,浏览器发起的一个请求如何经过Tomcat内各组件的流转调用到具体应用里去的,这一系列问题都还没谈到。因为Tomcat本身庞大繁杂,需要找一个视角切入进去,为了叙述的简单,先从整体上对Tomcat内包含的各组件产生机制有一个大体轮廓的了解,这样为后面的介绍提供一个统一的背景。

 

下一篇文章将分析本文开头遗留的一个问题——setStateInternal方法的作用,以及Tomcat中的Lifecycle实现原理。

Tomcat7启动分析(五)Lifecycle机制和实现原理

在上篇文章分析Tomcat7的各组件的init、start方法时经常会看到有一个setStateInternal方法的调用,在查看LifecycleBase类及其它各组件的源码时会在多处看到这个方法的调用,这篇文章就来说说这方法,以及与这个方法相关的Tomcat的Lifecycle机制和实现原理。

 

上篇文章里谈到Tomcat7的各组件的父类LifecycleBase类,该类实现了接口org.apache.catalina.Lifecycle,下面是这个接口里定义的常量和方法:


细心的读者会发现,上篇文章里提到的init和start方法实际上是在这个接口里面定义好的,也正因为有各组件最终都会实现这个接口作为前提条件,所以才能支持组件内部的initInternal、startInternal方法内对于子组件(组件里面嵌套的子组件都是以接口的形式定义的,但这些接口都会以Lifecycle作为父接口)的init和start方法的调用。通过这种方式,只要调用了最外层的Server组件的init和start方法,就可以将Tomcat内部的各级子组件初始化和启动起来。我叫这种方式为链式调用。实际上关于Tomcat的关闭机制也是通过这种方式一步步调用各层组件的stop方法的。这里不再展开叙述,留待读者自己研究研究吧。

 

Lifecycle接口中的这些字符串常量定义主要用于事件类型的定义,先按下不表,文章后面会提到。

 

重点看下面三个方法:

 1     /** 2     * Add a LifecycleEvent listener to this component. 3     * 4     * @param listener The listener to add 5      */ 6     publicvoid addLifecycleListener(LifecycleListener listener);//给该组将添加一个监听器 7 8 9     /**10     * Get the life cycle listeners associated with this life cycle. If this11     * component has no listeners registered, a zero-length array is returned.12      */13     public LifecycleListener[] findLifecycleListeners();//获取该组件所有已注册的监听器141516     /**17     * Remove a LifecycleEvent listener from this component.18     *19     * @param listener The listener to remove20      */21     publicvoid removeLifecycleListener(LifecycleListener listener);//删除该组件中的一个监听器

这三个方法的作用在代码的注释里简要说明了一下。这三个方法涉及org.apache.catalina.LifecycleListener接口,那么就看下这个接口的定义:

 1publicinterface LifecycleListener { 2 3 4     /** 5     * Acknowledge the occurrence of the specified event. 6     * 7     * @param event LifecycleEvent that has occurred 8      */ 9     publicvoid lifecycleEvent(LifecycleEvent event);101112 }

如此简单,只有一个方法,这个方法用作某个事件(org.apache.catalina.LifecycleEvent)产生时通知当前监听器的实现类,具体针对该事件如何处理由监听器实现类自己决定。

 

看下LifecycleEvent的实现:

 1publicfinalclass LifecycleEvent extends EventObject { 2 3     privatestaticfinallong serialVersionUID = 1L; 4 5 6     // ----------------------------------------------------------- Constructors 7 8     /** 9     * Construct a new LifecycleEvent with the specified parameters.10     *11     * @param lifecycle Component on which this event occurred12     * @param type Event type (required)13     * @param data Event data (if any)14      */15     public LifecycleEvent(Lifecycle lifecycle, String type, Object data) {1617         super(lifecycle);18         this.type = type;19         this.data = data;20    }212223     // ----------------------------------------------------- Instance Variables242526     /**27     * The event data associated with this event.28      */29     private Object data = null;303132     /**33     * The event type this instance represents.34      */35     private String type = null;363738     // ------------------------------------------------------------- Properties394041     /**42     * Return the event data of this event.43      */44     public Object getData() {4546         return (this.data);4748    }495051     /**52     * Return the Lifecycle on which this event occurred.53      */54     public Lifecycle getLifecycle() {5556         return (Lifecycle) getSource();5758    }596061     /**62     * Return the event type of this event.63      */64     public String getType() {6566         return (this.type);6768    }697071 }

这个类也很简单,data和type作为类的内置实例变量,唯一特别是使用了jdk内置的java.util.EventObject作为父类来支持事件定义,这里在事件构造函数中将org.apache.catalina.Lifecycle类的实例lifecycle作为事件源,保存lifecycle对象的引用,并提供了getLifecycle方法返回这个引用。

 

那么Tomcat中是如何实现关于这些事件的监听以及通知的呢?

 

在本文开头提到的LifecycleBase类中第47行定义了一个实例变量lifecycle,正是通过该变量来注册组件上定义的各类监听器的。留心一下lifecycle这个实例变量,它并不是org.apache.catalina.Lifecycle类的实例,而是org.apache.catalina.util.LifecycleSupport类的实例。正是这个工具类提供了事件监听和事件通知的功能。

 

先看下实际代码中是如何给组件发布时间通知的,看下前面文章中曾经提到过的org.apache.catalina.core.StandardServer类的startInternal方法:

 1     protectedvoid startInternal() throws LifecycleException { 2 3         fireLifecycleEvent(CONFIGURE_START_EVENT, null); 4        setState(LifecycleState.STARTING); 5 6        globalNamingResources.start(); 7          8         // Start our defined Services 9         synchronized (services) {10             for (int i = 0; i < services.length; i++) {11                services[i].start();12            }13        }14     }

我们前面已经分析过第9到13行代码,这里看下第3行,它调用了父类org.apache.catalina.util.LifecycleBase里的fireLifecycleEvent方法,这里的CONFIGURE_START_EVENT就是本文最开始Lifecycle接口中定义的常量,这里表示发布了一个start配置事件。

 

org.apache.catalina.util.LifecycleBase类中的fireLifecycleEvent方法里调用的是org.apache.catalina.util.LifecycleSupport类fireLifecycleEvent方法,该方法代码如下:

1     publicvoid fireLifecycleEvent(String type, Object data) {23         LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);4         LifecycleListener interested[] = listeners;5         for (int i = 0; i < interested.length; i++)6            interested[i].lifecycleEvent(event);78     }

这里通过传进来的两个参数构造一个LifecycleEvent对象,然后向注册到组件中的所有监听器发布这个新构造的事件对象。

 

这里有个疑问,到底什么时候向组件里注册监听器的呢?

 

还是以StandardServer举例,在前面讲Digester的使用时,org.apache.catalina.startup.Catalina类的createStartDigester方法有这么一段代码:

 1         // Configure the actions we will be using 2         digester.addObjectCreate("Server", 3                                  "org.apache.catalina.core.StandardServer", 4                                  "className"); 5         digester.addSetProperties("Server"); 6         digester.addSetNext("Server", 7                             "setServer", 8                             "org.apache.catalina.Server"); 910         digester.addObjectCreate("Server/GlobalNamingResources",11                                  "org.apache.catalina.deploy.NamingResources");12         digester.addSetProperties("Server/GlobalNamingResources");13         digester.addSetNext("Server/GlobalNamingResources",14                             "setGlobalNamingResources",15                             "org.apache.catalina.deploy.NamingResources");1617         digester.addObjectCreate("Server/Listener",18                                  null, // MUST be specified in the element19                                  "className");20         digester.addSetProperties("Server/Listener");21         digester.addSetNext("Server/Listener",22                             "addLifecycleListener",23                             "org.apache.catalina.LifecycleListener");

第17到24行,将调用org.apache.catalina.core.StandardServer类的addLifecycleListener方法,将根据server.xml中配置的Server节点下的Listener节点所定义的className属性构造对象实例,并作为addLifecycleListener方法的入参。所有的监听器都会实现上面提到的org.apache.catalina.LifecycleListener接口。Server节点下的Listener节点有好几个,这里以org.apache.catalina.core.JasperListener举例。

 

在构造完org.apache.catalina.core.JasperListener类的对象之后,调用addLifecycleListener方法,这个方法并没有直接在org.apache.catalina.core.StandardServer类中定义,而是在它的父类org.apache.catalina.util.LifecycleBase中:

1    @Override2     publicvoid addLifecycleListener(LifecycleListener listener) {3        lifecycle.addLifecycleListener(listener);4     }

这里调用的是前述的org.apache.catalina.util.LifecycleSupport类的addLifecycleListener方法:

 1     /** 2     * Add a lifecycle event listener to this component. 3     * 4     * @param listener The listener to add 5      */ 6     publicvoid addLifecycleListener(LifecycleListener listener) { 7 8       synchronized (listenersLock) { 9           LifecycleListener results[] =10             new LifecycleListener[listeners.length + 1];11           for (int i = 0; i < listeners.length; i++)12               results[i] = listeners[i];13           results[listeners.length] = listener;14           listeners = results;15      }1617     }

LifecycleSupport作为一个工具类,内部保存了一个监听器对象实例数组,见该类的第68行:

1     /**2     * The set of registered LifecycleListeners for event notifications.3      */4     private LifecycleListener listeners[] = new LifecycleListener[0];

上面的addLifecycleListener方法内部实现的是同步给该数组增加一个监听器对象。

 

看到这里应该大体明白Tomcat中的Lifecycle是怎么回事了,总的来说就是通过一个工具类LifecycleSupport,调用该类的addLifecycleListener方法增加监听器,需要发布事件时还是调用该工具类的fireLifecycleEvent方法,将事件发布给组件上注册的所有监听器,由监听器内部实现来决定是否处理该事件。

 

以前面看到的一个监听器org.apache.catalina.core.JasperListener举例: 

 1publicclass JasperListener 2     implements LifecycleListener { 3 4     privatestaticfinal Log log = LogFactory.getLog(JasperListener.class); 5 6     /** 7     * The string manager for this package. 8      */ 9     protectedstaticfinal StringManager sm =10        StringManager.getManager(Constants.Package);111213     // ---------------------------------------------- LifecycleListener Methods141516     /**17     * Primary entry point for startup and shutdown events.18     *19     * @param event The event that has occurred20      */21    @Override22     publicvoid lifecycleEvent(LifecycleEvent event) {2324         if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {25             try {26                 // Set JSP factory27                 Class.forName("org.apache.jasper.compiler.JspRuntimeContext",28                               true,29                               this.getClass().getClassLoader());30             } catch (Throwable t) {31                ExceptionUtils.handleThrowable(t);32                 // Should not occur, obviously33                 log.warn("Couldn't initialize Jasper", t);34            }35             // Another possibility is to do directly:36             // JspFactory.setDefaultFactory(new JspFactoryImpl());37        }3839    }404142 }

重点关注来自接口的lifecycleEvent方法的实现,可以看到这个监听器只关心事件类型为BEFORE_INIT_EVENT的事件,如果发布了该事件,才会做后续处理(这里会产生一个org.apache.jasper.compiler.JspRuntimeContext对象)。

 

Lifecycle相关类UML关系图:

如果对设计模式比较熟悉的话会发现Tomcat的Lifecycle使用的是观察者模式:LifecycleListener代表的是抽象观察者,它定义一个lifecycleEvent方法,而实现该接口的监听器是作为具体的观察者。Lifecycle 接口代表的是抽象主题,它定义了管理观察者的方法和它要所做的其它方法。而各组件代表的是具体主题,它实现了抽象主题的所有方法。通常会由具体主题保存对具体观察者对象有用的内部状态;在这种内部状态改变时给其观察者发出一个通知。Tomcat对这种模式做了改进,增加了另外两个工具类:LifecycleSupport、LifecycleEvent,它们作为辅助类扩展了观察者的功能。LifecycleEvent中定义了事件类别,不同的事件在具体观察者中可区别处理,更加灵活。LifecycleSupport 类代理了所有具体主题对观察者的管理,将这个管理抽出来统一实现,以后如果修改只要修改 LifecycleSupport 类就可以了,不需要去修改所有具体主题,因为所有具体主题的对观察者的操作都被代理给 LifecycleSupport 类了。

 

事件的发布使用的是推模式,即每发布一个事件都会通知主题的所有具体观察者,由各观察者再来决定是否需要对该事件进行后续处理。

 

下面再来看看本文一开头所说的setStateInternal方法,以org.apache.catalina.core.StandardServer类为例,上面看到的startInternal方法中第4行:setState(LifecycleState.STARTING);

它调用了父类org.apache.catalina.util.LifecycleBase中的setState方法:

 1     /** 2     * Provides a mechanism for sub-classes to update the component state. 3     * Calling this method will automatically fire any associated 4     * {@link Lifecycle} event. It will also check that any attempted state 5     * transition is valid for a sub-class. 6     *  7     * @param state The new state for this component 8      */ 9     protectedsynchronizedvoid setState(LifecycleState state)10             throws LifecycleException {11         setStateInternal(state, null, true);12     }

在这个类里面调用本类的一个同步方法setStateInternal

 1     privatesynchronizedvoid setStateInternal(LifecycleState state, 2             Object data, boolean check) throws LifecycleException { 3          4         if (log.isDebugEnabled()) { 5             log.debug(sm.getString("lifecycleBase.setState", this, state)); 6        } 7          8         if (check) { 9             // Must have been triggered by one of the abstract methods (assume10             // code in this class is correct)11             // null is never a valid state12             if (state == null) {13                 invalidTransition("null");14                 // Unreachable code - here to stop eclipse complaining about15                 // a possible NPE further down the method16                 return;17            }18             19             // Any method can transition to failed20             // startInternal() permits STARTING_PREP to STARTING21             // stopInternal() permits STOPPING_PREP to STOPPING and FAILED to22             // STOPPING23             if (!(state == LifecycleState.FAILED ||24                     (this.state == LifecycleState.STARTING_PREP &&25                             state == LifecycleState.STARTING) ||26                     (this.state == LifecycleState.STOPPING_PREP &&27                             state == LifecycleState.STOPPING) ||28                     (this.state == LifecycleState.FAILED &&29                             state == LifecycleState.STOPPING))) {30                 // No other transition permitted31                invalidTransition(state.name());32            }33        }34         35         this.state = state;36         String lifecycleEvent = state.getLifecycleEvent();37         if (lifecycleEvent != null) {38            fireLifecycleEvent(lifecycleEvent, data);39        }40     }

重点关注第35到39行,第35行将入参LifecycleState实例赋值给本类中的实例变量保存起来,第36行取出LifecycleState实例的LifecycleEvent事件,如果该事件非空,则调用fireLifecycleEvent方法发布该事件。

 

既然看到了LifecycleState类,就看下LifecycleState类的定义: 

 1publicenum LifecycleState { 2     NEW(false, null), 3     INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT), 4     INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT), 5     STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT), 6     STARTING(true, Lifecycle.START_EVENT), 7     STARTED(true, Lifecycle.AFTER_START_EVENT), 8     STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT), 9     STOPPING(false, Lifecycle.STOP_EVENT),10     STOPPED(false, Lifecycle.AFTER_STOP_EVENT),11     DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),12     DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),13     FAILED(false, null),14     MUST_STOP(true, null),15     MUST_DESTROY(false, null);1617     privatefinalboolean available;18     privatefinal String lifecycleEvent;1920     private LifecycleState(boolean available, String lifecycleEvent) {21         this.available = available;22         this.lifecycleEvent = lifecycleEvent;23    }2425     /**26     * May the public methods other than property getters/setters and lifecycle27     * methods be called for a component in this state? It returns28     * <code>true</code> for any component in any of the following states:29     * <ul>30     * <li>{@link #STARTING}</li>31     * <li>{@link #STARTED}</li>32     * <li>{@link #STOPPING_PREP}</li>33     * <li>{@link #MUST_STOP}</li>34     * </ul>35      */36     publicboolean isAvailable() {37         return available;38    }3940     /**41     *42      */43     public String getLifecycleEvent() {44         return lifecycleEvent;45    }46 }

这个类在之前的Tomcat4和Tomcat5中都没有看到,可能是Tomcat7里面新定义的吧,就是一个枚举,内嵌了两个实例变量,一个布尔值表示是否可用,一个字符串表示是事件类型,看已经定义的枚举值里面发现这个字符串要么不设值,要么就是Lifecycle类中定义好的字符串常量。这个类实际上就是对Lifecycle类中定义好的字符串常量做了另外一层封装。

 

再说回开头在各组件代码中经常会看到的setStateInternal方法的调用,实际上就是向该组件中已注册的监听器发布一个事件。

 


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值