美文网首页
bat脚本进阶之——扒一下tomcat的bat启动脚本

bat脚本进阶之——扒一下tomcat的bat启动脚本

作者: moutory | 来源:发表于2023-12-20 14:23 被阅读0次

前言

我们在上一篇文章中介绍了window系统bat脚本的常用语法,事实上掌握了文章中的内容就足以看懂大部分脚本了。本篇文章将以大名鼎鼎的Apache Tomcat为例,从启动脚本来简要介绍一下tomcat启动时都做了哪些事情。通过学习优秀开源项目的代码,可以提升自己对bat脚本的能力。

系列文章:

关于bat脚本的一些常用的命令

说在前面

tomcat作为市面上比较常见的Web服务器,在使用方式上也是十分简单的,只要配置JAVA_HOME就可以启动项目了,甚至于不需要配置tomcat的目录名,那么相关的启动参数和校验是怎么实现的呢?本篇文章会基于tomcat的bat启动脚本简要介绍一下流程。
环境介绍:apache-tomcat-9.0.76

一、脚本入口startup.bat

@echo off
rem Licensed to the Apache Software Foundation (ASF) under one or more
rem contributor license agreements.  See the NOTICE file distributed with
rem this work for additional information regarding copyright ownership.
rem The ASF licenses this file to You under the Apache License, Version 2.0
rem (the "License"); you may not use this file except in compliance with
rem the License.  You may obtain a copy of the License at
rem
rem     http://www.apache.org/licenses/LICENSE-2.0
rem
rem Unless required by applicable law or agreed to in writing, software
rem distributed under the License is distributed on an "AS IS" BASIS,
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.

rem ---------------------------------------------------------------------------
rem Start script for the CATALINA Server
rem ---------------------------------------------------------------------------

setlocal

rem Guess CATALINA_HOME if not defined
set "CURRENT_DIR=%cd%"
if not "%CATALINA_HOME%" == "" goto gotHome
set "CATALINA_HOME=%CURRENT_DIR%"
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
cd ..
set "CATALINA_HOME=%cd%"
cd "%CURRENT_DIR%"
:gotHome
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome

set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"

rem Check that target executable exists
if exist "%EXECUTABLE%" goto okExec
echo Cannot find "%EXECUTABLE%"
echo This file is needed to run this program
goto end
:okExec

rem Get remaining unshifted command line arguments and save them in the
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs

call "%EXECUTABLE%" start %CMD_LINE_ARGS%

:end

先看开头,熟悉的@echo off和一大段的rem注释,关闭了脚本的命令回显以及用多行注释来解释脚本的作用。

  • 步骤一:setlocal:声明局部环境变量作用域
    setlocal用于在脚本中创建局部化的环境变量作用域。当你在脚本中使用 setlocal 命令时,它会启用局部化环境,并创建一个新的变量作用域。
  • 步骤二:寻找CATALINA_HOME环境变量
    首先通过%cd%这个特殊变量来获取当前脚本的目录,若用户未手动在环境变量中配置CATALINA_HOME,则把当前目录作为CATALINA_HOME,并判断是否在当前目录下的bin目录中查看是否存在catalina.bat文件,如果存在则跳转到okHome标签
set "CURRENT_DIR=%cd%"
if not "%CATALINA_HOME%" == "" goto gotHome
set "CATALINA_HOME=%CURRENT_DIR%"
if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
  • 步骤三:okHome分支指定了接下来要执行的脚本(脚本的路径会引用到上面的CATALINA_HOME变量)
:okHome
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"
  • 步骤四:判断catalina.bat脚本是否存在,若存在则将脚本中所有的入参重新拼接在CMD_LINE_ARGS变量中(使用了shift配合标签跳转模拟for循环),作为执行catalina.bat脚本的入参。
    最后使用call命令来执行catalina.bat脚本,也就是对应着EXECUTABLE这个变量。同时传入了start参数作为脚本的第一个入参,接下来就进入到了catalina.bat脚本的执行逻辑了。
if exist "%EXECUTABLE%" goto okExec
echo Cannot find "%EXECUTABLE%"
echo This file is needed to run this program
goto end
:okExec

rem Get remaining unshifted command line arguments and save them in the
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs

call "%EXECUTABLE%" start %CMD_LINE_ARGS%

:end

二、第二个核心脚本文件catalina.bat

由于这个脚本内容比较多,这里我就不一次性把代码都粘贴上来了,而是根据实际步骤来粘贴代码,尽量把正常情况下启动脚本所执行的流程描述一遍,对于一些特别的流程就不再展开讲了。

步骤一:脚本标配:关闭命令回显,书写注释,声明局部变量作用域

PS:考虑到这块代码量比较多是注释,就不放出来了

@echo off
rem Licensed to the Apache Software Foundation (ASF) under one or more
rem contributor license agreements.  See the NOTICE file distributed with
rem this work for additional information regarding copyright ownership.
...

setlocal
步骤二:寻找%CATALINA_HOME%变量

脚本会先判断第一个入参是否为run,我们从上一个小节的分析中知道,脚本的第一个入参是start,所以接下来会走mainEntry标签。而mainEntry标签的逻辑也十分简单,使用del命令删除掉位于临时目录中的catalina.run文件(%~nx0表示当前的文件名,加上.run后缀就变成了catalina.run
接下来判断CATALINA_HOME这个变量是否不为空,由于在上一个脚本中我们已经使用setlocal在局部作用域中国定义了这个变量,所以此时脚本可以拿到CATALINA_HOME变量的值,接下来就进入到gotHome标签上面

rem Suppress Terminate batch job on CTRL+C
if not ""%1"" == ""run"" goto mainEntry

:mainEntry
del /Q "%TEMP%\%~nx0.run" >NUL 2>&1

rem Guess CATALINA_HOME if not defined
set "CURRENT_DIR=%cd%"
if not "%CATALINA_HOME%" == "" goto gotHome
步骤三:获取tomcat实例的根路径,即CATALINA_BASE

首先还是根据上面得到的tomcat安装路径下的bin目录,判断catalina.bat文件是否存在,若不存在则直接退出,若存在则判断是否能找到CATALINA_BASE这个环境变量,从前文我们可以得知,脚本中一直都是没有给这个变量赋过值的,所以这里肯定是没有值的,在这种情况下会默认将CATALINA_HOME的值赋予给CATALINA_BASE变量,简单来说就是把tomcat实例的根路径和tomcat的安装路径赋予相同的值。
接下来就到了gotBase标签了。

:gotHome

if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHome
echo The CATALINA_HOME environment variable is not defined correctly
echo This environment variable is needed to run this program
goto end
:okHome

rem Copy CATALINA_BASE from CATALINA_HOME if not defined
if not "%CATALINA_BASE%" == "" goto gotBase
set "CATALINA_BASE=%CATALINA_HOME%"
:gotBase
步骤四:检查tomcat安装路径是否存在;这种特殊字符

这里将CATALINA_HOME变量值的所有;字符都替换成%再与原值进行对比,看二者是否一致,如果一致则说明变量值不存在特殊字符串,进入到homeNoSemicolon标签,如果路径中存在;,则报错提醒脚本结束。
再来看看homeNoSemicolon标签,里面用同样的方式检查了CATALINA_BASE标签是否存在;这种特殊符号,存在则报错,不存在则进入到baseNoSemicolon标签中。

:gotBase

if "%CATALINA_HOME%" == "%CATALINA_HOME:;=%" goto homeNoSemicolon
echo Using CATALINA_HOME:   "%CATALINA_HOME%"
echo Unable to start as CATALINA_HOME contains a semicolon (;) character
goto end
:homeNoSemicolon

if "%CATALINA_BASE%" == "%CATALINA_BASE:;=%" goto baseNoSemicolon
echo Using CATALINA_BASE:   "%CATALINA_BASE%"
echo Unable to start as CATALINA_BASE contains a semicolon (;) character
goto end
步骤五:执行可选环境变量文件setenv.bat

baseNoSemicolon标签会判断当前tomcat实例的bin目录下是否存在setenv.bat文件,如果存在则进行调用,不存在则继续进入到setenvDone标签中。

需要注意的是,在Tomcat中setenv.bat文件存在的目的是允许用户自定义环境变量和设置 JVM 参数,而无需修改 Tomcat 的启动脚本。这样,在升级或更改 Tomcat 版本时,setenv.bat 文件可以保持不变,而不会丢失用户的自定义设置。需要注意的是,默认情况下setenv.bat文件是没有在bin目录中存在的,有需要的话用户要单独创建这个文件

:baseNoSemicolon

rem Ensure that any user defined CLASSPATH variables are not used on startup,
rem but allow them to be specified in setenv.bat, in rare case when it is needed.
set CLASSPATH=

rem Get standard environment variables
if not exist "%CATALINA_BASE%\bin\setenv.bat" goto checkSetenvHome
call "%CATALINA_BASE%\bin\setenv.bat"
goto setenvDone
:checkSetenvHome
if exist "%CATALINA_HOME%\bin\setenv.bat" call "%CATALINA_HOME%\bin\setenv.bat"
:setenvDone
步骤六:执行setclasspath.bat脚本,保障运行环境

setenvDone标签中会去tomcat安装路径下的bin目录检查setclasspath.bat文件是否存在,如果不存在则报错且脚本退出(因为classpath变量是运行tomcat的必要条件之一),也是tomcat下载后就自带的一个文件。如果文件存在则跳转到okSetclasspath标签,在这个标签中执行setclasspath.bat文件(会把start作为第一个入参也一并传入)
文件执行结束后,查看CLASSPATH变量是否存在

  • 若不存在则跳转到emptyClasspath标签,将tomcat实例目录的\bin\bootstrap.jar加入到CLASSPATH变量中
  • 若存在的话则在原来的变量值基础后面追加;后,再将tomcat实例目录的\bin\bootstrap.jar加入到CLASSPATH变量中
:setenvDone

rem Get standard Java environment variables
if exist "%CATALINA_HOME%\bin\setclasspath.bat" goto okSetclasspath
echo Cannot find "%CATALINA_HOME%\bin\setclasspath.bat"
echo This file is needed to run this program
goto end
:okSetclasspath
call "%CATALINA_HOME%\bin\setclasspath.bat" %1
if errorlevel 1 goto end

rem Add on extra jar file to CLASSPATH
rem Note that there are no quotes as we do not want to introduce random
rem quotes into the CLASSPATH
if "%CLASSPATH%" == "" goto emptyClasspath
set "CLASSPATH=%CLASSPATH%;"

:emptyClasspath
set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
步骤七:设置Tomcat实例临时目录,将日志依赖也加入到classpath中

检查当前CATALINA_TMPDIR变量是否有值(默认情况下是没有这个变量的),没有的话则以tomcat实例目录下的temp目录作为临时目录,接下来进入到gotTmpdir标签。在gotTmpdir标签中会检查Tomcat实例目录中是否已有自己的日志依赖(tomcat-juli.jar),如果有则将该依赖加入classpath,没有的话则使用Tomcat安装目录下的日志依赖来加入到classpath变量中。执行完成后,就进入到juliClasspathDone标签中

if not "%CATALINA_TMPDIR%" == "" goto gotTmpdir
set "CATALINA_TMPDIR=%CATALINA_BASE%\temp"
:gotTmpdir

rem Add tomcat-juli.jar to classpath
rem tomcat-juli.jar can be over-ridden per instance
if not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHome
set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"
goto juliClasspathDone
:juliClasspathHome
set "CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\tomcat-juli.jar"
:juliClasspathDone
步骤八:将ssl安全相关的环境变量加入到JVM运行参数中

TomcatJSSE_OPTS变量是用来存放与 SSL/TLS 相关的选项,如果没有配置的话则默认会将-Djdk.tls.ephemeralDHKeySize=2048作为JSSE_OPTS变量的值,最后将JSSE_OPTS-Djava.protocol.handler.pkgs=org.apache.catalina.webresources追加加入到JVM运行参数JAVA_OPTS变量中。

:juliClasspathDone

if not "%JSSE_OPTS%" == "" goto gotJsseOpts
set "JSSE_OPTS=-Djdk.tls.ephemeralDHKeySize=2048"
:gotJsseOpts
set "JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"

set "JAVA_OPTS=%JAVA_OPTS% -Djava.protocol.handler.pkgs=org.apache.catalina.webresources"
步骤九:加入日志配置文件

判断是否存在LOGGING_CONFIG变量,如果前两位为-D则认为用户存在自定义配置,继续检查CATALINA_LOGGING_CONFIG变量,如果后者的值不存在的话则会将LOGGING_CONFIG的值作为CATALINA_LOGGING_CONFIG变量的值,进入到noLoggingDeprecation标签中。默认情况下这个变量都是没有配置的,所以会直接走noLoggingDeprecation标签。

noLoggingDeprecation标签中,我们会检查在上一个环境中CATALINA_LOGGING_CONFIG变量是否有值,有值的话进入noJuliConfig标签,如果没有值的话,则检查Tomcat实例目录下的conf\logging.properties文件是否存在,不存在则不启用日志。存在则将这里的日志文件作为tomcat实例的日志配置文件(这也是默认情况的处理方式)。设置完成后则跳转到noJuliConfig标签。

PS:需要注意的是,在文档的备注中提到了LOGGING_CONFIG变量已经是过时了,此处做判断只是为了兼容以前的旧版本。

if not "%LOGGING_CONFIG:~0,2%"=="-D" goto noLoggingDeprecation
if not "%CATALINA_LOGGING_CONFIG%" == "" goto noLoggingDeprecation
set "CATALINA_LOGGING_CONFIG=%LOGGING_CONFIG%"
:noLoggingDeprecation

if not "%CATALINA_LOGGING_CONFIG%" == "" goto noJuliConfig
set CATALINA_LOGGING_CONFIG=-Dnop
if not exist "%CATALINA_BASE%\conf\logging.properties" goto noJuliConfig
set CATALINA_LOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties"
步骤十:设置日志管理器和优化JVM启动参数

noJuliConfig标签中,会判断LOGGING_MANAGER变量是否存在,不存在则使用-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager作为这个变量的值(这也是一般情况下的执行结果)。接下来就跳转到noJuliManager标签,我们在这个标签里面针对高版本的JDK进行了JVM参数的优化。

:noJuliConfig

if not "%LOGGING_MANAGER%" == "" goto noJuliManager
set LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
:noJuliManager

rem Configure JAVA 9 specific start-up parameters
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.lang=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.io=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.util=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.base/java.util.concurrent=ALL-UNNAMED"
set "JDK_JAVA_OPTIONS=%JDK_JAVA_OPTIONS% --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED"
步骤十一:判断用户是否存在拓展JDK API的操作

判断JAVA_ENDORSED_DIRS变量是否为空

  • 如果为空则跳转到noEndorsedVar标签(默认情况下会走这个流程)。在新的标签中判断Tomcat安装目录中是否存在endorsed文件(默认情况下是不存在的),存在则初始化ENDORSED_PROP变量,不存在则直接跳转到doneEndorsed标签。
  • 如果不为空则初始化ENDORSED_PROP变量,跳转到doneEndorsed标签。

doneEndorsed标签中我们将上面校验得到的环境变量进行了输出。
PS:endorsed 标准 API 的作用是允许开发人员替换或扩展 JDK 中的标准 API 实现。此处步骤其实只是为了判断用户本身是否存在覆盖标准JDK代码的行为。

set ENDORSED_PROP=ignore.endorsed.dirs
if "%JAVA_ENDORSED_DIRS%" == "" goto noEndorsedVar
set ENDORSED_PROP=java.endorsed.dirs
goto doneEndorsed
:noEndorsedVar
if not exist "%CATALINA_HOME%\endorsed" goto doneEndorsed
set ENDORSED_PROP=java.endorsed.dirs
:doneEndorsed


echo Using CATALINA_BASE:   "%CATALINA_BASE%"
echo Using CATALINA_HOME:   "%CATALINA_HOME%"
echo Using CATALINA_TMPDIR: "%CATALINA_TMPDIR%"
步骤十二:初始化最终的Java启动参数

判断脚本第一个参数是不是debug,这里的值是start,所以下面会输出JRE_HOME,然后进入到java_dir_displayed标签,对CLASSPATH等环境变量进行输出,接下来就是定义Main启动类入口MAINCLASS等核心变量。执行结束后进入后续的执行步骤选择

if ""%1"" == ""debug"" goto use_jdk
echo Using JRE_HOME:        "%JRE_HOME%"
goto java_dir_displayed
:use_jdk
echo Using JAVA_HOME:       "%JAVA_HOME%"
:java_dir_displayed
echo Using CLASSPATH:       "%CLASSPATH%"
echo Using CATALINA_OPTS:   "%CATALINA_OPTS%"

set _EXECJAVA=%_RUNJAVA%
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start
set SECURITY_POLICY_FILE=
set DEBUG_OPTS=
set JPDA=
步骤十三:根据脚本首个入参判断接下来的启动命令分支

判断脚本首个入参是否是jpda,不是则进入noJpda标签,在noJpda标签中因为我们的首个入参是start所以会进入doStart标签

if not ""%1"" == ""jpda"" goto noJpda
set JPDA=jpda
if not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransport
set JPDA_TRANSPORT=dt_socket
:gotJpdaTransport
if not "%JPDA_ADDRESS%" == "" goto gotJpdaAddress
set JPDA_ADDRESS=localhost:8000
:gotJpdaAddress
if not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspend
set JPDA_SUSPEND=n
:gotJpdaSuspend
if not "%JPDA_OPTS%" == "" goto gotJpdaOpts
set JPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%
:gotJpdaOpts
shift
:noJpda

if ""%1"" == ""debug"" goto doDebug
if ""%1"" == ""run"" goto doRun
if ""%1"" == ""start"" goto doStart
if ""%1"" == ""stop"" goto doStop
if ""%1"" == ""configtest"" goto doConfigTest
if ""%1"" == ""version"" goto doVersion
步骤十四:定义控制台窗口的标题和最终执行脚本

进入到doStart标签后,很重要的一点。这里执行了一次shift,也就是说原来的start参数此时会被后面的参数覆盖掉。
先判断TITLE变量是否存在(一般默认是不存在),若不存在则使用Tomcat作为最终cmd窗口的标题,,然后再把TITLE_RUNJAVA变量组装到_EXECJAVA变量上面,这里需要注意的是_RUNJAVA变量是上面在执行setclasspath.bat脚本中所定义的变量,它的值是%JAVA_HOME%\bin\java.exe
接下来判断首个入参是不是security(默认情况下不是),所以会跳转到execCmd标签上面

:doStart
shift
if "%TITLE%" == "" set TITLE=Tomcat
set _EXECJAVA=start "%TITLE%" %_RUNJAVA%
if not ""%1"" == ""-security"" goto execCmd
shift
echo Using Security Manager
set "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"
goto execCmd
步骤十五:把脚本原有的其他传递参数拼接在最终需要执行的脚本上面

这里使用了setArgsshift来将脚本中原有的其他参数全部拼接在新的命令行上面,作为新的入参。全部完成后,进入到doneSetArgs标签。

:execCmd
rem Get remaining unshifted command line arguments and save them in the
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
步骤十六:执行最终的执行脚本

判断是否存在JPDA或者SECURITY_POLICY_FILE变量,前者用于开启远程debug端口,后者是配置安全策略文件,默认情况下这两个参数也都是空的,接着就是拼接所有变量组合成的执行脚本,完成程序启动了。
可以说本质上最后是用java来运行tomcat的main文件jar包,然后通过各种入参来设置关于日志、临时目录等应用运行所需要的参数,最终来运行我们的web应用。

:doneSetArgs

rem Execute Java with the applicable properties
if not "%JPDA%" == "" goto doJpda
if not "%SECURITY_POLICY_FILE%" == "" goto doSecurity
%_EXECJAVA% %CATALINA_LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -D%ENDORSED_PROP%="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
goto end

:end

小结

其实start.sh作为入口启动文件并没有做太多的逻辑,只是简单的检查一下是否具备运行tomcat脚本的启动环境而已,重头戏还是在catalina.bat脚本身上。而catalina.bat脚本本身加上注释也就三百多行,其实逻辑分支虽然多但还不算特别复杂,主要是对运行环境所需要的参数进行校验和定义,语法也十分精简,仔细查看会有很多收获。
从脚本中我们也可以看出Tomcat本身有很多地方支持我们进行拓展,这种开发理念也十分值得我们学习。

相关文章

网友评论

      本文标题:bat脚本进阶之——扒一下tomcat的bat启动脚本

      本文链接:https://www.haomeiwen.com/subject/tiizgdtx.html