It is common for your project's dependencies to in turn have dependencies of their own. HttpUnit, for instance, depends on six other libraries. To use HttpUnit in Maven 1.x you would have to add to your project's POM not only HttpUnit but also the six libraries on which HttpUnit in turn depends. Maven 2.0 recursively analyzes the POMs of your project's dependencies and automatically includes their dependencies in your project. Transitive dependencies will greatly simplify managing project dependencies, as you will see shortly.
Although the transitive dependency mechanism is extremely helpful in simplifying the build process, it brings with it a few challenges. For example, what about cyclical dependencies in the dependency graph, and what if there are version conflicts in some of these dependencies? Maven 2.0 provides dependency mediation techniques, conflict management schemes, and reporting options to address these challenges. More information on these advanced features of transitive dependencies can be found on the Maven 2.0 Web site.
When building enterprise applications you often need different libraries for compiling, running, and testing. For example, you may want to include the HttpUnit testing library at test time but exclude it from the runtime classpath. In Maven 2.0 this ability to restrict which classpath a dependency is added to is referred to as dependency scoping.
There are four dependency scopes in Maven: compile, test, runtime, and provided. Compile-scoped dependencies are available on all classpaths, runtime-scoped dependencies on the runtime and test classpaths, and test-scope dependencies only on the test compilation and execution classpaths. The provided scope is intended for dependencies such as servletapi that are necessary for compilation but are expected to be provided by a container. Dependencies with a provided scope are excluded from artifact packaging. The scope for your dependencies is specified along with the dependency in your POM.
Greeter ApplicationIteration 2
I want to enhance the greeter application from the last section by writing an HttpUnit test for the GreeterServlet, which will allow you to test both transitive dependencies and dependency scoping. I'll start by adding HttpUnit as a test-scoped dependency to my greeter-web project. To accomplish this, add the following to the greeter-web's POM:
Now you can add the GreeterServletTest class to the test source directory of the greeter-web project:
public class GreeterServletTest extends TestCase
private ServletUnitClient client;
public GreeterServletTest() throws IOException, SAXException
ServletRunner sr = new ServletRunner();
client = sr.newClient();
public void testGreeting() throws MalformedURLException, IOException, SAXException
WebResponse response = client.getResponse("http://localhost/greeter");
String greeting = ((TextBlock) response.getTextBlocks()).getText();
assertEquals("Incorrect greeting returned", "Bonjour, tout le monde!", greeting.trim());
To rebuild your application run 'm2 install' from the top-level directory. HttpUnit and its dependencies will be automatically discovered, downloaded, and added to the test classpath. Your new unit test will be executed before the WAR is packaged. Open the generated WAR file and notice that HttpUnit and its dependencies are not included in the artifact. Now change the scoping of HttpUnit to 'compile' and run it again. Notice the additional JARs added to the WEB-INF/lib directory.
Maven knew which dependencies to download for HttpUnit because they were specified in the POM for HttpUnit; you can see this at http://www.ibiblio.org/maven2/httpunit/httpunit/1.6/httpunit-1.6.pom . Not all libraries stored in the mirrored remote repositories have their dependencies defined. When adding a library to your applications it is always a good idea to check the POM of the library in question.
Most projects follow a similar compile, test, and deploy build cycle. Maven 2.0 encapsulates these similarities in the form of a defined lifecycle, which consists of a series of stages. The basic stages are:
(For a complete list of these phases see the Maven 2.0 Web site
During each phase you will have various tasks that you would like performed. For instance, in the generate-sources stage you might like to run XDoclet to generate some code. And in the integration-test phase you might like to deploy an EAR to an application server and run integration tests against it.
How do you control which tasks get executed in each phase of the lifecycle? The answer is by binding plugin goals to lifecycle phases. First, you can bind the execution of a plugin goal to a particular lifecycle phase by specifying the plugin, phase, goal, and additional configuration in your POM. So, for example, you could bind the deploy-ejb goal of the JBoss plugin to the integration-test lifecycle phase in order to deploy an EAR prior to testing.
Because most types of projects share many of the same basic tasks, each packaging type (JAR, WAR, etc.) specifies a default set of plugin bindings. As an example, the default plugin bindings for a JAR project are illustrated in Table 1. As the build progresses through each of the phases the plugin goals associated with that phase are executed.
Table 1. Default JAR lifecycle plugin bindings.
By default, the goals will be executed in their corresponding lifecycle phase for a JAR project.