Maven small note 3

124 阅读6分钟

3. Maven Dependency Management

Enterprise-level projects typically depend on a variety of open source libraries. Consider the scenario where you want to use Log4J for your application logging. To accomplish this, you would go to the Log4J download page, download the JAR file, and put it in your project’s lib folder or add it to the project’s class path. There are a couple of problems with this approach:

  1. The JAR file you downloaded might depend on a few other libraries. You would now have to hunt down all of those dependencies (and their dependencies) and add them to your project.

  2. When the time comes to upgrade the JAR file, you need to start the process all over again.

  3. You need to add JAR files to source control along with your source code so that your projects can be built on a computer other than your own. This increases project size, checkout, and build time.

  4. Sharing JAR files across teams within your organization becomes difficult.

To address these problems, Maven provides declarative dependency management. With this approach, you declare your project’s dependencies in an external file called pom.xml . Maven will automatically download those dependencies and hand them over to your project for the purpose of building, testing, or packaging.

Figure 3-1 shows a high-level view of Maven’s dependency management. When you run your Maven project for the first time, Maven connects to the network and downloads artifacts and related metadata from remote repositories. The default remote repository is called Maven Central , and it is located at repo.maven.apache.org and uk.maven.org. Maven places a copy of these downloaded artifacts in its local repository. In subsequent runs, Maven will look for an artifact in its local repository; and upon not finding the artifact, Maven will attempt to download it from remote repository.

A332298_2_En_3_Fig1_HTML.jpg

Figure 3-1 Maven dependency management

Although the architecture shown in Figure 3-1 works in the majority of cases, it poses a few problems in an enterprise environment. The first problem is that sharing company-related artifacts between teams is not possible. Because of security and intellectual property concerns, you wouldn’t want to publish your enterprise’s artifacts on Maven Central. Another problem concerns legal and licensing issues. Your company might want the teams only to use officially approved open source software, and this architecture would not fit in that model. The final issue concerns bandwidth and download speeds. In times of heavy load on Maven Central, the download speeds of Maven artifacts are reduced, and this might have a negative impact on your builds. Hence, most enterprises employ the architecture shown in Figure 3-2.

A332298_2_En_3_Fig2_HTML.jpg

Figure 3-2 Enterprise Maven repository architecture

The internal repository manager acts as a proxy to remote repositories. This allows you to cache artifacts from remote repositories resulting in faster artifact downloads and build performance improvements. Because you have full control over the internal repository, you can regulate the types of artifacts allowed in your company. Additionally, you can also push your organization’s artifacts onto the repository manager, thereby enabling collaboration. There are several open source repository managers as shown in Table 3-1.

Table 3-1 Open Source Repository Managers

Repository ManagerURL
Nexus Repository OSSwww.sonatype.com/nexus-repos…
Apache Archivaarchiva.apache.org/
Artifactory Open Sourcejfrog.com/open-source…

Using New Repositories

In order to use a new repository, you need to modify your settings.xml file. Listing 3-1 shows Spring and JBoss repositories added to the settings.xml file. In this same way, you can add your company’s repository manager.

Note

Information regarding repositories can be provided in the **settings.xml ** or the pom.xml file. There are pros and cons to each approach. Putting repository information in the pom.xml file can make your builds portable. It enables developers to download projects and simply build them without any further modifications to their local settings.xml file. The problem with this approach is that when artifacts are released, the corresponding pom.xml files will have the repository information hard coded in them. If the repository URLs were ever to change, consumers of these artifacts will run into errors due to broken repository paths. Putting repository information in the ** settings.xml ** file addresses this problem, and because of the flexibility it provides, the settings.xml approach is typically recommended in an enterprise setting.

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

    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

    .......

    <profiles>

        <profile>

            <id>your_company</id>

            <repositories>

                <repository>

                    <id>spring_repo</id>

                    <url>http://repo.spring.io/release/</url>

                </repository>

                <repository>

                    <id>jboss_repo</id>

                    <url>https://repository.jboss.org/</url>

                </repository>

            </repositories>

        </profile>

    </profiles>

    <activeProfiles>

        <activeProfile>your_company</activeProfile>

    </activeProfiles>

    .......

</settings>

Listing 3-1 Adding Repositories in settings.xml

Dependency Identification

Maven dependencies are typically archives such as JAR, WAR, enterprise archive (EAR), and ZIP. Each Maven dependency is uniquely identified using the following group, artifact, and version (GAV) coordinates:

  • groupId: Identifier of the organization or group that is responsible for this project. Examples include org.hibernate, log4j, org.springframework and com.companyname.

  • artifactId: Identifier of the artifact being generated by the project. This must be unique among the projects using the same groupId. Examples include hibernate-tools, log4j, spring-core, and so on.

  • version: Indicates the version number of the project. Examples include 1.0.0, 2.3.1-SNAPSHOT, and 5.4.2.Final.

  • type: Indicates the packing of the generated artifact. Examples include JAR, WAR, and EAR.

Artifacts that are still in development are labeled with a SNAPSHOT in their versions. An example version is 1.0-SNAPSHOT. This tells Maven to look for an updated version of the artifact from remote repositories on a daily frequency.

Dependencies are declared in pom.xml file using the dependencies tag as shown in the following:

<dependencies>

    <dependency>

        <groupId>org.hibernate</groupId>

        <artifactId>hibernate-tools</artifactId>

        <version>5.4.2.Final</version>

    </dependency>

</dependencies>

Transitive Dependencies

Dependencies declared in your project’s pom.xml file often have their own dependencies . Such dependencies are called transitive dependencies . Take the example of Hibernate Core. For it to function properly, it requires JBoss Logging, dom4j, javaassist, and so forth. The Hibernate Core declared in your pom.xml file is considered a direct dependency, and dependencies such as dom4j and javaassist are considered your project’s transitive dependencies. A key benefit of Maven is that it automatically deals with transitive dependencies and includes them in your project.

Figure 3-3 provides an example of transitive dependencies. Notice that transitive dependencies can have their own dependencies. As you might imagine, this can quickly get complex, especially when multiple direct dependencies pull different versions of the same JAR file.

Maven uses a technique known as dependency mediation to resolve version conflicts. Simply stated, dependency mediation allows Maven to pull the dependency that is closest to the project in the dependency tree. In Figure 3-3, there are two versions of dependency B: 0.0.8 and 1.0.0. In this scenario, version 0.0.8 of dependency B is included in the project, because it is a direct dependency and closest to the tree. Now look at the three versions of dependency F: 0.1.3, 1.0.0, and 2.2.0. All three dependencies are at the same depth. In this scenario, Maven will use the first-found dependency, which would be 0.1.3, and not the latest 2.2.0 version. If you want Maven to use the latest 2.2.0 version of artifact F, you need to explicitly add that version dependency to pom.xml file.

A332298_2_En_3_Fig3_HTML.jpg

Figure 3-3 Transitive dependencies

Although highly useful, transitive dependencies can cause problems and unpredictable side effects, as you might end up including unwanted JAR files or older versions of JAR files. Maven provides a handy dependency plug-in that allows you to visualize project dependency tree. Listing 3-2 shows the output of running the dependency tree goal on a sample project. You can see that the project depends on 4.11 version of JUnit JAR file. The JUnit JAR itself depends on 1.3 version of hamcrest JAR file.

[sudha]$mvn dependency:tree

[INFO] Scanning for projects...

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ gswm

[INFO] com.apress.gswmbook:gswm:jar:1.0.0-SNAPSHOT

[INFO] \- junit:junit:jar:4.11:test

[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test

[INFO] -----------------------------------------------

[INFO] BUILD SUCCESS

Listing 3-2 Maven Dependency Tree Plug-in

There are times where you don’t want to include certain transitive dependency JARs in the final archive. For example, when deploying an application inside a container such as Tomcat or WebLogic, you might want to exclude certain JAR files such as servlet-api or javaee-api as they would conflict with versions loaded by containers. Maven provides an “excludes” tag to exclude a transitive dependency. Listing 3-3 shows the code to exclude the hamcrest library from JUnit dependency. As you can see, the exclusion element takes the groupId and artifactId coordinates of the dependency that you would like to exclude.

<dependencies>

    <dependency>

        <groupId>junit</groupId>

        <artifactId>junit</artifactId>

        <version>${junit.version}</version>

        <scope>test</scope>

        <exclusions>

            <exclusion>

                <groupId>org.hamcrest</groupId>

                <artifactId>hamcrest</artifactId>

            </exclusion>

        </exclusions>

    </dependency>

</dependencies>

Listing 3-3 JUnit Dependency with Exclusion

Dependency Scope

Consider a Java project that uses JUnit for its unit testing. The JUnit JAR file you included in your project is only needed during testing. You really don’t need to bundle the JUnit JAR in your final production archive. Similarly, consider the MySQL database driver, mysql-connector-java.jar file. You need the JAR file when you are running the application inside a container such as Tomcat but not during code compilation or testing. Maven uses the concept of scope, which allows you to specify when and where you need a particular dependency.

Maven provides the following six scopes:

  • compile: Dependencies with the compile scope are available in the class path in all phases on a project build, test, and run. This is the default scope.

  • provided: Dependencies with the provided scope are available in the class path during the build and test phases. They don’t get bundled within the generated artifact. Examples of dependencies that use this scope include Servlet api, JSP api, and so on.

  • runtime: Dependencies with the runtime scope are not available in the class path during the build phase. Instead they get bundled in the generated artifact and are available during runtime.

  • test: Dependencies with the test scope are available during the test phase. JUnit and TestNG are good examples of dependencies with the test scope.

  • system: Dependencies with the system scope are similar to dependencies with the provided scope, except that these dependencies are not retrieved from the repository. Instead, a hard-coded path to the file system is specified from which the dependencies are used.

  • import: The import scope is applicable for .pom file dependencies only. It allows you to include dependency management information from a remote .pom file. The import scope is available only in Maven 2.0.9 or later.

Manual Dependency Installation

Ideally, you will be pulling dependencies in your projects from public repositories or your enterprise repository manager. However, there will be times where you need an archive available in your local repository so that you can continue your development. For example, you might be waiting on your system administrators to add the required JAR file to your enterprise repository manager.

Maven provides a handy way of installing an archive into your local repository with the install plug-in. Listing 3-4 installs a test.jar file located in the c:\apress\gswm-book\chapter3 folder.

C:\apress\gswm-book\chapter3>mvn install:install-file -DgroupId=com.apress.gswmbook -DartifactId=test -Dversion=1.0.0 -Dfile=C:\apress\gswm-book\chapter3\test.jar -Dpackaging=jar -DgeneratePom=true

[INFO] Scanning for projects...

[INFO]

[INFO] ------------< org.apache.maven:standalone-pom >---------

[INFO] Building Maven Stub Project (No POM) 1

[INFO] -------------------------[ pom ]------------------------

[INFO]

[INFO] --- maven-install-plugin:2.4:install-file (default-cli) @ standalone-pom ---

[INFO] Installing C:\apress\gswm-book\chapter3\test.jar to C:\Users\bavara\.m2\repository\com\apress\gswmbook\test\1.0.0\test-1.0.0.jar

[INFO] Installing C:\Users\bavara\AppData\Local\Temp\mvninstall5971068007426768105.pom to C:\Users\bavara\.m2\repository\com\apress\gswmbook\test\1.0.0\test-1.0.0.pom

[INFO] --------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] --------------------------------------------------------

[INFO] Total time: 0.439 s

[INFO] Finished at: 2019-09-01T00:05:21-06:00

[INFO] --------------------------------------------------------

Listing 3-4 Installing Dependency Manually

After seeing the BUILD SUCCESS message, you can verify the installation by going to your local Maven repository, as shown in Figure 3-4

A332298_2_En_3_Fig4_HTML.jpg

Figure 3-4 Dependency added to repository

Summary

Dependency management is at the heart of Maven. Every nontrivial Java project relies on open source or external artifacts, and Maven’s dependency management automates the process of retrieving those artifacts and including them at the right stages of the build process. You also learned that Maven uses GAV coordinates to identify its artifacts.

In the next chapter, you will learn about the organization of a basic Maven project.