Maven depends on conflicts and avoids pits

Maven depends on conflicts and avoids pits

Preface: The Origin of Dependency Conflicts

Maven is one of the most popular construction tools in Java projects today, and the number of libraries that projects depend on will gradually increase with the increase in project size and complexity.

Sufficient dependencies will also bring some hard-to-find dependency conflicts to the project, threatening the stability of the system operation at all times, and also bring a lot of trouble to the future iterations of the project and the upgrade of the architecture.

So, what is a dependency conflict? There is the most direct phenomenon, that is, in the actual development process, some dependencies must be introduced more or less. If the project cannot be started after the dependency is introduced, or the logic that was running normally before suddenly reports an error in some scenarios, etc. Etc, a dependency conflict might be the culprit.

But don't worry, because the problem of dependency conflicts will exist in almost any Java project of any size. For example, you introduced the spring-boot-starter-redis package into your project, and then you need to use distributed locks, but since spring-boot officially does not provide a formed class library for use, you are on Du Niang I found a xxx-distribution-lock-redis, and pasted it into the pom. After writing the code, start the project and prepare to show off. As a result, the console outputs the following information:

Cause by: java.lang.NoclassDefFoundError:org/springframework/data/redis/connection/lettuce/LettuceClientConfiguration  at java.base/java.lang.Class.getDeclaredMothods0(Native Method)  at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3166)  at java.base/java.lang.Class.getDeclaredMethods(Class.java:2309)  at java.base/java.lang.Class.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463)  ... 21 common frames omittedCause by: java.lang.ClassNotFoundException: org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration  at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)  ...25 common frames omitted("hello world!");

This is a typical dependency conflict problem, what? You said there is no problem with compilation? Written according to the document? Can I still pack it? TOO NAIVE...

Do you think that if you quoted the dependency of this version, this version is running in the project?

In fact, this scenario is relatively simple, because for the user, he knows that he has introduced a dependency that may have the same function, and there will be a corresponding error message when the project starts. But sometimes, you don't know how many dependencies in the project intersect, and the project is started normally, often at a certain time, place and people, the service suddenly has an unknown error.

So why is this happening? How does Maven handle the simultaneous introduction of multiple versions of the same dependency? Let's put these questions aside first. This article will start from practice, explaining from discovering and analyzing dependencies to gradually explaining the core mechanism of dependencies, and finally giving operational suggestions on how to avoid dependency conflicts when developing new and old systems. Let me introduce how to analyze dependencies in the actual development process.

Dependency Visualization

A Java Web project with a small scale depends on hundreds of packages, so your service dependencies should be tree-like. Through Maven built-in commands, or third-party plug-ins can help you analyze project dependencies.

Use Maven command to display dependency tree

Maven provides commands to view the dependency tree:

mvn dependency:tree

Information in the following formats can be output:

picture

As shown above, the sub-dependencies of each module will be output and displayed in a tree structure. With the string search command of the terminal, you can also quickly query the results. But sometimes if you want to see a graphical display, you can use IDE tools to display dependencies more intuitively.

Use IDEA built-in tools to display

Find a project, in the pom of the startup module, with the help of IntelliJ IDEA, we can visually view the dependency tree:

picture

Set the graph to the actual size or zoom in, and you can see where each red line points, that is, the conflicting dependencies, but there are countless such red lines.

Well, let me use " dependent health " to measure the severity of the conflict. Although the industry does not have tools and algorithms for scanning service dependent health similar to the mobile phone cleaning housekeeper, it is obvious that the more red lines there are, the more The more serious the conflict.

Unless you are very clear about whether each conflict point has an impact, instead of judging by intuition, every conflict may turn into the fuse of the next company's brother service avalanche.

picture

Use the IDEA plugin to analyze dependencies

Human civilization from the Stone Age to the Bronze and Iron Age to modern civilization is closely related to the fact that humans are good at using good production tools. Similarly, distinguishing which era a programmer comes from can also be based on the methods used to solve problems.

There are many useful productivity tools in IDEA's plug-in market. For the analysis and troubleshooting of Maven's dependencies, it is recommended to use the Maven Helper plug-in.

Step 1: Plugin Installation

Open IDEA's Preferences, (Mac shortcut key is "⌘+,") click Plugins on the left, search for maven helper as shown in the figure below, click Install, and restart IDEA after the download is complete.

picture

Step 2: Analyze dependencies using plugins

Enter any pom file, and the bottom tab of the IDEA edit box will have a Dependency Analyzer tab.

picture

Click this tab, the following UI will appear, and All Dependencies as Tree will display all dependencies in a tree structure.

picture

Conflicts will show the conflicting dependencies of the current module:

picture

Of course it also supports search.

picture

And when pom.xml is manually modified, "⚠️ Refresh UI" will be prompted at the top to refresh the dependency graph.

Step 3: Use plugins to resolve conflicts

When conflicting dependencies are found, you can right-click Jump to Source[META DOWN] to quickly jump to the corresponding location of pom.xml:

picture

If you click Exclude, this dependency will be excluded.

Take the above picture as an example. After clicking Exclude, the current pom changes as follows:

Before exclusion:

<dependency>        <groupId>com.shizhuang-inc</groupId>        <artifactId>instrument-core</artifactId></dependency>

After exclusion:​​​​​​

<dependency>  <groupId>com.shizhuang-inc</groupId>  <artifactId>instrument-core</artifactId>  <exclusions>    <exclusion>      <artifactId>utils</artifactId>      <groupId>com.poizon</groupId>    </exclusion>  </exclusions></dependency>

It should be noted that resolving dependency conflicts in this way is a bad idea . Unless it is a last resort, it is not recommended to use this method to exclude. Not only does this make messy dependencies worse, but it also defeats the purpose of the dependency provider itself.

In the following article, we will introduce how to avoid dependency conflicts to the greatest extent and solve the problem from the source.

The core mechanism of dependence

transitivity of dependencies

Maven's dependencies are transitive. For example, your project A depends on B, but B also depends on C. The relationship is as follows:

A -> B -> C

Before building tools such as Maven, you need to manually find the two dependent jar packages of B and C, and then put them in the project directory, like this:

A├── lib│   ├── B.jar│   └── C.jar└── src    └── main        └── java

With Maven, you only need to create a pom and declare dependencies:​​​​​​​​

A├── pom.xml└── src    └── main        └── java

Then declare the dependency on B in the pom.xml of module A (assuming that B has declared that it depends on C):​​​​​​​​

<dependency>  <groupId>com.poizon</groupId>  <artifactId>B</artifactId>  <version>2.0</version></dependency>

What is the proximity principle?

As the project is gradually iterated, the cost of dependency management will gradually increase. In order to avoid the ambiguity caused by each library inevitably declaring the use of the same library, Maven introduces an additional mechanism, which is the "proximity principle".

The principle of proximity ensures that in the dependency tree of the project, when there are multiple versions of the same dependency, which version should be selected for use.

In Maven's official documentation, an example of the following dependency tree is given:​​​​​​​​

  A  ├── B  │   └── C  │       └── D 2.0  └── E      └── D 1.0

The dependencies of the four modules of ABCD are: A -> B -> C -> D 2.0 and A -> E -> D 1.0. Obviously, the latter has the shortest dependency path from A to D modules, so when building A module , will use version 1.0 of the D module instead of 2.0.

But what if you want to use the D of the 2.0 module? You can explicitly declare the version of the D module in the A module:​​​​​​​​

 A  ├── B  │   └── C  │       └── D 2.0  ├── E  │   └── D 1.0  │  └── D 2.0 使用此项    

A -> B -> C -> D path 1

A -> E -> D path 2

A -> D path 3 (use this shortest path)

Dependency management and control

As the leader of dependency management, Maven has flexible control over dependencies. The official provides a dependency management mechanism, and in order to control the timing of the introduction of dependencies, the scope of dependencies and optional dependencies are also specified. Finally, sometimes you have to use manual intervention to resolve dependency conflicts, that is, dependency exclusion.

1) Dependency management

Dependency management is dependencyManagement, which is mainly used to declare the version of the dependent library, and is often used in parent-child type projects. One of the most basic methods is to declare the dependencyManagement tag in the parent project, which declares the dependent library version required by the submodule, and imports the corresponding dependent library without version declaration in the submodule.

Parent module declaration dependency management:​​​​​​​​

<properties>  ...  <!-- 声明依赖库版本 -->   <fastjson.version>1.2.76</fastjson.version>  ...</properties>
<dependencyManagement>   <dependencies>        ...        <!-- 声明依赖库 -->     <dependency>      <groupId>com.alibaba</groupId>      <artifactId>fastjson</artifactId>      <version>${fastjson.version}</version>    </dependency>        ...  </dependencies></dependencyManagement>

There is no need to specify the version in the submodule:

​​​​​​​

<dependencies>  ...  <dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>  </dependency>  ...</dependencies>

The advantage of this is that within the same project, even if a dependency declares different versions in each module, in actual use, if other modules import the module containing this dependency, the version number is still based on your The version declared in the parent module shall prevail.

To give a practical example, in the XNIO Parent POM, the dependencyManagement node declares the following dependencies:​​​​​​​​

...<version.org.wildfly.common>1.5.2.Final</version.org.wildfly.common>......<dependency>  <groupId>org.wildfly.common</groupId>  <artifactId>wildfly-common</artifactId>  <version>${version.org.wildfly.common}</version></dependency><dependency>  <groupId>org.wildfly.client</groupId>  <artifactId>wildfly-client-config</artifactId>  <version>${version.org.wildfly.client-config}</version></dependency>...

In wildfly-client-config, wildfly-common is also declared, but the version is different from that in XNIO Parent POM:​​​​​​​​

...<version.org.wildfly.common>1.2.0.Final</version.org.wildfly.common>...<dependency>  <groupId>org.wildfly.common</groupId>  <artifactId>wildfly-common</artifactId>  <version>${version.org.wildfly.common}</version></dependency>

Now there is an xnio-api module, which is a submodule of XNIO Parent. This module depends on the above two modules at the same time:​​​​​​​​

<dependency>  <groupId>org.wildfly.common</groupId>  <artifactId>wildfly-common</artifactId></dependency><dependency>  <groupId>org.wildfly.client</groupId>  <artifactId>wildfly-client-config</artifactId></dependency>

At this time, even though the version 1.2.0.Final has been specified in the wildfly-client-config, the dependent version of wildfly-common in the xnio-api module is still the version 1.5.2.Final declared in the parent module.

Use the following to describe the above relationship:​​​​​​​​

XNIO Parent POM {
   
     revision: 1.0  依赖管理项 {
   
           wildfly-common:1.5.2.Final,        wildfly-client-config: 1.0-SNAPSHOT {
   
             依赖声明 {
   
               wildfly-common:1.2.0.Final          }        }        ...    },  子模块 {
   
       xnio-api: ${revision} {
   
         依赖声明 {
   
           wildfly-common, //版本为 1.5.2.Final        wildfly-client-config      }    }  }}

2) Dependent scope

Dependency scope can better control when dependencies will be introduced. The official document also introduces 6 scopes:

  • compile : The default scope. After compiling and packaging the project, the dependencies of this scope will be packaged together.

  • provided : Both compilation and operation will be used. Generally, this dependency is finally provided by the outside. For example, the project is packaged as a war package and then deployed to the Tomcat container, and the Tomcat container provides servlet-api dependencies, so this dependency in the project The scope is provided, which is to avoid packaging this type of library into the class directory when packaging, resulting in dependency conflicts caused by repeated imports.

  • runtime : It is only used during runtime. For example, a specific database connection driver is used for the underlying interface in the actual code development process. Direct use of a specific driver also uses reflection or SPI. In fact, it is to avoid interfering with the logic of dynamically loading related dependencies.

  • test : dependencies used only during testing

  • system : The dependencies declared in this scope must explicitly specify the jar package path.

  • import : This scope only supports dependencies of type pom and can only be used in dependencyManagement. Dependencies declared with this scope will be replaced by the list of dependencies in dependencyManagement declared in this dependency.

3) Dependency exclusion

Dependency exclusion is usually used to resolve dependency conflicts caused by objective reasons, such as the following module dependencies:​​​​​​​​

  A  ├── B  │   └── C  │       └── D 2.0  └── E      └── D 1.0

According to the principle of proximity, when building A module at this time, the version of the dependent D module is 1.0, which may violate the original intention and want to use a higher version of the dependency. If modules B and E are third-party modules and you do not have the right to change the pom file, you need to use dependency exclusion:​​​​​​​​

<dependency>  <groupId>com.shizhuang-inc</groupId>  <artifactId>E</artifactId>  <exclusions>    <exclusion>      <groupId>com.shizhuang-inc</groupId>      <artifactId>D</artifactId>    </exclusion>  </exclusions></dependency>

4) Optional dependencies

Optional dependencies are dependencies where the tag "optional" is "true" in the dependency declaration.

Consider a dependency like this: A -> B -> C(optional).

A depends on B, and B depends on C, but C is declared as an optional dependency in B, then the dependency of module A only includes module B, and does not include the optional dependency C in module B. At this time, module A can explicitly specify to depend on module C to ensure that A can work normally. ​​​​​​

  A  ├── B  │   └── C(optional)         └── C 显式依赖 C

For the developer of B, the control over the dependency on C is handed over to the user.

dependency trap

It is precisely because of these core dependency mechanisms that, as a developer, it is still inevitable to dig a hole.

Here are some examples of situations to prevent mining pits:

1) The same dependency is introduced multiple times in a single module, and the version number is declared.

When sorting out the dependencies of a certain service, the author found that some modules actually declared a dependency multiple times, but this dependency did not use the parent module dependency management, and the version number was different.

Regardless of the specification, in such a case, the dependencies defined later take effect according to the order of definitions. ​​​​​​​​

  A  ├── C(2.0)         └── C(1.0) 此项生效

2) Multiple bom dependencies are declared in the parent pom in the project.

In this way, it is inevitable that there are the same dependencies but different versions in these multiple boms:

THE GOOD:​​​​​​​

<dependencyManagement>  <dependencies>    ...    <dependency>      <groupId>cglib</groupId>      <artifactId>cglib</artifactId>      <version>2.1</version>    </dependency>    ...  </dependencies></dependencyManagement>

B GOOD:​​​​​​​

<dependencyManagement>  <dependencies>     ...    <dependency>      <groupId>cglib</groupId>      <artifactId>cglib</artifactId>      <version>3.3.0</version>    </dependency>    ...  </dependencies></dependencyManagement>

Parent module parent BOM:​​​​​​​​

...<dependencyManagement>  <dependencies>    <dependency>      <groupId>com.shizhuang-inc</groupId>      <artifactId>A</artifactId>      <version>1.0</version>      <type>pom</type>      <scope>import</scope>    </dependency>
    <dependency>      <groupId>com.shizhuang-inc</groupId>      <artifactId>B</artifactId>      <version>1.0</version>      <type>pom</type>      <scope>import</scope>    </dependency>    ...  </dependencies></dependencyManagement>    

Submodule C:​​​​​​

<parent>  <artifactId>com.shizhuang</artifactId>  <groupId>parent</groupId>  <version>${revision}</version></parent>
<dependencies>  <dependency>   <groupId>cglib</groupId>   <artifactId>cglib</artifactId>  </dependency></dependencies>

At this time, the effective version is cglib version 2.1 defined in A BOM. Therefore, different versions of the same dependency are defined in multiple boms, and the first declared bom will override the subsequent declarations.

3) Multiple modules introduce the same dependency.

The following dependencies exist:​​​​​​​​

  A  ├── B  │   └── C(1.0) 此项生效      └── D      └── C(2.0)

Module A declares its dependencies on modules B and D, while modules B and D depend on module C at the same time, but the versions are different.


At this time, there are two shortest paths for A’s indirect dependence on C. Then, the dependency version of C depends on the import order of modules B and D. Since the declaration order of B takes precedence over D, version 1.0 of C in module B will be used.

After understanding the core mechanism, it is actually easy to answer the question at the beginning. Express the dependent selection in the form of a flow chart as follows:

picture

How to avoid dependency conflicts

Understand existing service dependencies

For a service with a deep accumulation of technical debt, it takes a lot of energy to understand each conflict. Here I can provide suggestions for key operations in the following aspects:

1) Focus on the module where the core link is located

The importance of core links is unquestionable. Analyzing the dependencies of the modules where these links are located will help improve the stability of core links.

2) Focus on network and serialization-related dependent libraries

From experience, many dependency conflicts stem from the following categories:

①Local serialization/deserialization is highly dependent.

Such as: jackson, Gson, fastjson and other dependent libraries are generally called frequently. Sometimes the imported version may not be expected, but it can run normally. This only means that the version compatibility of this dependent library is excellent. , does not mean that there is no conflict.

For example, in the abstract class JsonGenerator of the jackson-core library, the version 2.11 series released in April 2020 added the writeNumber(char[],int,int) method:​​​​​​​​

public void writeNumber(char[] encodedValueBuffer, int offset, int length) throws IOException {
   
           writeNumber(new String(encodedValueBuffer, offset, length));}

And before that there was none.

If the project project was created at the end of 2019 and relies on jackson-core version 2.9.10, by 2021, due to the iteration of requirements, many new dependencies will be added. If the jackson-core used is after 2.11 version, and use a method such as the one above that only exists in subsequent versions, it is likely to be due to dependency conflicts, because the project is still using the 2.9.10 version of the library.

This will result in the following error:

java.lang.AbstractMethodError: com/fasterxml/jackson/core/JsonGenerator.writeNumber([CII)V

② Related to serialization and deserialization of network calls, such as Protobuf, Thrift, Hessian, etc.

These dependent libraries are also frequently called in distributed systems. What cannot be ignored is that network calls in distributed systems generally have inherent complexity, and the parameter boundaries are generally wider, so it is difficult to avoid dependency conflicts. The resulting runtime exception.

③ RPC, Data classes such as Feign, Dubbo, gRPC, JDBC, Redis-related dependent libraries.

To give an actual scenario, in actual Web project engineering, we generally use Redis, and basically use spring-boot-starter-data-redis. Throughout 2019, the latest and stable version of the Spring Cloud ecosystem is still the G series version, the corresponding Spring Boot version is 2.1.x, and the lettuce-core version used is up to 5.1.8.RELEASE (Note: Released in March 2018 After springboot 2.x, the default connection client has been replaced by Lettuce from Jedis).

However, at the end of 2019, with the release of the first official release version of Spring Cloud Hoxton, the SpringBoot 2.2.x series has also become popular, and the lettuce-core version that depends on it has also reached 5.2 or higher. Compared with the previous version, the Lettuce R&D team came A one-key three-link: many new functions have been added, many bugs have been fixed, and many old features have been enhanced. However, he is not backward compatible, or not fully backward compatible. For example, the Tracing interface added in version 5.1 is used to monitor and track the execution of Redis commands, and a method is added in version 5.2:​​​​​​​​

/** * Returns {@code true} if tags for {@link Tracer.Span}s should include the command arguments. * * @return {@code true} if tags for {@link Tracer.Span}s should include the command arguments. * @since 5.2 */boolean includeCommandArgsInSpanTags();

The method added by this interface is not the default method (the interface method declared using the default keyword in Java8), which means that the class that implements the Tracing interface must implement this method. But what if it doesn't happen? I cited someone else's library, what should I do if it doesn't implement it? Then the following error must be waiting for you, and this error will appear at runtime !

java.lang.AbstractMethodError: com.xx.xx.monitor.instrument.redis.lettuce5x.LettuceTracing.includeCommandArgsInSpanTags()Z

Unless you downgrade back to the previous compatible version, you cannot use the Tracing interface based on this version as a packaged dependency library.

④Dynamic proxy, and the package where the target object to be dynamically proxied is located, bytecode enhancement related dependency libraries.

In addition, special attention should be paid to the objects used by dynamic proxies, which are very prone to errors that the target method to be proxied cannot be found due to dependency conflicts, and JVM opcode compatibility problems caused by dependency conflicts in the ASM version:

  • asm 5.0.4

public ClassVisitor(final int api, final ClassVisitor cv) {
   
           if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
   
               throw new IllegalArgumentException();        }        this.api = api;        this.cv = cv;}
  • asm 7.1 

public ClassVisitor(final int api, final ClassVisitor classVisitor) {
   
       if (api != Opcodes.ASM7 && api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4) {
   
           throw new IllegalArgumentException("Unsupported api " + api);    }    this.api = api;    this.cv = classVisitor;}

3) Develop a good habit of dependency management

When P0 appears, no one in the team is innocent. To avoid dependency conflicts, we should focus on both management and technology. Technicians need to have basic professionalism, and they can’t plant the seeds of evil for online stability in the hope of being happy for a while. Managers also need to strictly implement and implement related service dependency governance related measures.

As mentioned in the previous chapters, Maven provides a good dependency management mechanism. With this mechanism, a specification can be formed, which can greatly avoid conflicts caused by dependency issues. The basic principle is to declare the dependencies groupId, artifactId and version required by the project in the parent module, and only need to declare groupId and artifactId in the submodule. For specific examples, please refer to the introduction to dependency management in the previous chapter.

4) Regularly analyze project dependencies

Maven also provides a command-line tool to analyze the dependencies of the project, so as to adjust the relationship of dependencies appropriately, and avoid the confusion and conflict of dependency logic in the subsequent iteration process as much as possible.

mvn dependency:analyze

This command will list used but undefined dependencies and unused but defined dependencies. ​​​​​​​​

[WARNING] Used undeclared dependencies found:[WARNING]    javax.annotation:javax.annotation-api:jar:1.3.2:compile[WARNING]    org.springframework.boot:spring-boot:jar:2.1.13.RELEASE:compile[WARNING]    org.springframework.boot:spring-boot-autoconfigure:jar:2.1.13.RELEASE:compile[WARNING]    org.springframework:spring-web:jar:5.1.14.RELEASE:compile[WARNING]    org.springframework:spring-context:jar:5.1.14.RELEASE:compile[WARNING]    org.slf4j:slf4j-api:jar:1.7.29:compile[WARNING] Unused declared dependencies found:[WARNING]    org.springframework.boot:spring-boot-starter-actuator:jar:2.1.13.RELEASE:compile[WARNING]    org.springframework.boot:spring-boot-starter-web:jar:2.1.13.RELEASE:compile[WARNING]    org.springframework.boot:spring-boot-starter-undertow:jar:2.1.13.RELEASE:compile

Used undeclared dependencies: dependencies that have been used but not defined . Such dependencies are generally introduced by the dependency delivery mechanism, and are also used directly in the code.

These dependencies may change due to changes in objective factors, including changes in dependent versions or even being deleted directly. For example the following dependencies exist:

A -> B -> C

At this time, due to the transitive dependency mechanism, the A module will contain both B and C dependencies, and there is no problem in using the ClassA of the C module in the A module. Afterwards, due to the security upgrade of module B, the version of module C was also upgraded.

A -> B1 -> C1

If the Class A of the C module is changed in this upgrade, for example, the class access identifier is no longer public, or a method and field are removed. Of course, this kind of situation will generally cause errors during compilation, but if a class or method is called based on dynamic proxy and reflection, there may be no errors during compilation, and it will only appear during actual operation. Runtime exception.

Unused declared dependencies: Unused but introduced dependencies . Such dependencies are not used directly in the code, nor does it mean that they are not used during runtime . Just as a reference to remove unused dependencies.

Advice for module developers

As a qualified developer, you must always keep in mind that the modules you develop may be used by developers all over the world, and take on the important responsibility of a link in the billion-level traffic.

This does not introduce how the architecture should be designed, because there is no way to measure the quality of the architecture regardless of the size or the audience. Here are the following suggestions only from the perspective of avoiding dependency conflicts:

1) Establish a clear dependency boundary.

Think about what problem the current module is mainly responsible for solving, figure out what to do, and avoid a single module containing multiple functions. For submodules that use BOM as the parent during the development process, use the flatten plug-in to remove the parent declaration as much as possible when publishing the module, so as to avoid irrelevant dependencies being introduced into the consumer's module through the dependency transfer mechanism. For specific plug-in usage documents, refer to the link below.

2) Appropriately expand your own structure and distinguish between primary and secondary.

When introducing a dependent library, carefully consider the scope of use of this dependency, and whether the user must directly depend on the dependency you introduced according to the standard when using your module. A typical example is:

Suppose you develop an enhanced version based on the lettuce-redis class library. Of course, this module needs to introduce the official dependency of lettuce-core, but is it necessary for this dependency to participate in the dependency transfer mechanism?

Obviously, the module you develop is not the main dependency introduced by the user using redis, but both the user and your module need this main redis-related dependency, and lettuce-core is the main dependency, so it depends on control To be handed over to the user, the enhanced version module developed by oneself needs to declare this dependency as an optional dependency .

It should be noted that declaring the main modules that both the user and itself will use as optional dependencies can avoid accidents caused by dependencies?

Obviously, the answer is no.

Since the dependency control is handed over to the user, this will inevitably cause the inconsistency between the version of the dependency introduced by the user and the version used by the developer. Therefore, it is also necessary to consider the general scope of the framework environment of most users, and minimize the problems caused by version differences.

3) Embedding flowers and trees

When the module you develop has to include a specific version of the dependency, but at the same time, it is also considered that the user is likely to introduce this dependency, you can also use the technique of "transplanting flowers and trees". The specific performance is to rename the package names of all classes that introduce dependent libraries, and then package these classes with modified package names together with the code of the module itself, and finally there will be a package name in the class directory of the packaged module The renamed dependent library bytecode file is equivalent to porting the code of the dependent library to its own module.

With the help of the maven-shade-plugin plug-in, this requirement can be well fulfilled. In this way, when the JVM loads the "same class", because the package names are different, these classes with the same class name will also be loaded and used instead of each other. Influence. (For details on how to use the plugin, please refer to the link below.)

References

Maven - Introduction to the Dependency Mechanism

https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

Flatten Maven Plugin

https://www.mojohaus.org/flatten-maven-plugin/index.html

Apache Maven Shade Plugin - Introduction

https://maven.apache.org/plugins/maven-shade-plugin/

Guess you like

Origin blog.csdn.net/u012921921/article/details/121192494