Редеплойинг приложения на Tomcat средствами Ant

Процесс разработки нашего проекта обладает одним минусом, свойственным многим J2EE-проектам: при изменении кода проекта и пересборке его сервер не сразу подхватывает обновку, а требует полного останова себя, очистки кэша, запуска себя по-новой и передеплойинга пакета.

Для того чтобы не делать это каждый раз ручками есть несколько простых способов: например, те же скрипты (batch’и для windows и shell-скрипты для linux). Но мне показалось более простым сделать так, чтобы делал это сам собирающий ant-скрипт (сценарий?): одно нажатие клавиши и все просходит автоматически…

(версия для windows)

Поэтому я взял скрипт существующий и стал его править. Пришлось столкнуться с несколькими проблемами/хитростями ant’а на windows (опытному в ant’о-строении человеку они конечно нипочем :) ), в результате чего получился рабочий скрипт, выдержки из которого я и разберу ниже.

Вот – файл build.properties. Он содержит некоторые значения, которые возможно будут часто меняться и поэтому лучше хранить их отдельно от ant-срипта.


# имя пакета
war.name = SomeProjectPackage

# путь к JDK
java.home = "C:\Worktable\Java\jdk1.5.0_12"
# путь к корневому каталогу сервера
server.dir = C:/Worktable/Java/apache-tomcat-5.5.25
# скрипт, использующийся для запуска сервера
server.command = catalina.bat

# путь к расположению проекта
root.dir = C:/Workspace/SomeProject
# путь к месту, куда на сервере будет выкладываться пакет
deploy.dir = ${server.dir}/webapps/

# настройки JPDA (удаленнный дебаггинг может осуществляться
# (например, средствами Eclipse) подключением к указанному порту)
jpda.transport = dt_socket
jpda.port = 56666

# путь к библиотекам проекта
lib.dir = ${root.dir}/lib/
# путь к временному месту сборки проекта
dist.dir = ${root.dir}/dist/
# путь к каталогу с веб-содержимым - страницами, скриптами и т.п.
web.dir = ${root.dir}/WebContent/

Теперь по частям рассмотрим сам скрипт. В заголовке - включаем наш файл .properties.


<?xml version="1.0" encoding="UTF-8"?>

    <project name="SomeProject" default="redeploy" basedir=".">
    <property file="build.properties"/>

    . . .

Далее идут цели сборки (build), очистки временных каталогов, использованных при сборке (clean) и цель пересборки - очищающая, а затем собирающая (rebuild). Их подробное рассмотрение не относится к цели статьи :).


    . . .

    <!-- Compiles project with all dependencies. -->

    <target name="build"
            description="--> compiles project with all dependencies">
        <mkdir dir="${dist.dir}"/>
        <mkdir dir="${dist.dir}/classes"/>
        <javac source="1.5"
            srcdir="${root.dir}/src"
            destdir="${dist.dir}/classes"
            debug="on"
            verbose="false"
            optimize="on">
            <classpath>
                <fileset dir="${lib.dir}" includes="**/*.jar"/>
            </classpath>
        </javac>
    </target>

    <!-- Cleans the build. -->

    <target name="clean"
            description="--> cleans the build">
        <delete quiet="true" dir="${dist.dir}"/>
    </target>

    <!-- Rebuild. -->

    <target name="rebuild" depends="clean,build"
            description="--> [clean, build]"/>

    . . .

А вот, собственно, наши подчиненные – цели деплойинга (выкладывания нового пакета на сервер), де-деплойинга (забирания старого пакета с сервера) и пере-деплойинга (забирания старого, а потом выкладывания нового).

При выкладывании (deploy) мы компилируем код (depends="build"), затем создаем на сервере каталог для логов, собираем пакет из скомпилированных исходников (командой jar), выкладывая его во временный каталог, а затем запускаем сервер. Для Windows сервер из ant-скрипта может быть запущен только в своем окружении, для этого приходится вызывать его командой cmd /c catalina.bat jpda start, через команду ant’а exec (аргументы должны быть разделены командами arg именно так, как представлено ниже – для того чтобы exec обернул команду catalina jpda start в кавычки, для ее целостности). Также серверу нужно передать несколько переменных окружения, что мы и делаем, используя команды env. Сервер мы запускаем в отдельном потоке (spawn="true" – иначе скрипт будет ожидать от сервера команды завершения и не будет производить дальнейших действий) и в чистом виде (не через ява-машину - vmlauncher="false"). Сервер запущен, можно выложить туда пакет и удалить временные каталоги и файлы (последовательность команд copy и двух delete).

Для выгрузки пакета с сервера (undeploy) мы останавливаем серевер по правилам, описанным выше (при остановке мы можем не указывать переменные окружения для JPDA и подолждать пока сервер остановится; но если что-то не вышло – это нормально – возможно сервер и не был запущен (failifexecutionfails="false")). Затем мы очищаем каталог на сервере, в которые он распаковывал наш пакет, удаляем сам пакет и очищаем кэш сервера.

При перевыладке (redeploy) - цели по умолчанию - старая версия пакета удаляется с сервера (undeploy), очищаются временные каталоги (clean), и пакет собирается и выкладывается на сервер (deploy).


    . . .

    <!-- Prepares deployment -->

    <target name="pre-deploy">
        <mkdir dir="${dist.dir}/war"/>
        <mkdir dir="${dist.dir}/war/WEB-INF"/>
        <mkdir dir="${dist.dir}/war/WEB-INF/classes"/>
        <mkdir dir="${dist.dir}/war/WEB-INF/lib"/>
        <copy todir="${dist.dir}/war">
            <fileset dir="${web.dir}">
                <include name="**/*.*"/>
            </fileset>
        </copy>
        <copy todir="${dist.dir}/war/WEB-INF/classes">
            <fileset dir="${dist.dir}/classes">
                <include name="**/*.*"/>
            </fileset>
        </copy>
        <copy todir="${dist.dir}/war/WEB-INF/lib" flatten="true">
            <fileset dir="${lib.dir}">
                <include name="**/*.jar"/>
            </fileset>
        </copy>
    </target>

    <!-- Deploys application on server. -->

    <target name="deploy" depends="rebuild, pre-deploy"
            description="--> deploys application on server">
        <mkdir dir="${server.dir}/logs"/>
        <jar jarfile="${war.name}.war" basedir="${dist.dir}/war"/>
        <exec dir="${server.dir}/bin" executable="cmd"
                vmlauncher="false" spawn="true">
            <env key="JAVA_HOME" value="${java.home}"/>
            <env key="JPDA_TRANSPORT" value="${jpda.transport}" />
            <env key="JPDA_ADDRESS" value="${jpda.port}" />
            <env key="CATALINA_HOME" value="${server.dir}"/>
            <arg value="/c" />
            <arg value="${server.command} jpda start"/>
        </exec>

        <copy file="${war.name}.war" todir="${deploy.dir}"/>
        <delete dir="${dist.dir}" failonerror="false" />
        <delete file="${war.name}.war" failonerror="false" />
    </target>

    <!-- Un-deploys application from server. -->

    <target name="undeploy"
            description="--> un-deploys application from server">
        <exec dir="${server.dir}/bin" executable="cmd"
                failifexecutionfails="false" vmlauncher="false">
            <env key="JAVA_HOME" value="${java.home}"/>
            <env key="CATALINA_HOME" value="${server.dir}"/>
            <arg value="/c" />
            <arg value="${server.command} stop"/>
        </exec>

        <delete quiet="true">
            <fileset dir="${deploy.dir}">
                <include name="${war.name}*"/>
            </fileset>
        </delete>

        <delete dir="${deploy.dir}/${war.name}" failonerror="false"/>
        <delete file="${deploy.dir}/${war.name}.war" failonerror="false" />
        <delete dir="${server.dir}/work/Catalina" failonerror="false" />
    </target>

    <!-- Re-deploys application on server. -->

    <target name="redeploy"
            depends="undeploy,clean,deploy"
            description="--> [undeploy,clean,deploy]">
    </target>

    </project>

</xml>

Собственно, все :)

P.S. Действительно, как мне подсказывают, есть Tomcat Client Deployer – пакет, имеющий свои средства (в том числе таски) для деплоинга через Ant, но требующий сервер быть всегда запущенным.

И конечно же, чтобы избежать новых претензий, есть дополнительные средства, облегчающие этот процесс вроде CruiseControl и так далее - при большом количестве проектов и действительно большой команде они бы, возможно, были бы хорошим решением (пока у меня такого опыта [к счастью?] нет).

А ещё у Maven есть, например, war-плагин. Я об этом тоже знаю, правда-правда :).

#tomcat #ant #java