Programming
Contents [hide]
When dealing with a Java Web applications, the completed application is commonly delivered as a WAR file. The Maven build system can easily be set up to create WAR files. In this post we take an in-depth look at how Maven goes from a source project to the final WAR product.
Used software: Apache Maven 3.0.4.
The structure of a WAR file looks like this (source):
Let’s look at a very simple Mavenized Web application project in a directory named myprojectname. The contents of this folder are:
The pom.xml file looks like this:
To package this bare-bones project into a WAR file we use the
mvn package
command.
This creates a WAR file at
/target/myprojectname.war
. The execution of this mvn package
command is the scenario we will take a closer look at. An overview of the operation is displayed in the image below.
We know that Maven can succesfully create a WAR file from the source project. But our pom.xml file is almost empty, so how does Maven know things like where the .java files for compilation are, or what the exact structure of the WAR file should be? The answer is that these settings are implicit. Maven uses a “convention over configuration” approach, and provides default values for the project’s configuration.
First, some things are implicit because of the default way Maven plugins are bound to the lifecycle phases. For example, thecompile phase has the goal compiler:compile assigned to it by default. This means that when executing the compile phase, thecompile goal of the compiler plugin is called. When you select WAR packaging in your pom.xml, the war:war goal (which creates the WAR) is bound to the package phase.
Second, plugins define default values for their parameters that are used when no explicit value is passed. For example, thecompiler:compile goal has a parameter called compilerId. This parameter has the value
javac
by default, meaning the regular JDK compiler will be used. A project’s pom.xml file can override this default to use a different compiler.
Finally, some settings are contained in the Super POM that every pom.xml implicitly inherits. For Maven 3 the Super POM is located in
maven_dir/lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml
. In this file we find the default directory locations for source files, resources, test source files, etc:
These mappings define Maven’s Standard Directory Layout, and it is encouraged to keep this default structure in all your Maven projects.
If you are having trouble understanding this section, go read about Maven lifecycles, phases, goals and plugins here.
For our project, when
mvn package
is entered this will execute all lifecycle phases that have a goal bound to them in sequence, up to and including the package phase. In our case this means six phases are executed: process-resources
,compile
, process-test-resources
, test-compile
, test
and package
.
Each phase consists of one or more goals. Goals are provided by Maven plugins: a plugin may have one or more goals wherein each goal represents a capability of that plugin. For example, the Compiler plugin has two goals:
compiler:compile
and compiler:testCompile
.
We can use the
mvn help:describe -Dcmd=phasename
command to list the lifecycle phases along with bound goals for a project. Example:
Next, we look at each goal that is goals executed in the WAR build in more detail.
The resources:resources goal copies resources for the main source code to the main output directory.
The compiler:compile goal compiles the application source files.
The resources:testResources goal copies resources for the unit test source code to the test output directory.
The compiler:compile goal compiles the application unit test sources.
The surefire:test goal executes the unit tests of an application, on the classes in the
/target/test-classes
directory. The build stops if a unit test fails. When there are no test classes, the goal is always succesful.
The war:war goal builds a WAR file. It gathers all needed files in the
/target/myprojectname/
directory, then packages them up into myprojectname.war
. One of these steps is copying the contents of /src/main/webapp/
to/target/myprojectname/
.
Other important tasks performed by the war plugin are copying the compiled classes are copied to
WEB-INF/classes/
, and runtime dependencies to WEB-INF/lib/
. By default the WAR builder also includes two Maven descriptor files (details):- The pom file, located in the archive in
META-INF/maven/${groupId}/${artifactId}/pom.xml
- A pom.properties file, located in the archive in
META-INF/maven/${groupId}/${artifactId}/pom.properties
. During the build this file is generated in the/target/maven-archiver/pom.properties
directory.
The file looks something like this:
Finally, the
/target/myprojectname
directory is packaged up into a WAR file. In addition the pom file and the pom.properties file are put in the META-INF/maven
directory of the archive, and a manifest file is generated at META-INF/MANIFEST.MF
.
The final WAR is placed in
/target/myprojectname.war
. This WAR can now be deployed with a servlet container (e.g., Tomcat) or application server (e.g., Glassfish).
Currently, our pom.xml only has one dependency (JUnit). Let’s add another dependency and see what happens when the WAR is built.
We add this dependency to the pom:
When no scope is specified, a dependency has the compile scope (read more). The compile scope means that the dependency is available during compilation, testing, and at runtime.
Every dependency that is needed at runtime must be distributed with the application. So when we run
mvn package
with this pom, Maven downloads the log4j JAR and includes it the /WEB-INF/lib
directory in the WAR file.
No comments:
Post a Comment