Sunday, October 27, 2013

Programming
12 May 2012 9 Comments

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:
 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  >4.0.0
> >mygroup.com> >myprojectname> >war> >1.0-SNAPSHOT> >myprojectname Maven Webapp> >http://maven.apache.org> > > >junit> >junit> >3.8.1> >test> > > > >myprojectname> > >
To package this bare-bones project into a WAR file we use the mvn package command.
C:\Projects\myprojectname>mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building myprojectname Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
...
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ myprojectname ---
[INFO] Packaging webapp
[INFO] Assembling webapp [myprojectname] in [C:\Projects\myprojectname\target\myprojectname]
[INFO] Processing war project
[INFO] Copying webapp resources [C:\Projects\myprojectname\src\main\webapp]
[INFO] Webapp assembled in [18 msecs]
[INFO] Building war: C:\Projects\target\myprojectname.war
...
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.
Maven is used to build a WAR file from the source project, through the "mvn package" command.

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:
  >
>${project.basedir}/target> >${project.build.directory}/classes> >${project.artifactId}-${project.version}> >${project.build.directory}/test-classes> >${project.basedir}/src/main/java> >src/main/scripts> >${project.basedir}/src/test/java> > > >${project.basedir}/src/main/resources> > > > > >${project.basedir}/src/test/resources> > > ... >
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,compileprocess-test-resourcestest-compiletest 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:compileand 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:
C:\Project\myprojectname>mvn help:describe -Dcmd=package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building myprojectname Maven Webapp 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-help-plugin:2.1.1:describe (default-cli) @ myprojectname ---
[INFO] 'package' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-war-plugin:2.1.1:war
 
It is a part of the lifecycle for the POM packaging 'war'. This lifecycle includes the following phases:
* validate: Not defined
* initialize: Not defined
* generate-sources: Not defined
* process-sources: Not defined
* generate-resources: Not defined
* process-resources: org.apache.maven.plugins:maven-resources-plugin:2.5:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile
* process-classes: Not defined
* generate-test-sources: Not defined
* process-test-sources: Not defined
* generate-test-resources: Not defined
* process-test-resources: org.apache.maven.plugins:maven-resources-plugin:2.5:testResources
* test-compile: org.apache.maven.plugins:maven-compiler-plugin:2.3.2:testCompile
* process-test-classes: Not defined
* test: org.apache.maven.plugins:maven-surefire-plugin:2.10:test
* prepare-package: Not defined
* package: org.apache.maven.plugins:maven-war-plugin:2.1.1:war
* pre-integration-test: Not defined
* integration-test: Not defined
* post-integration-test: Not defined
* verify: Not defined
* install: org.apache.maven.plugins:maven-install-plugin:2.3.1:install
* deploy: org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.496s
[INFO] Finished at: Sat May 12 04:30:35 CEST 2012
[INFO] Final Memory: 5M/121M
[INFO] ------------------------------------------------------------------------
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:
    #Generated by Maven
    #Sat May 12 00:50:42 CEST 2012
    version=1.0-SNAPSHOT
    groupId=mygroup.com
    artifactId=myprojectname
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 step is packaging everything into a WAR file. The bulk of the files comes from the target/myprojectname directory (red), and some additional files are included (green).
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:
>
>log4j> >log4j> >1.2.16> >
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: