A. introduction

Most Java applications today are organized through Maven, whether distributed or single cluster applications often through a parent POM plus several child POM to complete the project organization. However, this multi-application and multi-module separation comes with a huge physical cost – outsourcing

Here’s an example of why this is the case:

In the figure above, there are two applications portal and dump. The four packages of portal need to be referenced, that is, the client, Domain, common, and log packages are shared by the two applications. And sharing inevitably leads to competition!

Simple analysis has the following problems:

  1. Multiple application publishing ** : ** Adding classes and methods to the domain in dump will inevitably cause the Portal application to be published once, merging the code to the baseline

  2. ** Version error: ** The snapshot version number is not the same when developing multiple branches, so you are either handling conflicts or on the way to handling conflicts

  3. ** Online package swap: ** Before the release of the application, all code should be cut into the same official version, and all references to the version in the code should be replaced one by one

ß.Maven Dependency Mechanism

To address the above issues, what can be done to make the process of frequent outsourcing, version replacement, and conflict resolution easier and easier to automate? In a nutshell, my idea is centralized version control! If that sounds familiar, and it’s the opposite of what git is known for, here’s a look at how to grace your process and how to climb back up after stepping into some of Maven’s big holes.

Before we do that, let’s take a look at how modules and packages are organized in a Maven project.

First create a Maven project and then complete the creation of a new module in the three steps shown above.

As a result you get one parent POM and two child POMs as shown in the figure above.

2.1 father and son POM

The core contents of the parent POM are as follows:

There are two parts: one is the declaration of the parent POM, which contains the GAV coordinates and must be packaged as POM because you need to use the aggregation model, and the other is the submodule tag of the parent project management.

Subpom is relatively simple:

If you are careful, you may notice that it does not write GroupId and Version because the parent project is already declared. If there is no specific Version number and GroupId requirement, it inherits from the parent project directly.

2.2 Dependency passing

Maven supports dependency inheritance in the parent POM to prevent us from manually specifying the version of the dependency library. However, passing dependencies can cause the dependency graph to grow very large rapidly, so Maven has certain restrictions on passing dependencies:

  • When multiple versions of components are dependent, Maven will only choose one version as the dependency, and the policy chosen is called nearest Definition shortest path

  • Automatic dependency introduction: When A depends on B and C depends on A, then C automatically introduces B

  • Dependency exclusion: This is easy to understand, if you don’t want to introduce some automatically introduced dependencies you can remove them by means of dependency exclusion

2.3 Dependency Range

The scope of dependencies determines when they are loaded, which is especially useful for operations such as Jar sizing, and also for resolving dependency conflicts

  • Compile is the default, meaning that dependencies that are not written to scope are loaded into the classpath at compile and run time

  • Provided is very similar to compile except that it’s only loaded during compile and test, not at run time. For example, we often use the Servlet API. This JAR is only needed to compile the test. Tomcat already has this JAR for us at run time, and if it is added, it may cause class conflicts

  • Runtime This range means that dependencies are not required at compile time, but are required at execution time, such as a database driver

  • Test is basically a bunch of jars that you can run tests on

  • System is the same as provided in terms of participation, except that dependencies are not captured from maven repositories, but from the local file system, which must be used with the systemPath property.

The current project is A, A depends on B, B depends on C. If WE know the scope of B in project A, how do we know the scope of C in PROJECT A? This needs to be determined according to a nexus table:

Let’s say A depends on B provided, B depends on C runtime and finally A depends on C provided

C. hole

Going back to the problem we raised at the beginning, if there are three people on the team working on the same application, everyone needs to change the version number of the two packages, and the branch merge is bound to conflict. Applications that refer to this two-sided package are also bound to conflict because everyone uses a different version number, so whose version is the one that counts? Who will resolve this conflict? It’s often the case that a half hour application can’t be built because of version number conflicts.

At the same time, when the release of online to change the package to the official package, need to replace a lot of places, everyone’s version also need to be consistent, often need to solve multiple places version conflict.

To solve this problem, I adopted the following solution:

  1. When developing in the same environment, the version number will always be the same. For example, your package version must be pre0-snapshot or the branch will not commit

  2. All package versions are bundled into the main POM, disallowing separate declarations in each POM of binary packages to be published or dependent on

Before and after the transformation, the main POM looks as follows:

Instead of declaring a separate version number in the child POM, the child POM directly inherits the version number defined in the parent POM:

This worked fine for both of the above issues, but one deployment run into a very weird problem.

Our project structure is as follows:

ProjA | - Apache Commons 3.0 | _____ | Proj B 's Client | | - mq - Client | | - redis - Client | | -- etc. | | ______ Server | -- Server Libraries | -- etc.Copy the code

Project A refers to project B’s client package, and its client package introduces mq and Redis clients, so project A can directly use the classes in these two packages without introducing these two packages. However, in the process of A deployment, project A could not find the mq and redis class files, which makes people confused, the online can be all ok, why the pre-release has this problem??

Partial roots.

It’s time for the tense and exciting problem finding. I downloaded the latest compiled package from the MVN repository and put it into jad, and found that the code was consistent with my branch, no problem, and the timestamp after the snapshot package was also the timestamp I published the package.

That is, there is no problem in the process and result of sending the package. It must be something wrong when pulling the package. Let’s see if there is any abnormality in the process of pulling the package.

mvn clean && mvn install -fn 
Copy the code

A set of commands run down, there seems to be no error, but the packet is not pulled down. Let’s see what’s inside the log! A log search found a waring log: The dependency package introduced by the application is invalid, and the dependencies passed in the dependency package are unavailable. You can enable debug to obtain more information.

[WARNING] the POM for A is invalid, transitive dependencies (if any) will not be available, enable debug logging for more details...
Copy the code

When maven debug is enabled, the warning is followed by an error message, as follows.

[WARNING] The POM forxx:jar:1.0 -snapshot is invalid, transitive dependencies (if any) will not be available: 2 Problems were encountered while building the effective model for XX: 1.0-snapshot [ERROR] 'dependencies.dependency.version' for xx:jar is missing. [ERROR] 'dependencies.dependency.version' for xx:jar is missing.Copy the code

I have a problem with transitive dependencies, but I have a problem with transitive dependencies. I have a problem with transitive dependencies, but I have a problem with transitive dependencies.

But why is this a problem? I found the answer in StackOverflow based on the keyword reported above:

One reason for this is when you rely on a project for which the parent pom is outdated. This often happens if you are updating the parent pom without installing/deploying it.

To see if this is the case, just run with mvn dependency:tree -X and search for the exact error. It will mention it misses things you know are in the parent pom, not in the artifact you depend on (e.g. a jar version).The fix is pretty simple: install the parent pom using mvn install -N and re-try

In a few short sentences above, both the reasons and the solutions are given. This is because the parent POM of the two-sided package is using an older version of the POM that does not contain some of the jar packages that are passed on. The solution is as simple as adding the dependent version numbers from the parent POM and repackaging them.

Recalling the component delivery dependencies mentioned above, the redis and MQ client packages that are dependent on the two-party package are not dropped because the version number of a JAR in the two-party package POM is not defined in either the parent POM or the two-party POM. When looking for a component dependency, the two-party package will first look in this POM, and if not, it will look in the POM

<parent> <artifactId>module-test</artifactId> <groupId>org.example</groupId> <version> 1.0-snapshot </version> </parent>Copy the code

The version number of the declared parent POM is found in the parent POM, because the old version of the parent POM does not have the version number of the package, so the error was reported.

So be sure to redistribute the parent POM if you are releasing a new two-party package and want to use the pass dependency feature!