catalogue
Dependent storage place: repository (warehouse)
Find dependencies through pom files
Dependency version conflict and dependency mediation
Simplification of Dependency Management: Dependency Management
Dependency management is a very important part of project management. Almost any project development needs to use the library. These libraries are likely to rely on other libraries, so the dependency of the whole project forms a tree structure. With the extension and expansion of the dependency tree, a series of problems will arise. Maven provides a complete set of methods that allow users to easily manage project dependencies.
Dependent storage place: repository (warehouse)
Review the previous knowledge:
Dependencies and plug-ins can be collectively referred to as artifacts
The place where components are stored in Maven is called a Repository.
In the configuration file, use the < repository > tag declaration for the dependent repository and the < pluginrepository > tag for the plugin repository. Unless otherwise specified, the repository mentioned below includes both.
Warehouses can be roughly divided into two types
The local repository is the repository of the local computer. When a project declares that it needs a component, it will first look for it in the local repository and cache the components previously downloaded in the central repository or remote repository.
Remote repository is the repository on the remote server. When the component cannot be found in the local repository, it will first download the component to the local repository in the remote repository. Remote warehouses can be roughly divided into three categories.
(1) In the central repository, most components are stored in the central repository. Some online blogs will separate the central warehouse from the remote warehouse, but according to the official documents, the central warehouse should belong to the remote warehouse. However, this central warehouse is special. Maven returns to the central warehouse to find components without any configuration.
(2) The repository of third-party libraries that can be accessed on the Internet, such as the repository of JBoss.
(3) Internal repository is an internal repository built by a group or company for internal access, which is used to store some internally used components and cache components downloaded from the central warehouse or other warehouses.
It should be noted that the above division of remote warehouses is for the convenience of readers. The configuration files are all configured in the same way. In essence, they are all remote repositories.
As an analogy, if a project is compared to a dish, the recipe of the dish corresponds to the pom file of the project. The recipe writes what ingredients the dish needs, just as the pom file states what components the project needs. The local Repository is the place where food materials are stored at home, and the remote Repository is the vegetable market. If you already have ingredients for cooking at home, you can start cooking directly. However, if not, you need to go to the vegetable market to buy it at home (download it from the remote Repository to the local Repository). One difference is that the same component can be reused by multiple projects, but one food material can only make one dish, and another dish needs the same food material, and you need to go to the vegetable market to buy it.
If multiple repositories are configured in multiple places, Maven will access these repositories in a certain order when downloading components until the required components are found in a Repository.
If there are multiple configurations, access them in the following order.
1. Valid setting XML (binding profile configuration required)
- Global configuration file setting xml
User profile setting xml
2. Valid POM file
- The POM file of the project itself (you can bind the profile configuration or not).
- The parent POM file of the project POM file. If the parent POM file also has its own parent POM file, recursively find the Repository.
- Super POM file, which is a default POM file. All POM files will implicitly inherit this POM file.
3. The project depends on POM files on the path
Note: the above profile tab is mainly used to switch different configurations in different operating environments, which will not be described in detail here.
If there are more than one Repository in one configuration, search according to the declared order of the Repository.
java - How to set order of repositories in Maven settings.xml - Stack Overflow(
Here is setting There seems to be a bug in the reading order of the repository in XML.
Maven in version 2.x is searched according to the dictionary order of id, 3 Version x fixes this bug and looks for it in the declared order.)
Note that the super POM file is loaded by default without any configuration. The declaration in the super POM file is as follows. The POM declares a central warehouse, that is, a repository with id central. Therefore, the principle of finding components in the central warehouse by default is that the central warehouse is declared in the super POM. This also shows that the central warehouse is a special remote warehouse rather than a warehouse independent of the remote warehouse.
Find dependencies through pom files
Each project in maven has a POM file, which will be uploaded when the project is packaged and uploaded to the repository. When downloading, the POM file and jar package will be downloaded together. As shown in the picture, Jakarta mail-1.6. 4. Jakarta. Jar mail-1.6. 4.pom.
After maven downloads the dependency, it will read the dependent pom file and download the dependency declared in the pom file.
For example, com. Com is declared in the pom file sun. activation. jakarta. Activation, so maven will continue to download com sun. activation. jakarta. Activation is a dependency. The downloaded version is determined by other methods, which will be described in detail later.
With the increasing dependence on projects, a series of problems will arise. The typical two are dependency version conflict and circular dependency.
Cyclic dependence
Circular dependency refers to the direct or indirect interdependence between project dependencies. For example, a depends on b, b depends on c, and c depends on a.
As shown in the figure below
If there is circular dependency in the project, maven will report an error, because the occurrence of circular dependency indicates that the project structure is unreasonable and needs to be adjusted by the developer.
Dependency version conflict and dependency mediation
Dependency version conflict means that there are two versions of the same dependency on the project dependency path. As shown in the figure below, A depends on B, while B depends on 1.1 Version 0 of D. At the same time, A depends on C, and C depends on 1.2 Version 0 of D. However, only one version of D can be used. maven provides A mechanism to determine which version of D to use, that is, Dependency mediation
The so-called dependency mediation is actually very simple. Determine which version of dependency to use according to the following two rules.
1. The nearest principle is to take the latest version on the dependent path. For example, the following cases, because A to D1 If the path of 0 is the closest, take D1 0 is the version used.
2. If multiple versions meet the nearest principle, the version on the path first declared is taken. For example, A to D1 below 0 and D2 0 is the same distance, but because D2 The path of 0 is declared first, so D2 is taken 0
2. If multiple versions meet the nearest principle, the version on the path first declared is taken. For example, A to D1 below 0 and D2 0 is the same distance, but because D2 The path of 0 is declared first, so D2 is taken 0
Developers can also use POM XML declares the dependency on D to determine the version of D. at this time, the dependency path is as follows, so this meets the nearest principle.
Dependency delivery
Dependency passing refers to the dependency on which the project depends. It sounds very tongue twister. For example, if project A depends on B and B depends on C, then normally A depends on C. maven provides some methods for A to avoid dependence on C, which can simplify the dependency path of the project.
These methods can be summarized as follows:
(1) Dependent scope
(2) optional and exclusion keywords.
Dependent scope
There is a scope configuration item under depedency, which is used to configure the dependent scope.
There are four desirable values for scope. Scope limits the delivery of dependencies, which is a part of maven's management of dependency delivery. Only the dependencies of a specific scope will participate in dependency delivery, which reduces dependency conflicts.
scope has the following values:
compile (default), test, runtime, provided, system, import.
import is related to dependency management, which will be described later. At present, the following five scopes are mainly introduced: compile (default), test, runtime, provided and system.
compile imports dependencies when compiling, running, and testing.
The following describes the functions of each scope and the corresponding application scenarios
be careful:
If dependencies are introduced during compilation, the classes under the dependency package will not report compilation errors in the source code. On the contrary, the IDE will report compilation errors.
If dependencies are introduced at runtime, you can use class The forname () method loads the classes under the dependency package, otherwise it will throw
java.lang.ClassNotFoundException exception.
If dependencies are introduced during testing, the classes under the dependency package will not report compilation errors in the test code, and can class Forname() loads the class under dependency.
The default scope of compile. This dependency will be introduced during compilation, running and testing. Generally, this scope is used.
Test will import dependencies only when testing, such as juni library. If some dependencies are only used in testing, such as some test libraries, you need to set their scope to test.
The runtime will not import dependencies at compile time, but will import dependencies at run time. For example, various database driven jdbc implementation libraries will not be used when compiling, but jdbc will dynamically load and use these libraries when running.
The provided scope will introduce dependencies during compilation and testing, but not during runtime. Some libraries are provided by the running environment. In order to avoid conflicts with existing libraries in the running environment, you need to set the scope to provided. For example, the servlet Library in the java web. The tomcat container comes with this library, so when writing a web project, you need to declare the scope that the servlet depends on as provided.
The scope of system is similar to that of runtime, except that the current system provides the dependencies required for project operation, such as the sdk of android. It is generally not recommended, because the dependency may only be provided by a specific system, so it cannot run on other systems, and the code portability is poor.
Introducing relevant classes into the source code will not report compilation errors, but ClassNotFound exceptions will be reported if the class is loaded at runtime.
This is a pit. If a novice is not familiar with Maven's scope and a dependency of the project is set to provided (for example, the dependency of someone else's configuration file is copied on the Internet), a situation will occur. The project can be compiled normally, but a ClassNotFound exception will be thrown when running.
Next, use the provided scope as a demo to show the dependent scope.
servlet dependency is introduced into the project, and the scope is set to provided.
The Cookie class of the servlet initialized in the code can be compiled normally, but ClassNotFound exception will be reported when running.
The test code can use the Cookie class normally.
The relationship between the scope and when it will be introduced is as follows:
Compile time | Runtime | When testing | |
compile | √ | √ | √ |
test | × | × | √ |
runtime | × | √ | √ |
provided/system | √ | × | √ |
Transfer of scope: if project a depends on project b with scope x, project b depends on project c with scope y, then the scope z of project a's dependence on project c depends on X and y. The following table describes the z generated by different X and y. Where the column is the value of X and the row is the value of Y.
Transfer of scope: if project a depends on project b with scope x, project b depends on project c with scope y, then the scope z of project a's dependence on project c depends on X and y. The following table describes the z generated by different X and y. Where the column is the value of X and the row is the value of Y.
compile | provided | runtime | test | |
compile | compile(*) | - | runtime | - |
provided | provided | - | provided | - |
runtime | runtime | - | runtime | - |
test | test | - | test | - |
Note: the official document explains compile(*). Generally, it should be runtime, but for a special envoy case: the class of b inherits a class or interface in c. in order to compile, you need to set the value of the scope to compile.
It can be summarized in a few sentences:
1. When the scope of a's dependency on b is provided or test, no dependency will be passed.
2. When the scope of a's dependence on b is compile or runtime, the scope of a's dependence on c is determined by the scope of b's dependence on c, but compile will generally be degraded to runtime.
3. Under special circumstances, the scope of a's dependence on c will be compile. Several conditions need to be met:
(1) The scope of a's dependence on b is compile
(2) The scope of B's dependence on c is compile
(3) The class of B inherits a class or interface in c.
Optional
The official document of the optional usage scenario describes this: A project A provides some features that are sometimes not used, and these features cannot be divided into sub modules, and these features need to use some specific dependencies, such as B. Then the project can set the value of these dependent options to true. The Demo in the official document is as follows:
<project> ... <dependencies> <!— Statement pair B Dependencies are optional --> <dependency> <groupId>sample.ProjectB</groupId> <artifactId>Project-B</artifactId> <version>1.0</version> <scope>compile</scope> <optional>true</optional> </dependency> </dependencies> </project>
Now there is a project X that depends on A. It is recorded that although the pom of a declares that the scope of the dependency on Project-B is compile, a will not pass the dependency on B. if x needs to use these features in a, it needs to explicitly declare the dependency on B in the pom file.
Example: for example, an ORM framework supports connecting different databases mysql, Oracle and SqlServer. This framework has a dependency on the jdbc libraries of these databases, but when other projects use this framework, only one database is connected. In order to avoid introducing unnecessary libraries, the dependency on the jdbc libraries needs to be declared as optional in the framework's pom file.
Exclusions
The above example is considered from the perspective of project A, but sometimes A does not declare the dependency on B optional. It may be because the features that depend on B are useful most of the time. For the convenience of users, there is no declaration, or it may be for other reasons.
At this time, from the perspective of project x, it needs to use project A. However, some features that depend on B do not need to be used, so it does not need to rely on project B. at the same time, X needs to eliminate the dependence on B for some reasons, such as B has some security problems or to save project space. At this time, X needs to use exclusions to exclude dependence on B. The official DEMO is as follows:
<project> ... <dependencies> <dependency> <groupId>sample.ProjectA</groupId> <artifactId>Project-A</artifactId> <version>1.0</version> <scope>compile</scope> <exclusions> <exclusion> <!—Set dependency exclusion here --> <groupId>sample.ProjectB</groupId> <artifactId>Project-B</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </project>
Simplification of Dependency Management: Dependency Management
If a project has multiple modules, each module has a common dependency. If the dependent version is declared in multiple modules, it is troublesome to manage and maintain. Therefore, maven provides a method to centralize the dependency configuration information: dependency management. The following is an official Demo to illustrate the use of dependency management.
The dependency management element can declare the configuration information about dependencies, but it will not be introduced. If a dependency has been configured in dependency management, the relevant configuration information can be simplified when the dependency is actually introduced.
In addition, the maven sub POM can inherit the configuration information of the parent POM, so dependency management can be used to centralize the configuration information.
It is assumed that the dependencies of project A are as follows:
<project> ... <dependencies> <dependency> <groupId>group-a</groupId> <artifactId>artifact-a</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>group-c</groupId> <artifactId>excluded-artifact</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>bar</type> <scope>runtime</scope> </dependency> </dependencies> </project>
The dependencies of project B are as follows:
<project> ... <dependencies> <dependency> <groupId>group-c</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>war</type> <scope>runtime</scope> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>bar</type> <scope>runtime</scope> </dependency> </dependencies> </project>
If they have the same parent POM, they can be declared in the parent POM as follows:
<project> ... <dependencyManagement> <dependencies> <dependency> <groupId>group-a</groupId> <artifactId>artifact-a</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>group-c</groupId> <artifactId>excluded-artifact</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>group-c</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>war</type> <scope>runtime</scope> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>bar</type> <scope>runtime</scope> </dependency> </dependencies> </dependencyManagement> </project>
Then the configuration of items A and B can be simplified as follows:
Item A
<project> ... <dependencies> <dependency> <groupId>group-a</groupId> <artifactId>artifact-a</artifactId> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <!-- This is not a jar Dependency, so its type must be determined--> <type>bar</type> </dependency> </dependencies> </project>
Item B
<project> ... <dependencies> <dependency> <groupId>group-c</groupId> <artifactId>artifact-b</artifactId> <!-This is not a jar Dependency, so its type must be determined--> <type>war</type> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <!-- This is not a jar Dependency, so its type must be determined --> <type>bar</type> </dependency> </dependencies> </project>
Because the dependent version and configuration attribute are declared under the dependencyManagement element of the parent POM, A and B only need to declare the dependent groupId, artifact ID and type. Therefore, dependency management plays A role in centralizing dependency configuration.
Another function of dependency management simplification is to determine the version of passed dependencies.
The official DEMO is as follows:
The configuration of item A is as follows:
<project> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>A</artifactId> <packaging>pom</packaging> <name>A</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>test</groupId> <artifactId>b</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>c</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>d</artifactId> <version>1.2</version> </dependency> </dependencies> </dependencyManagement> </project>
Item B uses item A as the parent POM, and its dependency configuration is as follows:
<project> <parent> <artifactId>A</artifactId> <groupId>maven</groupId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>B</artifactId> <packaging>pom</packaging> <name>B</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>d</artifactId> <version>1.0</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>c</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>
After the above declaration configuration, when maven runs on project B, the final versions of a, B, c and d are 1.0.
It should be noted that the priority of different configurations for determining the delivery dependent version is as follows:
Direct declaration > dependency Management > dependency mediation
(1) A and c are explicitly declared as 1.0 in b. according to the dependency call rules, the explicit declaration has the highest priority, so a and c are version 1.0.
(2) B takes version 1.0 from the dependency management item in B's parent POM A, and dependency management has a higher priority than dependent mediation, so B finally takes version 1.0.
(3) The POM of B does not explicitly declare the dependency on D, so D may be the transitive dependency of a or c. according to the same rule, dependency management has a higher priority than dependency mediation, and the current POM file of the project has a higher priority than the parent POM file, so d finally takes version 1.0.
Dependency import
The dependency management described earlier can centralize the dependency configuration of the project, but the project needs to declare a specific parent POM, which is very restrictive. Only the dependency management of one project can be introduced, so the dependency with the scope of import needs to be used here.
The official Demo is as follows:
The configuration of item B is as follows:
<project> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>B</artifactId> <packaging>pom</packaging> <name>B</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>maven</groupId> <artifactId>A</artifactId> <version>1.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>d</artifactId> <version>1.0</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>c</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>
B introduces project a in dependency management, with type pom and scope import. (note that import can only be used under dependency management, and the matching type can only be pom). The configuration file of project a is the same as that of project a in the previous example, and the versions of a, B, c and d that project B depends on are also 1.0. therefore
Introducing POM has the same effect as the parent POM.
The process of introducing POM configuration is recursive. Adding POM A introduces POM B and POM B introduces POMC, then POM A will also reference the configuration of POM C.
If multiple POMS are introduced under dependency management, those declared first have higher priority.
The priority of dependency management configuration also adopts the same shortest path priority principle as that of dependency mediation, that is, if there are multiple identical configurations, the configuration of the shortest path is taken; if there are paths with the same length, the one declared first is taken.
Sometimes there are multiple components under a library, and the component versions are usually related when using it. For example, when using 1.1 A, you need to use 1.2 B (usually a and B are different modules under the same project). For example, this is the case when introducing the Spring Boot library.
This is the case. If other projects need to determine the versions of A and B separately, it is very troublesome for use and maintenance. Therefore, Maven provides BOM to simplify the POM configuration of the project in this case.
Bill of materials (BOM) is generally configured by the library provider. Users only need to import this BOM to easily reference the components under the library.
The official DEMO is as follows:
Suppose there is a library with two components project1 and project2 under the library. The configuration of other projects when importing the library is simplified through BOM files.
First define a BOM:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.test</groupId> <artifactId>bom</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <properties> <project1Version>1.0.0</project1Version> <project2Version>1.0.0</project2Version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.test</groupId> <artifactId>project1</artifactId> <version>${project1Version}</version> </dependency> <dependency> <groupId>com.test</groupId> <artifactId>project2</artifactId> <version>${project2Version}</version> </dependency> </dependencies> </dependencyManagement> <modules> <module>parent</module> </modules> </project>
Then, declare a parent POM, reference the BOM as the parent POM, and there are two modules under the project
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.test</groupId> <version>1.0.0</version> <artifactId>bom</artifactId> </parent> <groupId>com.test</groupId> <artifactId>parent</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> </dependencies> </dependencyManagement> <modules> <module>project1</module> <module>project2</module> </modules> </project>
project1 and project2.
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.test</groupId> <version>1.0.0</version> <artifactId>parent</artifactId> </parent> <groupId>com.test</groupId> <artifactId>project1</artifactId> <version>${project1Version}</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </dependency> </dependencies> </project>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.test</groupId> <version>1.0.0</version> <artifactId>parent</artifactId> </parent> <groupId>com.test</groupId> <artifactId>project2</artifactId> <version>${project2Version}</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </dependency> </dependencies> </project>
Then, when other projects want to import this library, they can simplify the configuration by importing BOM.
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.test</groupId> <artifactId>use</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>com.test</groupId> <artifactId>bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.test</groupId> <artifactId>project1</artifactId> </dependency> <dependency> <groupId>com.test</groupId> <artifactId>project2</artifactId> </dependency> </dependencies> </project>
summary
Maven uses dependency mediation to automatically resolve dependency version conflicts.
Unnecessary dependency passing can be avoided through the dependent scope, optional and exclusion.
Through depedencyManagement, you can centralize the dependent configuration information.
Through BOM, the library provider allows users to simplify the configuration during import and receipt.