Detailed explanation of scope (range of action) in Maven

1. Dependency transmission

Maven dependency transfer is one of Maven's core mechanisms, which can simplify Maven's dependency configuration to a certain extent.

As shown in the figure below, project A depends on project B, and B depends on project C. At this time, B is a direct dependency of A, and C is an indirect dependency of A.

Maven's dependency transfer mechanism means that no matter how many indirect dependencies exist in a Maven project, only its direct dependencies need to be defined in the POM, and no indirect dependencies need to be defined. Dependencies are introduced into the current project as transitive dependencies. Maven's dependency transfer mechanism can help users simplify the configuration of POM to a certain extent.

Based on the dependencies among A, B, and C, according to Maven's dependency transfer mechanism, we only need to define its direct dependency on B in the POM of project A, and define its direct dependency on C in the POM of project B, and Maven will resolve A The POM that directly depends on B introduces the indirect dependency C into project A in the form of transitive dependency.

Through this dependency transfer relationship, the dependency tree can grow rapidly to a large magnitude, and it is very likely that there will be repeated dependencies, dependency conflicts, etc. Maven provides the following functions to deal with these situations.

  • Dependency scope
  • Dependency mediation
  • Optional dependencies
  • Excluded dependencies
  • Dependency management

Second, the scope of dependence

First of all, we need to know that Maven will use three different sets of classpaths when compiling, testing and running the project. When a Maven project is built, the dependencies introduced into the classpath are different at different stages.

We can use the scope element in the dependency declaration of the POM to control the relationship between the dependency and the three classpaths (compile classpath, test classpath, and run classpath), which is the dependency scope.

Below we first need to understand the three classpaths:

  • We use the idea to run the code in the src/main/java directory of the project, which is actually the bytecode file in the current project target/classes directory.
  • We use the idea to run the code in the src/test/java directory of the project, which is actually the bytecode file in the current project target/test-classes directory.
  • Why is it said here that it is a war package instead of a jar package, because the jar package can be run directly, and the war package needs to depend on tomcat.

Optional configurations include compile, test, provided, runtime, system, and import. If not specified, compile is the default. First of all, we need to know that there are no jar packages in the classes and test-classes directories. The jar packages that the project depends on directly use the local maven warehouse. The dependency range is more popularly understood. In fact, it is to mark the dependent packages. For example, the The A dependency package is marked as "compile", and Maven knows that the A dependency package is not only used to run the code in the classes directory, but also used to run the test-classes, and the packaged war package is also used (packaging means that the dependent packages must also be packaged to war, so that war can be run in tomcat without depending on the local warehouse).

  • compile (compile dependency scope): Maven dependencies using this dependency scope, 对于编译、测试、运行三种classpath 都有效. A typical example is spring-core, which needs to be used when compiling, testing and running.
  • test (test dependency scope): 只对于测试classpath有效, such dependencies will not be available when compiling the main code or running the project's usage. A typical example is junit, which is only needed when compiling the test code and running the test.
  • provided: 对于编译和测试classpath有效, but has no effect at runtime. A typical example is servlet-api, which is required when compiling and testing a project. If it needs to be packaged into a war and run in tomcat, since tomcat has already provided it, Maven does not need to introduce it repeatedly. So set scope as a provided dependency 不会参与项目的war打包. If packaged as jar, 设置与不设置provided并不会影响maven将依赖打包到jar当中.

Speaking of provided, here are <dependency>the sub-labels below <optional>. The difference between the two is:
1. <optional>If it is true, it means that the dependency will not be passed. For example: x depends on B, and B depends on A (x->B->A), then <optional>the dependency set to true in A will not be passed to x. If a dependency of the current project <optional>is true, it will not affect the packaging of the dependency in the current project, it will only affect the packaging of other projects that depend on the current project.
2. <scope>Provided means that the dependency does not participate in the war package.

This dependency has been provided in the target container, no need to provide
detailed explanation about optional: https://blog.csdn.net/weixin_43888891/article/details/130510971

  • runtime (runtime dependency scope): Runtime dependency scope: valid for testing and running class-path, but invalid when compiling the main code (invalid for compiled classpath). A typical example is JDBC driver implementation. The compilation of the main code of the project only needs the JDBC interface provided by the JDK, and the specific JDBC driver that implements the above interface is only required when executing the test or running the project.
  • system (system dependency scope): used when a third-party local jar package needs to be referenced, case: https://blog.csdn.net/weixin_43888891/article/details/130611728
  • import (import dependency range): import dependency range, which can only be used with the dependencyManagement element, and its function is to import and merge the configuration of dependencyManagement in the target pom.xml file into the dependencyManagement of the current pom.xml. Case: https://blog.csdn.net/weixin_43888891/article/details/130520345

A list of the relationship between the dependency range and the three classpaths is shown below.

3. The impact of dependency scope on transitive dependencies

Project A depends on project B, and B depends on project C. At this time, we can call A's dependence on B the first direct dependence, and B's dependence on C as the second direct dependence.

B is a direct dependency of A, and C is an indirect dependency of A. According to Maven's dependency transfer mechanism, indirect dependency C will be introduced into A in the form of transitive dependency, but this introduction is not unconditional, it will be subject to dependency scope Impact.

The dependency scope of a transitive dependency is affected by the scope of the first direct dependency and the second direct dependency, as shown in the following table.

Note: In the above table, the first column on the left indicates the scope of the first direct dependence, and the first row above indicates the scope of the second direct dependence. The value of the cell in the intersection part is the dependency range of the transitive dependency. If the value of the cell in the intersection is "-", it means that the transitive dependency cannot be transmitted.

From the table above, the following rules can be concluded:

  • When the scope of the second direct dependency is compile, the scope of the transitive dependency is consistent with the scope of the first direct dependency;
  • When the scope of the second direct dependency is test, the transitive dependency will not be passed;
  • When the scope of the second direct dependence is provided, only the scope of the first direct dependence is also provided, and the scope of the transitive dependence is also provided;
  • When the scope of the second direct dependency is runtime, the scope of the transitive dependency is the same as the scope of the first direct dependency, except for compile, in which case the scope of the transitive dependency is runtime.

4. Dependent regulation

Maven's dependency transfer mechanism can simplify the declaration of dependencies. Users only need to care about the direct dependencies of the project, and do not need to care about the indirect dependencies that these direct dependencies will introduce. But when there are multiple import paths for an indirect dependency, in order to avoid the problem of repeated dependencies, Maven determines the import path of indirect dependencies through dependency adjustment.

Dependency regulation follows the following two principles:

  • The shortest import path is preferred:
  • The first to declare takes precedence

For the above two principles, the first principle should be used first, and if the first principle cannot be resolved, then the second principle should be used.

The shortest import path is preferred

The shortest import path is preferred. As the name suggests, when there are multiple import paths for an indirect dependency, the shortest import path will be resolved and used.

For example, A has a dependency like this:

  • A->B->C->D(1.0)
  • A->X->D(2.0)

D is an indirect dependency of A, but there are two different versions on the two import paths. Obviously, they cannot be imported at the same time, otherwise it will cause the problem of repeated dependencies. According to the first principle of Maven dependency adjustment: the shortest path is introduced first, the path length of D (1.0) is 3, and the path length of D (2.0) is 2, so the indirect dependency of D (2.0) will be from A->X- >D(2.0) path into A.

The first to declare takes precedence

The one that declares first takes precedence. As the name suggests, under the premise of the same import path length, the order of dependency declarations in the POM file determines whether the indirect dependencies will be parsed and used, and the one with the higher order will be used first.

For example, A has the following dependencies:

  • A->B->D(1.0)
  • A->X->D(2.0)

D is an indirect dependency of A, and the lengths of the two import paths are both 2. At this time, the first principle of Maven dependency adjustment can no longer be solved, and the second principle needs to be used: the one that declares first takes precedence.

The configuration in the POM file of A is as follows.

<dependencies>
    ...      
    <dependency>
        ...
        <artifactId>B</artifactId>       
        ...
    </dependency>
    ...
    <dependency>
        ...
        <artifactId>X</artifactId>
        ...
    </dependency>
    ...
</dependencies>

From the above configuration, it can be seen that since the dependency declaration of B is earlier than X, the indirect dependency D (1.0) will be introduced into A from the path A->B->D(1.0).

5. Optional dependencies

Optional dependencies refer to the optional tag, detailed explanation about optional: https://blog.csdn.net/weixin_43888891/article/details/130510971

6. Eliminate dependencies

Transitive dependencies can help us simplify the management of project dependencies, but at the same time, it will also bring other unnecessary risks, such as: some dependencies will be implicitly introduced, which may not be what we want to introduce, or these implicitly introduced Dependencies are SNAPSHOT version dependencies. The instability of dependencies caused the instability of our project.

In our project, the junit-vintage-engine dependency is excluded from the spring-boot-starter-test dependency because the springboot version we use is 2.2.6-RELEASE, and the corresponding Junit version is 5.x, but junit-vintage The 4.x version of Junit is included in the -engine dependency, so we can exclude this dependency at this time.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

The description of the exclusions element and exclusion dependencies is as follows:

  • Excluding dependencies is to control whether the current project uses the indirect dependencies passed down from its direct dependencies;
  • Several exclusion sub-elements can be included under the exclusions element to exclude several indirect dependencies;
  • The exclusion element is used to set specific excluded indirect dependencies. This element contains two sub-elements: groupId and artifactId, which are used to determine the coordinate information of the indirect dependencies that need to be excluded;
  • You only need to set groupId and artifactId in the exclusion element to determine the dependencies that need to be excluded, without specifying the version version.

7. Dependency classification

In our actual development process, we may need to integrate many third-party frameworks. When integrating these frameworks, we often need to add multiple dependencies in pom.xml to complete the integration. And these dependencies often need to maintain the same version. When upgrading the framework, they must be upgraded to the same version.

As shown in the figure below, we can see that when introducing the dubbo framework, we need to introduce two related dependencies, and the version numbers are the same. At this time, we can extract the corresponding version numbers and put them in the properties tag , used as a global parameter. Similar to the idea of ​​abstraction in the Java language.

At this time, we can see that if we need to upgrade the version corresponding to the dubbo framework one day in the future, we only need to modify the version number in properties to upgrade all dependent versions together.

8. Dependency management

Dependency management refers to the dependencyManagement tag, just read this article: https://blog.csdn.net/weixin_43888891/article/details/130520345

Guess you like

Origin blog.csdn.net/weixin_43888891/article/details/130517016