Maven combat (3) - POM refactoring of multi-module projects

Reprinted from http://www.infoq.com/cn/news/2011/01/xxb-maven-3-pom-refactoring/

 

In the previous article of this column, POM refactoring adds or deletes , we discussed some simple and practical POM refactoring techniques, including the premise of refactoring - continuous integration, and how to improve POM by adding or removing content Readability and build stability. However, in actual projects, these skills are not enough. It is especially worth mentioning that the actual Maven projects are basically multi-module. If only a single POM is refactored without considering the relationship between modules, it will cause Pointless repetition. This article discusses some POM refactoring techniques based on multiple modules.

repeat, repeat

Programmers should have a dog-like sense of smell. They should be able to smell repetition, the most common bad smell. No matter what kind of coat it wears, once it is discovered, it should be completely and mercilessly killed. Don't condone repetition here because the POM is not product code, for example, a piece of code like this has repetition:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactid>spring-beans</artifactId>
  <version>2.5</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactid>spring-context</artifactId>
  <version>2.5</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactid>spring-core</artifactId>
  <version>2.5</version>
</dependency>

Will you use different versions of SpringFramework components in a project? The answer is obviously no. So there is no need to repeat <version>2.5</version> three times here. Use Maven properties to extract 2.5 as follows:

<properties>
  <spring.version>2.5</spring.version>
</properties>
<depencencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactid>spring-beans</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactid>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactid>spring-core</artifactId>
    <version>${spring.version}</version>
  </dependency>
</depencencies>

Now 2.5 only appears in one place. Although the code is a little longer, the repetition disappears. When upgrading a dependency version in the future, only one need to be modified, and it can also avoid missing a dependency to upgrade.

The reader may already be very familiar with this example. I will repeat it here to pave the way for the future. The purpose of multi-module POM refactoring is the same as this example, but also to eliminate duplication. The more modules, the more potential duplications. structure is more necessary.

Eliminate duplication of multi-module dependency configuration

Consider such a small project, it has more than 10 Maven modules, these modules have a clear division of labor, each performs its own duties, and the coupling between each other is relatively small, so that everyone can focus on developing in their own modules instead of Think too much about the influence of others on you. (Okay, I admit that this is the ideal situation) Then I start coding module A, first of all, I need to introduce some common dependencies such as JUnit, Log4j, etc.:

  <dependency>
    <groupId>junit</groupId>
    <artifactid>junit</artifactId>
    <version>4.8.2</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>log4j</groupId>
    <artifactid>log4j</artifactId>
    <version>1.2.16</version>
  </dependency>

My colleague is developing module B, and he also uses JUnit and Log4j (we discussed it in a meeting, the unified unit testing framework is JUnit instead of TestNG, and the unified logging implementation is Log4j instead of JUL, I will not explain why this decision is made, Anyway, that's it). A colleague wrote the following dependency configuration:

  <dependency>
    <groupId>junit</groupId>
    <artifactid>junit</artifactId>
    <version>3.8.2</version>
  </dependency>
  <dependency>
    <groupId>log4j</groupId>
    <artifactid>log4j</artifactId>
    <version>1.2.9</version>
  </dependency>

See any problem? Yes, he left out the scope that JUnit depends on because he is not familiar with Maven. anymore question? Yes, version! Although he relies on JUnit and Log4j like me, the versions are inconsistent. We held a meeting to discuss which version to use, but if a project depends on multiple versions of a class library at the same time, it is very dangerous! OK, now it's just two dependencies of two modules, it's fine to fix it manually, but what if it's 10 modules, each with 10 dependencies or more? It seems that this is really a quagmire, and once it is stuck in it, it is difficult to clean up.

Fortunately, Maven provides an elegant solution, which can be solved by using the inheritance mechanism and the dependencyManagement element. Note that it is dependencyMananget and not dependencies. Maybe you have thought of configuring dependencies in the parent module, so that all sub-modules are automatically inherited, which not only achieves the purpose of consistent dependencies, but also saves a large piece of code, but this is problematic, for example, if you add the dependencies of module C spring-aop is extracted into the parent module, but modules A and B directly inherit spring-aop although they do not need spring-aop. There is no such problem with dependencyManagement. DependencyManagement only affects the configuration of existing dependencies, but does not introduce dependencies . For example, we can configure the following in the parent module:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactid>junit</artifactId>
      <version>4.8.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactid>log4j</artifactId>
      <version>1.2.16</version>
    </dependency>
  </dependencies>
</dependencyManagement>

This configuration does not introduce dependencies to any submodules, but if a submodule needs to use JUnit and Log4j, we can simplify the dependency configuration as follows:

  <dependency>
    <groupId>junit</groupId>
    <artifactid>junit</artifactId>
  </dependency>
  <dependency>
    <groupId>log4j</groupId>
    <artifactid>log4j</artifactId>
  </dependency>

Now only groupId and artifactId are needed, and other elements such as version and scope can be obtained by inheriting the parent POM's dependencyManagement. If there are dependencies configured with exclusions, the code savings will be even more impressive. But that's not the point, the point is that the JUnit and Log4j dependency configurations used by all modules are now guaranteed to be consistent. And submodules can still introduce dependencies as needed. If I don't configure dependencies, the spring-aop dependencies under dependencyManagement in the parent module will not have any effect on me.

Perhaps you have realized that in multi-module Maven projects, dependencyManagement is almost essential, because only it can effectively help us maintain dependency consistency .

Originally, I wanted to introduce more about dependencyManagement, but a discussion with Sunng a few days ago gave me more content to share. That is, when using dependencyManagement, we can not inherit from the parent module, but use a special import scope dependency. Sunng lists it as his own Maven Recipe #0 , and I'll briefly introduce it.

We know that Maven's inheritance, like Java's inheritance, cannot achieve multiple inheritance. If 10, 20 or even more modules inherit from the same module, then according to our previous practice, the dependencyManagement of this parent module will contain a large number of rely. If you want to categorize these dependencies for clearer management, that's not possible, import scope dependencies can solve this problem. You can put dependencyManagement in a separate POM dedicated to managing dependencies, and then import dependencyManagement through import scope dependencies in modules that need to use dependencies. For example, you can write a POM for dependency management like this:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.juvenxu.sample</groupId>
  <artifactId>sample-dependency-infrastructure</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <dependencyManagement>
    <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactid>junit</artifactId>
          <version>4.8.2</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>log4j</groupId>
          <artifactid>log4j</artifactId>
          <version>1.2.16</version>
        </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Then I can introduce this dependency management configuration in a non-inheritance way:

  <dependencyManagement>
    <dependencies>
        <dependency>
          <groupId>com.juvenxu.sample</groupId>
          <artifactid>sample-dependency-infrastructure</artifactId>
          <version>1.0-SNAPSHOT</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
    </dependencies>
  </dependencyManagement>

  <dependency>
    <groupId>junit</groupId>
    <artifactid>junit</artifactId>
  </dependency>
  <dependency>
    <groupId>log4j</groupId>
    <artifactid>log4j</artifactId>
  </dependency>

In this way, the POM of the parent module will be very clean, and the dependencies will be managed by the POM that is specifically packaged as the pom, which also conforms to the single responsibility principle in object-oriented design. Additionally, we are able to create multiple such dependency management POMs to manage dependencies in a more granular way. This approach is also somewhat similar to the use of composition rather than inheritance in object-oriented design.

Eliminate duplication of multi-module plugin configuration

Similar to dependencyManagement, we can also manage plugins using the pluginManagement element. A common usage is that we want all modules of the project to use Java 1.5 when using the Maven Compiler Plugin, and specify that the Java source file encoding is UTF-8. At this time, the pluginManagement can be configured in the POM of the parent module as follows:

<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

This configuration will be applied to the maven-compiler-plugin of all sub-modules. Since Maven has built-in maven-compiler-plugin and life cycle bindings, sub-modules no longer need any maven-compiler-plugin configuration. .

Unlike the dependency configuration, usually all projects should have the same configuration for any one dependency, but this is not the case with plugins. For example, you can want module A to run all unit tests, and module B to skip some tests. You need to configure maven-surefire-plugin to achieve this, so the plugin configuration of the two modules will be inconsistent. That is to say, simply extracting the plugin configuration to the parent POM's pluginManagement is often not suitable for all situations, so we need to pay attention when using it. Only those common plugin configurations should be extracted to the parent POM using pluginManagement. .

Regarding plug-in pluginManagement, Maven does not provide management in a similar way to import scope dependencies, so we can only rely on inheritance. Fortunately, the number of plug-in configurations is generally far less than that of dependency configurations, so this is not a problem.

summary

This concludes the introduction to Maven POM refactoring. Basically, if you master the refactoring techniques described in this and the last Maven column, and understand the purpose principle behind it, then you will definitely make the project's POM clearer and easier to understand, and you can avoid some potential risks of. While Maven is just a tool to help you build projects and manage dependencies, POMs are not part of your official production code. But we should also take POM seriously. It is a bit like testing code. In the past, everyone thought that testing code was optional, and they would not refactor and optimize test code carefully. However, with the increasing use of agile development and TDD methods People accepted, and the test code got more and more attention from developers. So here I hope that everyone is not only satisfied with a "usable" POM, but can actively repair the bad taste in POM.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326317672&siteId=291194637