解析Tomcat的启动脚本--catalina.bat
概述
Tomcat的三个最重要的启动脚本:
- startup.bat
- catalina.bat
- setclasspath.bat
上一篇咱们分析了startup.bat脚本
这一篇咱们来分析catalina.bat脚本.
至于setclasspath.bat这个脚本,相信看完这一篇,就可以自己看懂这个脚本了.
可以点击下载[setclasspath.bat脚本]查看附注释的setclasspath.bat脚本
catalina.bat
这个脚本的代码有点多,就单独弄了一篇展示catalina.bat脚本中的内容.点击[catalina.bat脚本]下载查看.
下面咱们就按照脚本中的内容一行行的来分析.
@echooff setlocal
这两个命令可以参考上一篇的文章(解析Tomcat的启动脚本--startup.bat)的解释
第一块脚本代码
remSuppressTerminatebatchjobonCTRL+C ifnot""%1""==""run""gotomainEntry if"%TEMP%"==""gotomainEntry ifexist"%TEMP%\%~nx0.run"gotomainEntry echoY>"%TEMP%\%~nx0.run" ifnotexist"%TEMP%\%~nx0.run"gotomainEntry echoY>"%TEMP%\%~nx0.Y" call"%~f0"%*<"%TEMP%\%~nx0.Y" remUseprovidederrorlevel setRETVAL=%ERRORLEVEL% del/Q"%TEMP%\%~nx0.Y">NUL2>&1 exit/B%RETVAL% :mainEntry del/Q"%TEMP%\%~nx0.run">NUL2>&1
脚本的作用
判断用户是否使用
catalina.batrun
来启动Tomcat的.
如果用户使用startup.bat脚本启动Tomcat,那么这段脚本不会被执行.
这段代码看起来很乱,慢慢分析.
第一行:
注释,意思就是:禁止使用CTRL+C来终止批处理任务,也不知道是怎么禁止的.
第二行:
ifnot""%1""==""run""gotomainEntry
首先明白这里的这个"%1"这个变量代表的是什么?正常情况下,这个脚本是被startup.bat脚本调用的,被调用的同时传递了一个start参数过来(上一篇分析得出的).在批处理命令中%1就表示命令之后的第一个参数,在这里指的就是start.所以"%1"=start.如果用户用catalina.batrun命令启动Tomcat的话,那么这里的"%1"=run.
第三行:
if"%TEMP%"==""gotomainEntry
这里的%TEMP%很有可能被认为是空,其实这里可以读取到系统的环境变量.所以,这里的%TEMP%就是系统的环境变量值,通常装完windows系统的话,系统会自动配置上这个环境变量.所以这里一般是有值的.大家可以去系统的环境变量看一下它指向那个目录,一般就是C:\Users\用户名\AppData\Local\Temp.注意:AppData是一个隐藏目录.
第四行:
ifexist"%TEMP%\%~nx0.run"gotomainEntry
这里又出现了一个新的东西%~nx0.在批处理中,我们知道%1表示的是程序之后的第一个参数,那么%0呢?%0表示这个可执行程序的名称,%~nx0的话就是程序的名称+扩展名
在这里就是catalina.bat.大家可以写一个小脚本(test.bat)验证一下:(我的脚本放在D盘下)
脚本内容:
@echooff echo"%~nx0" echo"%1"
执行结果:
PSD:\>.\test.batHello "test.bat" "Hello" PSD:\>
第五行:
echoY>"%TEMP%\%~nx0.run"
这段代码很简单,就是写入字符Y到%TEMP%\catalina.bat.run文件中.
第六行:
ifnotexist"%TEMP%\%~nx0.run"gotomainEntry
又判断了一下%TEMP%\catalina.bat.run文件是否存在.
第七行:
echoY>"%TEMP%\%~nx0.Y"
同第五行,写入Y到%TEMP%\catalina.bat.Y.如果文件不存在,则新建一个.
第八行:
call"%~f0"%*<"%TEMP%\%~nx0.Y"
这一行有点意思.又出现了两个新的东西:
(因为markdown语法限制,把下面代码写到代码块里)
-"%~f0":简单说就是表示当前命令的绝对路径.
-"%*":我们知道%1表示第一个参数,依次类推,%2表示第二个....那么%*就很好理解了,代表所有参数.
验证一下
脚本内容:
@echooff echo"%*" echo"%~f0"
执行结果:
PSD:\>.\test.batHelloWorld "HelloWorld" "D:\test.bat" PSD:\>
那么后面的<"%TEMP%\%~nx0.Y"意思就是读取%TEMP%\catalina.bat.Y文件中的内容.
之后又通过call进行调用.
我们自己写一个例子,在D盘建立test.bat文件,再建立catalina.bat.Y文件
脚本内容:
call"%~f0"%*<D:/catalina.bat.Y
catalina.bat.Y文件内容
Y
执行结果:
........ D:\>call"D:\test.bat"HelloWorld0<D:/catalina.bat.Y D:\>call"D:\test.bat"HelloWorld0<D:/catalina.bat.Y D:\>call"D:\test.bat"HelloWorld0<D:/catalina.bat.Y D:\>call"D:\test.bat"HelloWorld0<D:/catalina.bat.Y D:\>call"D:\test.bat"HelloWorld0<D:/catalina.bat.Y D:\>call"D:\test.bat"HelloWorld0<D:/catalina.bat.Y D:\>call"D:\test.bat"HelloWorld0<D:/catalina.bat.Y ******BATCHRECURSIONexceedsSTACKlimits****** RecursionCount=593,StackUsage=90percent ******BATCHPROCESSINGISABORTED******
最上面省略了很多重复代码,从这里发现它不断地调用自己本身,直到超出了堆栈的限制才停止.
我们如果加上@echooff的话
@echooff call"%~f0"%*<D:/catalina.bat.Y
结果只会出现
D:\>.\test.batHelloWorld ******BATCHRECURSIONexceedsSTACKlimits****** RecursionCount=593,StackUsage=90percent ******BATCHPROCESSINGISABORTED******
我们这里只需要明白这些命令的作用就可以,稍后我们会总结Tomcat执行这些命令的目的.
第十行:
setRETVAL=%ERRORLEVEL%
我们如果了解Linux的话都知道,每个命令的执行都会返回一个执行完成之后的退出码.Linux执行完一条命令之后用echo$?来查看上一条命令的退出码.在Windows中也是一样的,命令执行完之后都有自己的退出码.这里的%ERRORLEVEL%就是取的上面的call命令的退出码.赋值给一个变量RETVAL
第十一行:
del/Q"%TEMP%\%~nx0.Y">NUL2>&1
这里又出现了一个del命令,很容易联想到delete,那么/Q是什么意思呢?静默删除,不会给你任何提示,就比如Linux中的rm-f一样,这里是删除%TEMP%\catalina.bat.Y这个文件.
后面的>NUL2>&1又是什么意思呢?
于Linux中的输出流的重定向原理是一样的.
(因为markdown语法限制,把下面代码写到代码块里)
->NUL:表示将输出重定向到NUL中,你什么也看不到 -2>&1:2:错误输出,&1:标准输出,意思就是将错误消息输出到标准输出中. ->NUL2>&1:就是先将错误消息输出到标准输出中,然后再输出到NUL中.
第十二行:
exit/B%RETVAL%
退出当前批处理,/B指定退出时的编号,把RETVAL最为退出码,也就是call执行的命令的退出码.
最后两行:
:mainEntry del/Q"%TEMP%\%~nx0.run">NUL2>&1
定义一个mainEntry标签,然后删除临时目录中的catalina.bat.run文件.
总结第一段脚本的功能
简单说,这段代码的作用就是调用本身,判断临时目录中的文件是否存在来避免二次回调自己.感觉写的好复杂.
下面就进入Tomcat的正式启动过程,并没有开始执行main方法
第二段脚本代码
remGuessCATALINA_HOMEifnotdefined set"CURRENT_DIR=%cd%" ifnot"%CATALINA_HOME%"==""gotogotHome set"CATALINA_HOME=%CURRENT_DIR%" ifexist"%CATALINA_HOME%\bin\catalina.bat"gotookHome cd.. set"CATALINA_HOME=%cd%" cd"%CURRENT_DIR%" :gotHome ifexist"%CATALINA_HOME%\bin\catalina.bat"gotookHome echoTheCATALINA_HOMEenvironmentvariableisnotdefinedcorrectly echoThisenvironmentvariableisneededtorunthisprogram gotoend :okHome remCopyCATALINA_BASEfromCATALINA_HOMEifnotdefined ifnot"%CATALINA_BASE%"==""gotogotBase set"CATALINA_BASE=%CATALINA_HOME%" :gotBase
这段脚本还是比较简单的,主要是设置了两个环境变量CATALINA_HOME和CATALINA_BASE.
如果没有配置CATALINA_BASE环境变量的话,直接引用CATALINA_HOME的值
静下心来稍微看一下就懂了.
第三段脚本代码
remEnsurethatneitherCATALINA_HOMEnorCATALINA_BASEcontainsasemi-colon remasthisisusedastheseparatorintheclasspathandJavaprovidesno remmechanismforescapingifthesamecharacterappearsinthepath.Checkthis rembyreplacingalloccurrencesof';'with''andcheckingthatneither remCATALINA_HOMEnorCATALINA_BASEhavechanged if"%CATALINA_HOME%"=="%CATALINA_HOME:;=%"gotohomeNoSemicolon echoUsingCATALINA_HOME:"%CATALINA_HOME%" echoUnabletostartasCATALINA_HOMEcontainsasemicolon(;)character gotoend :homeNoSemicolon if"%CATALINA_BASE%"=="%CATALINA_BASE:;=%"gotobaseNoSemicolon echoUsingCATALINA_BASE:"%CATALINA_BASE%" echoUnabletostartasCATALINA_BASEcontainsasemicolon(;)character gotoend :baseNoSemicolon
这里主要是判断CATALINA_HOME环境变量的值和CATALINA_BASE环境变量的值是否以分号为结尾,如果以分号为结尾的话,就报错退出.
第四段脚本代码
remEnsurethatanyuserdefinedCLASSPATHvariablesarenotusedonstartup, rembutallowthemtobespecifiedinsetenv.bat,inrarecasewhenitisneeded. setCLASSPATH= remGetstandardenvironmentvariables ifnotexist"%CATALINA_BASE%\bin\setenv.bat"gotocheckSetenvHome call"%CATALINA_BASE%\bin\setenv.bat" gotosetenvDone :checkSetenvHome ifexist"%CATALINA_HOME%\bin\setenv.bat"call"%CATALINA_HOME%\bin\setenv.bat" :setenvDone remGetstandardJavaenvironmentvariables ifexist"%CATALINA_HOME%\bin\setclasspath.bat"gotookSetclasspath echoCannotfind"%CATALINA_HOME%\bin\setclasspath.bat" echoThisfileisneededtorunthisprogram gotoend :okSetclasspath call"%CATALINA_HOME%\bin\setclasspath.bat"%1 iferrorlevel1gotoend
设置一个临时环境变量:CLASSPATH.
如果Tomcat的bin目录下面存在setnv.bat脚本的话,就执行它.通常情况下是没有的.
继而又判断setclasspath.bat脚本是否存在,如果不存在的话,直接报错,停止启动Tomcat.
如果存在的话,就去调用它,并把第一个参数传进去.
setclasspath.bat这个脚本主要设置了几个环境变量
- JAVA_HOME
- JRE_HOME
- JAVA_ENDORSED_DIRS=%CATALINA_HOME%\endorsed
- _RUNJAVA=%JRE_HOME%\bin\java.exe
- _RUNJDB=%JAVA_HOME%\bin\jdb.exe
第五段脚本代码
remAddonextrajarfiletoCLASSPATH remNotethattherearenoquotesaswedonotwanttointroducerandom remquotesintotheCLASSPATH if"%CLASSPATH%"==""gotoemptyClasspath set"CLASSPATH=%CLASSPATH%;" :emptyClasspath set"CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar" ifnot"%CATALINA_TMPDIR%"==""gotogotTmpdir set"CATALINA_TMPDIR=%CATALINA_BASE%\temp" :gotTmpdir remAddtomcat-juli.jartoclasspath remtomcat-juli.jarcanbeover-riddenperinstance ifnotexist"%CATALINA_BASE%\bin\tomcat-juli.jar"gotojuliClasspathHome set"CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar" gotojuliClasspathDone :juliClasspathHome set"CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\tomcat-juli.jar" :juliClasspathDone
这段代码主要做了三件事:
- 把Tomcatbin目录下的bootstrap.jar加入到环境变量中
- 设置CATALINA_TMPDIR环境变量的值为Tomcat目录下的temp目录
- 把Tomcatbin目录下的tomcat-juli.jar加入到环境变量中
第六段脚本代码
ifnot"%JSSE_OPTS%"==""gotogotJsseOpts setJSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048" :gotJsseOpts set"JAVA_OPTS=%JAVA_OPTS%%JSSE_OPTS%" remRegistercustomURLhandlers remDothisheresocustomURLhandles(specifically'war:...')canbeusedinthesecuritypolicy set"JAVA_OPTS=%JAVA_OPTS%-Djava.protocol.handler.pkgs=org.apache.catalina.webresources" ifnot"%LOGGING_CONFIG%"==""gotonoJuliConfig setLOGGING_CONFIG=-Dnop ifnotexist"%CATALINA_BASE%\conf\logging.properties"gotonoJuliConfig setLOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties" :noJuliConfig set"JAVA_OPTS=%JAVA_OPTS%%LOGGING_CONFIG%" ifnot"%LOGGING_MANAGER%"==""gotonoJuliManager setLOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager :noJuliManager set"JAVA_OPTS=%JAVA_OPTS%%LOGGING_MANAGER%"
主要是追加一系列的启动参数到JAVA_OPTS这个环境变量中.
第八段脚本代码
echoUsingCATALINA_BASE:"%CATALINA_BASE%" echoUsingCATALINA_HOME:"%CATALINA_HOME%" echoUsingCATALINA_TMPDIR:"%CATALINA_TMPDIR%" if""%1""==""debug""gotouse_jdk echoUsingJRE_HOME:"%JRE_HOME%" gotojava_dir_displayed :use_jdk echoUsingJAVA_HOME:"%JAVA_HOME%" :java_dir_displayed echoUsingCLASSPATH:"%CLASSPATH%"
主要是打印相关的环境变量信息.
第九段脚本代码
set_EXECJAVA=%_RUNJAVA% setMAINCLASS=org.apache.catalina.startup.Bootstrap setACTION=start setSECURITY_POLICY_FILE= setDEBUG_OPTS= setJPDA=
设置一些列的环境变量:
- _RUNJAVA:%JRE_HOME%\bin\java.exe
- MAINCLASS:指定了Tomcat的启动类,没错main方法就是在这个类里面.
- ACTION:动作:就是启动
- SECURITY_POLICY_FILE:安全策略文件,如果启动的时候加上了-security参数的话,下面会对这个参数指定到Tomcat的conf目录下的catalina.policy文件.
- JPDA:这个参数可以百度一下,我们平时几乎用不到.
第十段代码
ifnot""%1""==""jpda""gotonoJpda setJPDA=jpda ifnot"%JPDA_TRANSPORT%"==""gotogotJpdaTransport setJPDA_TRANSPORT=dt_socket :gotJpdaTransport ifnot"%JPDA_ADDRESS%"==""gotogotJpdaAddress setJPDA_ADDRESS=localhost:8000 :gotJpdaAddress ifnot"%JPDA_SUSPEND%"==""gotogotJpdaSuspend setJPDA_SUSPEND=n :gotJpdaSuspend ifnot"%JPDA_OPTS%"==""gotogotJpdaOpts setJPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND% :gotJpdaOpts shift :noJpda
好像直接从第一行跳到了最后一行,没错,一般我没启动的时候没有加jpda参数的话,这里会直接跳过,里面的脚本是关于JPDA的设置等.
第十一段脚本代码
if""%1""==""debug""gotodoDebug if""%1""==""run""gotodoRun if""%1""==""start""gotodoStart if""%1""==""stop""gotodoStop if""%1""==""configtest""gotodoConfigTest if""%1""==""version""gotodoVersion echoUsage:catalina(commands...) echocommands: echodebugStartCatalinainadebugger echodebug-securityDebugCatalinawithasecuritymanager echojpdastartStartCatalinaunderJPDAdebugger echorunStartCatalinainthecurrentwindow echorun-securityStartinthecurrentwindowwithsecuritymanager echostartStartCatalinainaseparatewindow echostart-securityStartinaseparatewindowwithsecuritymanager echostopStopCatalina echoconfigtestRunabasicsyntaxcheckonserver.xml echoversionWhatversionoftomcatareyourunning? gotoend
好似一个switch开关.
- 如果我们用startup.bat启动Tomcat的话,这里的"%1"的值是start
- 如果通过catalina.batrun启动Tomcat的话,这里的"%1"的值是run
第十二段脚本代码
:doRun shift ifnot""%1""==""-security""gotoexecCmd shift echoUsingSecurityManager set"SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy" gotoexecCmd :doStart shift if"%TITLE%"==""setTITLE=Tomcat set_EXECJAVA=start"%TITLE%"%_RUNJAVA% ifnot""%1""==""-security""gotoexecCmd shift echoUsingSecurityManager set"SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy" gotoexecCmd
首先分析一下其中的两个shift命令
第一个shift是把start或者run参数移除,然后下面还是利用"%1"来取参数,这时候,取出来的就是参数列表中的第二个.
第二个shift是在第二个参数移除掉.
我们再来比较一下start和run的启动区别.
差别
if"%TITLE%"==""setTITLE=Tomcat set_EXECJAVA=start"%TITLE%"%_RUNJAVA%
- 如果是startup.bat脚本启动的话,会启动一个新的cmd窗口,并且把cmd的title设置为Tomcat.
- 如果是catalina.batrun启动的话,不会新建cmd窗口,也不会设置cmd的title.
最后都跳到了execCmd标签处.
第十三段脚本代码
:execCmd remGetremainingunshiftedcommandlineargumentsandsavetheminthe setCMD_LINE_ARGS= :setArgs if""%1""==""""gotodoneSetArgs setCMD_LINE_ARGS=%CMD_LINE_ARGS%%1 shift gotosetArgs :doneSetArgs
这里还是利用"%1"来取出启动命令之后的参数,如果存的话,追加到CMD_LINE_ARGS环境变量上,并把这个参数移除.
通常情况下,我们这里是不会有什么参数了,-security这个参数我们都不会追加.
继续往下走.
第十四段脚本代码
remExecuteJavawiththeapplicableproperties ifnot"%JPDA%"==""gotodoJpda ifnot"%SECURITY_POLICY_FILE%"==""gotodoSecurity %_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% gotoend
很明显,我们的%JPDA%没有值,不会跳转;由于我们没有加-security参数,所以%SECURITY_POLICY_FILE%没有值,不会跳转.
下面这段长命令就是来启动BootStrap类,并把相应的参数传进去.
只要把对应的环境变量替换为它们的值,就可以解析出这个长命令的内容.相信你可以的.Bepatient!
总结一下
- 首先判断一下用户直接使用catalina.batrun来启动Tocmat
- 设置CATALINA_HOME和CATALINA_BASE环境变量值
- 验证CATALINA_HOME和CATALINA_BASE环境变量值的正确性
- 调用setnv.bat脚本
- 调用setclasspath.bat脚本
- 添加bootstrap.jar和tomcat-juli.jar到CLASSPATH中
- 设置CATALINA_TMPDIR临时目录的值为Tomcat目录下的temp
- 追加一系列的参数到JAVA_OPTS中
- 整合相关的启动信息,参数
- 启动Tomcat
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,有兴趣的朋友可以看下上篇文章《解析Tomcat的启动脚本-startup.bat》