多模块化改造若干问题与解决方案(持续更新)

Scroll Down

1. 循环依赖问题

Maven中如果出现:

A 依赖 B
B 依赖 A

就会导致Maven Compile AssertionError:

[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ xxx ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 302 source files to /Path
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.124 s (Wall Clock)
[INFO] Finished at: 2021-11-12T10:12:25+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] AssertionError -> [Help 1]
java.lang.AssertionError
    at com.sun.tools.javac.util.Assert.error (Assert.java:155)
    at com.sun.tools.javac.util.Assert.check (Assert.java:46)
    at com.sun.tools.javac.comp.Modules.enter (Modules.java:247)
    at com.sun.tools.javac.main.JavaCompiler.readSourceFile (JavaCompiler.java:837)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment$ImplicitCompleter.complete (JavacProcessingEnvironment.java:1530)
    at com.sun.tools.javac.code.Symbol.complete (Symbol.java:642)
    at com.sun.tools.javac.code.Symbol$ClassSymbol.complete (Symbol.java:1326)
    at com.sun.tools.javac.code.Type$ClassType.complete (Type.java:1140)
    at com.sun.tools.javac.code.Type$ClassType.getTypeArguments (Type.java:1066)
    at com.sun.tools.javac.code.Printer.visitClassType (Printer.java:237)
    at com.sun.tools.javac.code.Printer.visitClassType (Printer.java:52)
    at com.sun.tools.javac.code.Type$ClassType.accept (Type.java:993)
    at com.sun.tools.javac.code.Printer.visit (Printer.java:136)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArgument (AbstractDiagnosticFormatter.java:199)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArguments (AbstractDiagnosticFormatter.java:167)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:111)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:67)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArgument (AbstractDiagnosticFormatter.java:185)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArguments (AbstractDiagnosticFormatter.java:167)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:111)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:67)
    at com.sun.tools.javac.util.JCDiagnostic.getMessage (JCDiagnostic.java:788)
    at com.sun.tools.javac.api.ClientCodeWrapper$DiagnosticSourceUnwrapper.getMessage (ClientCodeWrapper.java:799)
    at org.codehaus.plexus.compiler.javac.JavaxToolsCompiler.compileInProcess (JavaxToolsCompiler.java:131)
    at org.codehaus.plexus.compiler.javac.JavacCompiler.performCompile (JavacCompiler.java:174)
    at org.apache.maven.plugin.compiler.AbstractCompilerMojo.execute (AbstractCompilerMojo.java:1134)
    at org.apache.maven.plugin.compiler.CompilerMojo.execute (CompilerMojo.java:187)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder$1.call (MultiThreadedBuilder.java:202)
    at org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder$1.call (MultiThreadedBuilder.java:198)
    at java.util.concurrent.FutureTask.run (FutureTask.java:264)
    at java.util.concurrent.Executors$RunnableAdapter.call (Executors.java:515)
    at java.util.concurrent.FutureTask.run (FutureTask.java:264)
    at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1128)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:628)
    at java.lang.Thread.run (Thread.java:829)
[ERROR] java.lang.AssertionError
java.util.concurrent.ExecutionException: java.lang.AssertionError
    at java.util.concurrent.FutureTask.report (FutureTask.java:122)
    at java.util.concurrent.FutureTask.get (FutureTask.java:191)
    at org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder.multiThreadedProjectTaskSegmentBuild (MultiThreadedBuilder.java:142)
    at org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder.build (MultiThreadedBuilder.java:103)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:956)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:288)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:192)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
    at org.codehaus.classworlds.Launcher.main (Launcher.java:47)
Caused by: java.lang.AssertionError
    at com.sun.tools.javac.util.Assert.error (Assert.java:155)
    at com.sun.tools.javac.util.Assert.check (Assert.java:46)
    at com.sun.tools.javac.comp.Modules.enter (Modules.java:247)
    at com.sun.tools.javac.main.JavaCompiler.readSourceFile (JavaCompiler.java:837)
    at com.sun.tools.javac.processing.JavacProcessingEnvironment$ImplicitCompleter.complete (JavacProcessingEnvironment.java:1530)
    at com.sun.tools.javac.code.Symbol.complete (Symbol.java:642)
    at com.sun.tools.javac.code.Symbol$ClassSymbol.complete (Symbol.java:1326)
    at com.sun.tools.javac.code.Type$ClassType.complete (Type.java:1140)
    at com.sun.tools.javac.code.Type$ClassType.getTypeArguments (Type.java:1066)
    at com.sun.tools.javac.code.Printer.visitClassType (Printer.java:237)
    at com.sun.tools.javac.code.Printer.visitClassType (Printer.java:52)
    at com.sun.tools.javac.code.Type$ClassType.accept (Type.java:993)
    at com.sun.tools.javac.code.Printer.visit (Printer.java:136)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArgument (AbstractDiagnosticFormatter.java:199)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArguments (AbstractDiagnosticFormatter.java:167)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:111)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:67)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArgument (AbstractDiagnosticFormatter.java:185)
    at com.sun.tools.javac.util.AbstractDiagnosticFormatter.formatArguments (AbstractDiagnosticFormatter.java:167)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:111)
    at com.sun.tools.javac.util.BasicDiagnosticFormatter.formatMessage (BasicDiagnosticFormatter.java:67)
    at com.sun.tools.javac.util.JCDiagnostic.getMessage (JCDiagnostic.java:788)
    at com.sun.tools.javac.api.ClientCodeWrapper$DiagnosticSourceUnwrapper.getMessage (ClientCodeWrapper.java:799)
    at org.codehaus.plexus.compiler.javac.JavaxToolsCompiler.compileInProcess (JavaxToolsCompiler.java:131)
    at org.codehaus.plexus.compiler.javac.JavacCompiler.performCompile (JavacCompiler.java:174)
    at org.apache.maven.plugin.compiler.AbstractCompilerMojo.execute (AbstractCompilerMojo.java:1134)
    at org.apache.maven.plugin.compiler.CompilerMojo.execute (CompilerMojo.java:187)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder$1.call (MultiThreadedBuilder.java:202)
    at org.apache.maven.lifecycle.internal.builder.multithreaded.MultiThreadedBuilder$1.call (MultiThreadedBuilder.java:198)
    at java.util.concurrent.FutureTask.run (FutureTask.java:264)
    at java.util.concurrent.Executors$RunnableAdapter.call (Executors.java:515)
    at java.util.concurrent.FutureTask.run (FutureTask.java:264)
    at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1128)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:628)
    at java.lang.Thread.run (Thread.java:829)
[ERROR] 
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/AssertionError

在多模块项目中要尤其注意模块之间的依赖情况,避免循环依赖

2. API包如何打包给其他服务引入

我们的多模块中有一个API模块/Client模块(COLA),这个模块是需要deploy到私服上让其他服务引入进行使用的,这里有一些注意点。

2.1. 在父POM中进行Deploy操作

一般,我们的多模块可能是这样:

root.pom
	- api-module.pom
  - ....其他模块pom

例中的api-module.pom就是我们的API模块,但如果单独deploy api模块的话,可能在实际使用时会报错,找不到parent pom,也就是例中的root.pom。
因此,需要在父pom执行Deploy操作,这样可以将root.pom和api模块一起部署到私服上。
当然,这里可能会连带其他模块一起deploy上去,如果不需要的话,可以使用下面的方式跳过:

<plugins>
  <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <configuration>
      <skip>true</skip>
    </configuration>
  </plugin>
</plugins>

2.2. 如果API包的配置了spring-boot-maven-plugin需要注意

一般情况API包不会引入spring-boot-maven-plugin,但是不排除一些父POM中依赖传递下来的情况,因此,如果API包的父POM中配置了SpringBoot的Maven插件:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>repackage</goal>
      </goals>
    </execution>
  </executions>
  ......
</plugin>

你可能会遇到打出来的包无法被其他服务引入,或者即使引入也无法使用包中的class,这是因为引入了SpringBoot的Maven插件后,如果goal配置repackage,会在maven package阶段,对打包后的jar进行二次打包,打包成SpringBoot的可执行Jar包,无法被其他服务作为普通jar包引入。
解决方式有2个:

  1. 阻止SpringBoot进行repackage(因为API包本身也不应该是一个可执行Jar):
<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <executions>
            <execution>
                <phase>none</phase>
            </execution>
        </executions>
        ......
    </plugin>
</plugins>
  1. 配置classifier参数,这样SpringBoot打包出会有两个jar包(一个普通Jar包,一个SpringBoot可执行Jar包)
<plugins>
  <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <configuration>
      <file>maycur-sampling-review-service-client-${project.version}.jar</file>
    </configuration>
  </plugin>
  <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
      <classifier>exec</classifier>
    </configuration>
  </plugin>
</plugins>

如果使用第二种方式,还需要指定一下Deploy的jar包避免将SpringBoot的可执行Jar部署到私服上

<plugins>
  <plugin>
    <artifactId>maven-deploy-plugin</artifactId>
    <configuration>
      <file>artifactId-${project.version}.jar</file>
    </configuration>
  </plugin>
</plugins>

3. SpringBoot的多模块项目如何启动

在多模块项目中,往往SpringBoot的启动类往往还是会放在某一个模块中,比如COLA架构中,是放在start模块下,其他依赖的模块通过maven dependencies的方式引入就可以

3.1. 如果你的子模块中配置了spring-boot-maven-plugin需要注意

这个同2.2,也是需要调整spring-boot-maven-plugin相关配置,避免报错。

4. 运维相关

多模块项目打包需要和运维协商好,这个各家公司不同。

5. 如果你想统一整个模块的版本号

可以使用一个占位字段properties属性,在parent的POM中用占位的方式去做:

<groupId>com.xx</groupId>
<artifactId>xx-parent</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<name>xx-parent</name>

<properties>
  <revision>3.0.0-SNAPSHOT</revision>
</properties>

这里revision就是一个整体版本号的property,子模块中也可以使用。
不过,在打包时Maven会将“$”这个作为version打包进去,也就无法被其他服务引入了,这里可以引入flatten-maven-plugin:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>flatten-maven-plugin</artifactId>
  <version>1.1.0</version>
  <configuration>
    <updatePomFile>true</updatePomFile>
    <flattenMode>resolveCiFriendliesOnly</flattenMode>
  </configuration>
  <executions>
    <execution>
      <id>flatten</id>
      <phase>process-resources</phase>
      <goals>
        <goal>flatten</goal>
      </goals>
    </execution>
    <execution>
      <id>flatten.clean</id>
      <phase>clean</phase>
      <goals>
        <goal>clean</goal>
      </goals>
    </execution>
  </executions>
</plugin>

这个插件的作用就是在install或者deploy的时候,生成一个新的替换占位符的pom文件,也就是帮你替换了这些变量,接着把新的pom文件打入到jar包里。

6. 参考资料