Home Java Building Java applications with Apache Ant, quick start

Building Java applications with Apache Ant, quick start

by admin

What this article is about

One of the distinguishing features of the Java platform is its independence from the tools you use. You can develop as big a Java application as you want with notepad (vi) and the command line. Obviously nobody does that and everybody uses an IDE of some sort. As a consequence of this independence there are lots of IDEs for Java. This is all good but there is one peculiarity. If your colleague is creating an application and used IDE_A to build it, you won’t be able to do it in IDE_B which you have.
In general, this is not a problem for a long time. It is good practice to use a build system independent of the IDE. For Java, there are two of them: Apache-Ant and Maven (also in general Apache). But there is a pitfall. If in Delphi or Visual Studio you need to click the new button to build the application, then writing an ant script to build a web application, especially for the novice developer is not a trivial task.
This article discusses building and deploying a Java web application step by step.
In general the problem can be solved with either ant or maven, here we will consider ant. For beginners it is easier and more obvious.

Note 10 years later

Decided to take a look at my article written 10 years ago. Sounds old-fashioned, in 2021 I generally don’t recommend building Java applications with ant. But if you have such a need, the article can still help. Let it live.

Why you need a build script

  • Project task independence from the environment.
  • 100% repeatability of any result (works for me, works for everyone)
  • Elimination of human factor from important operations
  • Turning deplotting from a complex operation into a routine task.

Collecting and Deploying War

There are many ways to build war and there are many ways to arrange the application files. This article gives one way – maybe not the best way, but it’s not bad.

Project Structure

Project files are arranged like this.

 +---ant| | step1.ant.xml| | step2.ant.xml| | step3.ant.xml| | step4.ant.xml| | step5.ant.xml| | step6.ant.xml| || \---env| default.properties| semenych.properties|+---lib| \---google-gson-1.4| gson-1.4-javadoc.jar| gson-1.4-sources.jar| gson-1.4.jar|+---src| \---java| \---com| \---dataart| \---ant| \---demo| Test.java|\---web\---WEB-INF| web.xml|\---lib 


The real project will have build.xml instead of stepN.xml. There will be more libraries and each one will be in a separate directory. The names of the packages give away that I work for DataArt.

Step 1: compile

First of all just compile all the code by connecting the GSON library. The compiled code goes into the .build directory. The name can be anything, but it’s usually convenient if the directory name starts with a dot.

 <project name="step1"default="compile"> 
<target name="compile"><mkdir dir="../.build/classes"/><javac srcdir="../src/java" destdir="../.build/classes"><classpath location="../lib/google-gson-1.4/gson-1.4.jar"/></javac></target></project>

Step 2: improve the script

The script in step 1 is not very flexible and a number of paths are prescribed there twice. Let’s improve it

 <project name="step2" default="compile"><property name="dir.build" value="../.build"/><property name="dir.classes" value="${dir.build}/classes"/><property name="dir.src.java" value="../src/java"/><target name="clean"><delete dir="${dir.build}"/></target><target name="mkdirs"><mkdir dir="${dir.classes}"/></target><target name="compile" depends="mkdirs"><javac srcdir="${dir.src.java}" destdir="${dir.classes}"><classpath location="../lib/google-gson-1.4/gson-1.4.jar"/></javac></target></project> 

Step 3: Paths to Libraries

The paths to the libraries are hard-coded in the middle of the code. This is not good, let’s change it.

 <project name="step3" default="compile"><property name="dir.build" value="../.build"/><property name="dir.classes" value="${dir.build}/classes"/><property name="dir.src.java" value="../src/java"/><property name="dir.lib" value="../lib"/><path id="libs.gson"><fileset dir="${dir.lib}/google-gson-1.4"><include name="*.jar"/></fileset></path><path id="libs.main.module"><path refid="libs.gson"/></path><target name="clean"><delete dir="${dir.build}"/></target><target name="mkdirs"><mkdir dir="${dir.classes}"/></target><target name="compile" depends="mkdirs"><javac srcdir="${dir.src.java}" destdir="${dir.classes}"><classpath><path refid="libs.main.module"/></classpath></javac></target></project> 

Step 4: Managing Cofigurations

Configuration management is a mega technology that the great American programmer Walden Mathews told me about. The idea is that when you build you put a timeout or a path to some external directory or URL of some service in the properties file. On your local machine it is one, on the battle server (or on a colleague’s machine another). The question is how to use the correct property values and not kill each other.

 <project name="step4" default="compile"><property name="dir.build" value="../.build"/><property name="dir.classes" value="${dir.build}/classes"/><property name="dir.src.java" value="../src/java"/><property name="dir.lib" value="../lib"/><property name="dir.env" value="./env"/><property name="assembled.properties" value="${dir.build}/assembled.properties"/><path id="libs.gson"><fileset dir="${dir.lib}/google-gson-1.4"><include name="*.jar"/></fileset></path><path id="libs.main.module"><path refid="libs.gson"/></path><target name="clean"><delete dir="${dir.build}"/></target><target name="mkdirs"><mkdir dir="${dir.build}"/><mkdir dir="${dir.classes}"/></target><target name="init" depends="mkdirs"><property name="env" value="${user.name}"/><echo level="info" message="env=${env}"/><available file="${dir.env}/${env}.properties" property="env.props.available"/><fail unless="env.props.available"message="No such file: ${dir.env}/${env}.properties"/><property file="${dir.env}/${env}.properties"/><property file="${dir.env}/default.properties"/><echoproperties destfile="${assembled.properties}"/><filter filtersfile="${assembled.properties}"/></target><target name="compile" depends="init"><javac srcdir="${dir.src.java}" destdir="${dir.classes}"><classpath><path refid="libs.main.module"/></classpath></javac></target></project> 


Here target init reads the file with the name that matches your username. If you don’t find one, the build will not continue. And then it reads the default properties from default. Because property values in ant cannot be overridden, all properties must be in default and only those values which are different in your file.

default.properties

 welcome.file=default.htmltomcat.service.name=tomcat6tomcat.home=C:/bin/tomcat6 

semenych.properties

 welcome.file=index.html 


If you want to build a project with a properties file whose name is different from the name of the user – write it like this
ant -Denv=mihalych compile
Note that the command just says mihalych and not mihalych.properties

Step 5: Let’s build the jar and war file already

Yes indeed let’s.

 <project name="step5" default="compile"><property name="dir.build" value="../.build"/><property name="dir.classes" value="${dir.build}/classes"/><property name="dir.src.java" value="../src/java"/><property name="dir.lib" value="../lib"/><property name="dir.env" value="./env"/><property name="dir.war.content" value="${dir.build}/war.content"/><property name="assembled.properties" value="${dir.build}/assembled.properties"/><property name="file.jar" value="${dir.build}/main.module.jar"/><property name="name.application" value="demo"/><property name="file.war" value="${dir.build}/${name.application}.war"/><path id="libs.gson"><fileset dir="${dir.lib}/google-gson-1.4"><include name="*.jar"/></fileset></path><path id="libs.main.module"><path refid="libs.gson"/></path><target name="clean"><delete dir="${dir.build}"/></target><target name="mkdirs"><mkdir dir="${dir.build}"/><mkdir dir="${dir.classes}"/><mkdir dir="${dir.war.content}"/></target><target name="init" depends="mkdirs"><property name="env" value="${user.name}"/><echo level="info" message="env=${env}"/><available file="${dir.env}/${env}.properties" property="env.props.available"/><fail unless="env.props.available"message="No such file: ${dir.env}/${env}.properties"/><property file="${dir.env}/${env}.properties"/><property file="${dir.env}/default.properties"/><echoproperties destfile="${assembled.properties}"/><filter filtersfile="${assembled.properties}"/></target><target name="compile" depends="init"><javac srcdir="${dir.src.java}" destdir="${dir.classes}"><classpath><path refid="libs.main.module"/></classpath></javac></target><target name="build.jar" depends="compile"><jar destfile="${file.jar}"basedir="${dir.classes}"compress="false"index="true"></jar></target><target name="build.war.content"depends="build.jar"><copy todir="${dir.war.content}" preservelastmodified="true" overwrite="true"><fileset dir="../web"/></copy><copy todir="${dir.war.content}/WEB-INF/lib" preservelastmodified="true"><path refid="libs.main.module"/></copy><copy todir="${dir.war.content}/WEB-INF/lib"preservelastmodified="true"file="${file.jar}"></copy><replace dir="${dir.war.content}/WEB-INF/"propertyfile="${assembled.properties}"><include name="*.xml"/><replacefilter token="@welcome.file@" property="welcome.file"/></replace></target><target name="build.war" depends="build.war.content"><delete file="${file.war}"/><warcompress="true"encoding="utf-8"warfile="${file.war}"webxml="${dir.war.content}/WEB-INF/web.xml"><fileset dir="${dir.war.content}" excludes="WEB-INF/web.xml"/></war></target></project> 


One Comment :

 <replace dir="${dir.war.content}/WEB-INF/"propertyfile="${assembled.properties}"><include name="*.xml"/><replacefilter token="@welcome.file@" property="welcome.file"/></replace> 


this section does the replacement inside the web.xml and the web.xml looks like this :

 <?xml version="1.0" encoding="UTF-8"?><web-app version="2.5"xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><welcome-file-list><welcome-file> @welcome.file@</welcome-file></welcome-file-list></web-app> 

Step 6: war ready, the final touch, deplotting

The script will do the so called "cold" deploop i.e. Deployment of the application with server stopping. In some cases, "cold" deployment allows to avoid some problems with resources freeing and cache clearing.

 <project name="step6" default="compile"><property name="dir.build" value="../.build"/><property name="dir.classes" value="${dir.build}/classes"/><property name="dir.src.java" value="../src/java"/><property name="dir.lib" value="../lib"/><property name="dir.env" value="./env"/><property name="dir.war.content" value="${dir.build}/war.content"/><property name="assembled.properties" value="${dir.build}/assembled.properties"/><property name="file.jar" value="${dir.build}/main.module.jar"/><property name="name.application" value="demo"/><property name="file.war" value="${dir.build}/${name.application}.war"/><path id="libs.gson"><fileset dir="${dir.lib}/google-gson-1.4"><include name="*.jar"/></fileset></path><path id="libs.main.module"><path refid="libs.gson"/></path><target name="clean"><delete dir="${dir.build}"/></target><target name="mkdirs"><mkdir dir="${dir.build}"/><mkdir dir="${dir.classes}"/><mkdir dir="${dir.war.content}"/></target><target name="init" depends="mkdirs"><property name="env" value="${user.name}"/><echo level="info" message="env=${env}"/><available file="${dir.env}/${env}.properties" property="env.props.available"/><fail unless="env.props.available"message="No such file: ${dir.env}/${env}.properties"/><property file="${dir.env}/${env}.properties"/><property file="${dir.env}/default.properties"/><echoproperties destfile="${assembled.properties}"/><filter filtersfile="${assembled.properties}"/></target><target name="compile" depends="init"><javac srcdir="${dir.src.java}" destdir="${dir.classes}"><classpath><path refid="libs.main.module"/></classpath></javac></target><target name="build.jar" depends="compile"><jar destfile="${file.jar}"basedir="${dir.classes}"compress="false"index="true"></jar></target><target name="build.war.content"depends="build.jar"><copy todir="${dir.war.content}" preservelastmodified="true" overwrite="true"><fileset dir="../web"/></copy><copy todir="${dir.war.content}/WEB-INF/lib" preservelastmodified="true"><path refid="libs.main.module"/></copy><copy todir="${dir.war.content}/WEB-INF/lib"preservelastmodified="true"file="${file.jar}"></copy><replace dir="${dir.war.content}/WEB-INF/"propertyfile="${assembled.properties}"><include name="*.xml"/><replacefilter token="@welcome.file@" property="welcome.file"/></replace></target><target name="build.war" depends="build.war.content"><delete file="${file.war}"/><warcompress="true"encoding="utf-8"warfile="${file.war}"webxml="${dir.war.content}/WEB-INF/web.xml"><fileset dir="${dir.war.content}" excludes="WEB-INF/web.xml"/></war></target><target name="deploy"depends="build.war, do.undeploy.war, do.deploy.war"></target><target name="do.undeploy.war" depends="init"><service.stop.win32 service.name="${tomcat.service.name}"/><delete file="${tomcat.home}/webapps/${name.application}.war" failonerror="false"/><delete dir="${tomcat.home}/webapps/${name.application}" failonerror="false"/></target><target name="do.deploy.war" depends="init"><copy todir="${tomcat.home}/webapps" failonerror="yes"><fileset file="${file.war}"/></copy><service.start.win32 service.name="${tomcat.service.name}"/></target><!-- macro --><macrodef name="service.stop.win32"><attribute name="service.name"/><sequential><echo> Stoping service: @{service.name}</echo><exec executable="net" outputproperty="whatsRunning"><arg value="stop"/><arg value="@{service.name}"/></exec></sequential></macrodef><macrodef name="service.start.win32"><attribute name="service.name"/><sequential><echo> Starting service: @{service.name}</echo><exec executable="net" failonerror="yes"><arg value="start"/><arg value="@{service.name}"/></exec></sequential></macrodef></project> 

Conclusion

That’s all there is to it. This is almost finished example taken from real project. Feel free to use it, don’t forget to write comments.

Links

You may also like