Skip to content

Latest commit

 

History

History
598 lines (502 loc) · 29.4 KB

README.md

File metadata and controls

598 lines (502 loc) · 29.4 KB

12. Maven Notes

Gradle is a great tool, but not the only one. The most popular is still Maven. So if you already know Gradle, but want to grasp the basics of the Maven, this bonus chapter is for you!

Install Maven

Use this guide to install Maven.

Also you can use package managers:

  • Windows: choco install maven
  • Mac: brew install maven
  • Ubuntu: apt install maven

To check if Maven was installed correctly: mvn --version

What is Maven

Maven is a project development management and comprehension tool with a lot of useful features:

  • builds
  • dependency management
  • documentation creation
  • site publication
  • distribution publication, etc.

Gradle vs Maven

Both Gradle and Maven has its advantages and disadvantages. However, this chapter is more focused on functionality differences, for example, how to create a project in Maven, or what build script Maven has.

Short overview

🐘 / 🪶 Gradle Maven
Installation installation is necessary for project creation, but unnecessary for starting a project due to Wrapper functionality user has to have Maven installed in order to create or start Maven project (unless this Maven project has generated Wrapper files, then user can run project without locally installed Maven)
Creation gradle init command starts menu where user can choose project options (like base language, test dependency, as well as name of the project, etc) mvn archetype:generate starts a menu where user can choose type of archetype (= type of project, like webapp, spring project, etc) and project values (like package, version, etc). User can also skip the menu, and configure all the parameters as one command, e.g.: mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
IDE support is supported by popular IDEs like Eclipse or IntelliJ IDEA is supported by popular IDEs like Eclipse or IntelliJ IDEA
Project structure typical project structure has build.gradle file, wrapper files and settings.gradle file. Source code/test/resources, etc by default are located in src directory, build output files - in build directory default project structure has pom.xml file, src directory for source code/test/resources, etc, and target directory for build output files
Configuration build.gradle file written in Kotlin or Groovy-based DSL + settings.gradle file + gradle.properties file (for configuring behavior of Gradle itself) pom.xml file written in XML. User can also specify user configuration in settings.xml file if necessary
Commands to work with the tool CLI with build-in + custom tasks (task as a main concept of some small action to execute). To run a task, use gradle <TASK> command; or ./gradlew <TASK> (Linux/Mac) and ./gradlew.bat <TASK> (Windows) to use Wrapper lifecycle phases and plugin goals as a main concept of some small action to execute. Running a phase will also execute all preceding phases. To run a phase: mvn <PHASE>, to run a specific goal: mvn <PLUGIN>:<GOAL>. To use wrapper scripts: instead of mvn, use ./mvnw for Linux/Mac or mvnw.cmd for Windows.
Plugins core and community-made plugins (collection of tasks) for extending functionality adding or reconfiguring plugins (collection of goals) to customise the build for a Maven project
Multi-project root project has settings.gradle file to specify what subprojects to include into one build (but no build.gradle file in root); each subproject has its own build.gradle file root project has a pom.xml file that lists all submodules, and its packaging type is pom. Each submodule has its own pom.xml file with parent section in it
Build output files jar, archives, test reports and other build output are automatically generated into build directory, for example, after build task all build files are generated into target directory. It is a good practice to clean this directory before each build
Wrapper wrapper files are automatically generated when creating a project with gradle init task. to run a task using wrapper, use commands ./gradlew <TASK> (Linux/Mac) or ./gradlew.bat <TASK> (Windows) you should generate wrapper files manually by using command mvn wrapper:wrapper. To run a command with Wrapper, instead of mvn, type ./mvnw (Linux/Mac) or mvnw.cmd (Windows)

Maven project creation

  • user can create new project with the help of Archetype plugin
  • in simple words, archetype is a project template
  • additive mechanism: you can use archetype on the already generated by another archetype project. For example, user generated project with the quick start archetype, and wants to create a site for that project. He can use the site archetype within that existing project

To generate a project using archetype:

  1. run the following command in the project directory: mvn archetype:generate
  2. choose the archetype from the internal catalog
  3. enter the values for the groupId, the artifactId and the version (default is 1.0-SNAPSHOT) of the project and the base package for the sources
  4. confirm

It is also possible to pass all the parameters to the command immediately, for example:

mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false

🔎 Check also: guide to naming conventions on groupId, artifactId, and version.

Default archetype is quickstart archetype.

🔎 Check also: list of archetypes.

You may want to standardize J2EE development within your organization, so you may want to provide your own archetypes. Once archetype is created and deployed in your organization's repository, it is available for use by all developers within your organization. Guide to creating Archetypes.

IDE support

Maven is integrated into Apache NetBeans IDE, Eclipse IDE and JetBrains IntelliJ IDEA.

🔎 Read more about IDE Integration.

Project structure

After running a command for creating a project:

mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false

you will see the following project structure:

my-app
├── pom.xml
└── src
    ├── main
    |   └── java
    |       └── com
    |           └── mycompany
    |               └── app
    |                   └── App.java
    └── test
        └── java
            └── com
                └── mycompany
                    └── app
                        └── AppTest.java
  • pom.xml is a file for configuring a project
  • src directory is a place for project sources (tests, source code, resources, etc)

Maven has its conventions for default project structure (however, they can be overridden via the project descriptor):

  • At the top level of any project: pom.xml
  • In addition, there can be textual documents:
    • LICENSE.txt - project's license
    • NOTICE.txt - notices and attributions required by libraries that the project depends on
    • README.txt - project's readme
  • In the directory where pom.xml is located, there are two more directories: src and (possibly) target
    • target is used to house all output of the build. For example, after you compile the sources with mvn compile, compiled classes will be placed into the target/classes directory
    • src contains all of the source materials (e.g. code, resources) for building the project, its site and so on

🔎 Check also: more about src standard layout.

Configuration

Maven configuration occurs at 3 levels:

  • Project - most static configuration occurs in pom.xml
  • Installation - this is configuration added once for a Maven installation
  • User - this is configuration specific to a particular user. You can specify them in ${user.home}/.m2/settings.xml

The Project defines information that applies to the project, no matter who is building it, while Installation and User define settings for the current environment.

🔎 See more information here.

pom.xml

A little bit on XML sytax:

  • XML documents must contain one root element:
<root>
  <child>
    <subchild>.....</subchild>
  </child>
</root>
  • All elements must have a closing tag:
<p>This is a paragraph.</p>
  • XML tags are case sensitive. The tag is different from the tag . Opening and closing tags must be written with the same case
  • All elements must be properly nested within each other, meaning that you should keep an order of closing tags according to the order of opening tags (last opening - first closing)
  • XML elements can have attributes in name/value pairs, the attribute values must always be quoted:
<note date="12/11/2007">
  <to>Tove</to>
  <from>Jani</from>
</note>
  • The syntax for writing comments:
<!-- This is a comment -->
  • Some characters (<, >, &, ', ") have special meaning and cannot be written to XML elements as they are. Those characters must be replaced by entity reference (< -> &lt;, > -> &gt;, & -> &amp;, ' -> &apos;, " -> &quot;):
<!-- Will generate error -->
<message>salary < 1000</message>

<!-- Correct -->
<message>salary &lt; 1000</message>

Now back to pom.xml.

A Project Object Model or POM is an XML file that contains information about the project and configuration details used by Maven to execute a task or goal.

Each project has its POM, but there is also the Super POM - Maven's default POM. All POMs extend the Super POM unless explicitly set, meaning the configuration specified in the Super POM is inherited by the POMs you created for your projects.

The minimum required elements for a POM are:

  • project - root
  • modelVersion - should be set to 4.0.0
  • groupId - the id of the project's group
  • artifactId - the id of the artifact (project)
  • version - the version of the artifact under the specified group
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>

The last three values form the project's fully qualified artifact name in the form of <groupId>:<artifactId>:<version>.

If the configuration details are not specified, Maven will use defaults or inherit from the Super POM:

  • if packaging type is not specified in the POM, then the default value jar would be used
  • if repositories were not specified, Maven will use the Super POM repositories configuration

List of elements configurable in POM:

<project 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <!-- The Basics -->
  <groupId>...</groupId>
  <artifactId>...</artifactId>
  <version>...</version>
  <packaging>...</packaging>
  <dependencies>...</dependencies>
  <parent>...</parent>
  <dependencyManagement>...</dependencyManagement>
  <modules>...</modules>
  <properties>...</properties>
 
  <!-- Build Settings -->
  <build>...</build>
  <reporting>...</reporting>
 
  <!-- More Project Information -->
  <name>...</name>
  <description>...</description>
  <url>...</url>
  <inceptionYear>...</inceptionYear>
  <licenses>...</licenses>
  <organization>...</organization>
  <developers>...</developers>
  <contributors>...</contributors>
 
  <!-- Environment Settings -->
  <issueManagement>...</issueManagement>
  <ciManagement>...</ciManagement>
  <mailingLists>...</mailingLists>
  <scm>...</scm>
  <prerequisites>...</prerequisites>
  <repositories>...</repositories>
  <pluginRepositories>...</pluginRepositories>
  <distributionManagement>...</distributionManagement>
  <profiles>...</profiles>
</project>

🔎 To get more details on these elements, check this page.

The Basics:

  • packaging - default: jar, other available: pom, jar, maven-plugin, ejb, war, ear, rar
<project 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  ...
  <packaging>war</packaging>
  ...
</project>
  • dependencies element is a list of dependencies. Each dependency contain information about it, like groupId, artifactId, version, type, scope, etc:
<project 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  ...
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <type>jar</type>
      <scope>test</scope>
      <optional>true</optional>
    </dependency>
    ...
  </dependencies>
  ...
</project>
  • properties element is for value placeholders. Those values are accessible anywhere within a POM by using the notation ${X}, where X is the property:
<properties>
    <jackson.version>2.10.2</jackson.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-yaml</artifactId>
        <version>${jackson.version}</version>
    </dependency>
 <dependencies>

Or they can be used by plugins as default values. For example, the Compiler Plugin can be configured to compile a certain project to a different version than what you are currently using. If you want to use the Java 8 language features (-source 1.8) and also want the compiled classes to be compatible with JVM 1.8 (-target 1.8), you can either add the two following properties, which are the default property names for the plugin parameters:

<project>
  [...]
  <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  [...]
</project>

or configure the plugin directly:

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

🔎 More on The Basics here.

Build Settings:

Those settings consists of the build element, that handles things like declaring your project's directory structure and managing plugins; and the reporting element, that largely mirrors the build element for reporting purposes.

  • BaseBuild is the base set of elements:
<build>
  <defaultGoal>install</defaultGoal>
  <directory>${basedir}/target</directory>
  <finalName>${artifactId}-${version}</finalName>
  <filters>
    <filter>filters/filter1.properties</filter>
  </filters>
  ...
</build>

defaultGoal: the default goal or phase to execute if none is given directory: build output directory finalName: the name of the bundled project when it is finally built filter: defines *.properties files that contain a list of properties that apply to resources

🔎 More on Build Settings here.

More Project Information:

Here is where you can configure description of the project, its url, as well as licenses, developing organization and developers themselves.

🔎 More on More project information here.

Environment Settings:

Settings for managing project environment, for example, continuous integration build systems, mailing lists, repositories for plugins or dependencies, etc.

🔎 More on Environment Settings here.

Commands to work with the tool

Maven is based around the concept of a build lifecycle - process for building and distributing a particular project.

There are three built-in build lifecycles:

  1. default - main lifecycle, handles your project deployment
  2. clean - handles project cleaning
  3. site - handles the creation of your project's web site

Each of these build lifecycles consists of phases. In other words, a phase is a stage of a build lyfecycle. They are sequential (have a specific order). So if we execute one phase, all the preceding phases will be executed as well (for example, if we run the deploy phase, which is the last phase in the default build lifecycle, it’ll execute all the phases before the deploy phase as well, which is the entire default lifecycle).

To run a phase, use command mvn <PHASE>, for example, mvn deploy. Note that you can also use wrapper scripts to run commands: instead of mvn, use ./mvnw for Linux/Mac or mvnw.cmd for Windows.

Some of the most popular phases of default lifecycle (in order):

  • validate - validate the project is correct and all necessary information is available
  • compile - compile the source code of the project
  • test - test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
  • package - take the compiled code and package it in its distributable format, such as a JAR
  • verify - run any checks on results of integration tests to ensure quality criteria are met
  • install - install the package into the local repository to use as a dependency in other projects locally
  • deploy - done in the build environment, copies the final package to the remote repository for sharing with other developers and projects

Clean lifecycle:

  • clean - remove the target directory with all the build data

Site lifecycle:

  • site - generate a web site for your project

🔎 Check also: full list of build phases of 3 lifecycles.

So if you run mvn verify, it will execute every preceding phase before executing verify. And command mvn deploy will execute the entire default lifecycle.

It is possible to combine phases of different lifecycles in one command: mvn clean deploy - Maven traverses into every subproject and executes clean, then executes deploy (including all of the prior build phases).

Each phase is a sequence of goals, so each goal is responsible for a specific task. When we run a phase, all goals are executed in order. Goals are part of the plugins, so to execute one goal, you need to mention a name of a plugin, too (in a format <PLUGIN>:<GOAL>).

For example, package phase contains 2 goals: jar:jar and war:war. compile phase consists of compiler:compile goal.

To list all the goals bound to the specific phase: mvn help:describe -Dcmd=<PHASENAME>

A goal may be bound to zero or more build phases. A goal not bound to any build phase could be executed outside of the build lifecycle by direct invocation.

To run a specific goal without executing its entire phase (and the preceding phases), use the command: mvn <PLUGIN>:<GOAL>

Note: the phases named with hyphenated-words (pre-*, post-*, or process-*) are not usually directly called from the command line, since they are producing intermediate results that are not useful outside the build.

An interesting thing to note is that phases and goals may be executed in sequence.

mvn clean dependency:copy-dependencies package

This command will clean the project, copy dependencies, and package the project (executing all of the phases up to package, of course).

How to bind goals to phases?

  1. With packages. Each packaging contains a list of goals to bind to a particular phase. For example, jar packaging will bind the following goals to the phases of default lifecycle:
Phase Plugin:Goal
process-resources resources:resources
compile compiler:compile
process-test-resources resources:testResources
test-compile compiler:testCompile
test surefire:test
package jar:jar
install install:install
deploy deploy:deploy

🔎 You can check default bindings for different packages here.

  1. With plugins. Plugin itself is a collection of goals, so by configuring plugins, you can add goals to the build. You need to add the plugin to the POM in the <plugins> section of <build>.

For example, let's say you have a goal display:time that echos the current time to the commandline, and you want it to run in the process-test-resources phase to indicate when the tests were started. This would be configured like so:

 <plugin>
   <groupId>com.mycompany.example</groupId>
   <artifactId>display-maven-plugin</artifactId>
   <version>1.0</version>
   <executions>
     <execution>
       <phase>process-test-resources</phase>
       <goals>
         <goal>time</goal>
       </goals>
     </execution>
   </executions>
 </plugin>

Plugins

🔎 List of available plugins.

A Maven plugin is a group of goals; however, these goals aren’t necessarily all bound to the same phase. To list all goals in a specific plugin: mvn <PLUGIN>:help.

To see a description of the plugin and its parameters and types:

mvn <PLUGIN>:help -Ddetail -Dgoal=<GOAL>

There are two types of plugins, build and reporting:

  • Build plugins are executed during the build and configured in the <build/> POM element.
  • Reporting plugins are executed during the site generation and configured in the <reporting/> element.

All plugins should have minimal required information: groupId, artifactId and version.

Ways to use a plugin:

  • add it to the POM and use it as it is
  • add and configure the plugin as needed:
    • as described in the previous section, it is possible to add new goals to the process, configure specific goals, bind them to phases
    • or you can configure specific parameters, that will be applied to all the goals of the plugin

For example, to configure the Java compiler to allow JDK 5.0 sources, add configuration element, which applies the given parameters to every goal from the compiler plugin:

...
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.3</version>
      <configuration>
        <source>1.5</source>
        <target>1.5</target>
      </configuration>
    </plugin>
  </plugins>
</build>
...

About pluginManagement element: You can add and configure plugins either in plugins section of the POM, or in pluginManagement section. The difference is that by adding the plugin in the pluginManagement section, it becomes available to this POM, and all inheriting child POMs. Then in child POM, you don't need to duplicate the configurations.

🔎 You can read more about configuring plugins here.

Multi-project

  • A multi-module project contains an aggregator POM (the POM where all submodules listed), and the submodules (projects).
  • Each project can be build separately, or together through the the aggregator POM.
  • Each project has its own POM, that can inherit some shared configurations from aggregator POM.

How to create a multi-module project:

  1. generate root project:
mvn archetype:generate -DgroupId=com.example -DartifactId=parent-project
  1. change packaging type to pom so it won't produce any further artifacts and will serve as parent and aggregator POM:
<packaging>pom</packaging>
  1. go to the directory of root project (in this case to parent-project directory), and create submodules you need with the help of archetype:generate goal (note that you can add more parameters to the command depending on your project):
cd parent-project
mvn archetype:generate -DgroupId=com.example -DartifactId=service
mvn archetype:generate -DgroupId=com.example -DartifactId=webapp
  1. done!

Since packaging type of parent-project is pom, Maven will recognise all new created projects (service and webapp) as submodules. pom.xml files will be modified accordingly:

  • parent-project pom.xml file:
<modules>
    <module>service</module>
    <module>webapp</module>
</modules>
  • service and webapp pom.xml files:
<parent>
  <artifactId>parent-project</artifactId>
  <groupId>com.example</groupId>
  <version>1.0-SNAPSHOT</version>
</parent>

To build all projects at once, go to the parent directory and run:

mvn package

If you want to share dependencies in all submodules, use dependencyManagement section in root pom.xml (in this case, in POM located in parent-project directory):

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.16</version>
        </dependency>
        //...
    </dependencies>
</dependencyManagement>

Now, if you want to inherit this dependency by any submodule, you can simply specify groupId and artifactId parameters in submodule's POM (version and scope will be inherited):

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
    //...
</dependencies>

If you specify version parameter here, too, then parent's version will be overridden, and this specific submodule will use specified version.

Build output files

All the files generated by build are located in target directory. For example, target/classes contains compiled classes, similarly, target/test-classes contains compiled classes used for tests. Package file (e.g.: .jar) will be generated directly into the target directory. What gets generated in it during the build depends only on POM and how your build is happening.

Those files are temporary, so it is a good practice to run mvn clean to delete them before each build (not doing it can lead to weird compile behaviour, like being able to compile code that shouldn't be).

There is a difference between temporary and final build artifacts:

  • mvn package command will generate a file to target directory. Name of the file can be either custom, set within the POM, or default (composed from Maven coordinates of the project). This file will contain compiled classes, you can send it to anybody, but in general it is a temporary file.
  • mvn install will install the package to the local repository (~/.m2/repository/), and you will be able to use this package as a dependency in other projects locally. This file is not temporary, and it contains generated artifacts + project's POM. Name of this file cannot be custom. For example, a project with a group id of my.groupid, an artifact id of my-artifactid and a version of 1.0 will get installed in the folder my/groupid/my-artifactid/1.0; in which you'll find the POM file, and all the other artifacts. The name of the artifacts themselves cannot be overridden: it will be my-artifactid-1.0.jar for a JAR file.

Maven Wrapper

Wrapper provides a fully encapsulated build setup. To add or update all the necessary Maven Wrapper files to your project, use the command: mvn wrapper:wrapper

Wrapper files will appear. And now, to run a command, you can use wrapper scripts: ./mvnw (Linux/Mac) or mvnw.cmd (Windows). For example:

./mvnw clean install 

So if the user doesn't have the necessary version of Maven specified in .mvn/wrapper/maven-wrapper.properties, it will be downloaded, installed and then used. Subsequent uses of mvnw/mvnw.cmd will use the previously downloaded specific version as needed.

⬅️ BACK TABLE OF CONTENTS NEXT ➡️