You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Maven多模块项目单独执行插件目标的方案及默认启用风险咨询

Maven多模块项目依赖查找扩展的兼容性问题

问题背景

我正在解决Maven里一个常见问题,Stack Overflow上有不少相关提问,比如:

  • Maven多模块项目中使用exec:java目标
  • Spring Boot多模块Maven项目打包失败

现有几种解决方案但都不尽人意:

  • mvn install——这正是我想避免的操作
  • 涉及<skip>/<properties>/<profiles>的复杂项目配置
  • 打包时用maven-dependency-plugin把依赖复制到模块文件夹并配置classpath

我了解到Maven 4的root-reactor aware subfolder builds特性,但Maven 4还未发布,因此得寻找适配Maven 3的解决方案。

找到的Maven 3扩展点

研究后发现Maven 3的几个实用扩展点:

DefaultArtifactResolver.java代码片段

if ( workspace != null )
{
    File file = workspace.findArtifact( artifact );
    if ( file != null )
    {
        artifact = artifact.setFile( file );
        result.setArtifact( artifact );
        result.setRepository( workspace.getRepository() );
        artifactResolved( session, trace, artifact, result.getRepository(), null );
        continue;
    }
}

DefaultProjectDependenciesResolver.java代码片段

for ( RepositorySessionDecorator decorator : decorators )
{
    RepositorySystemSession decorated = decorator.decorate( project, session );
    if ( decorated != null )
    {
        session = decorated;
    }
}

实现的Maven扩展

最终我开发了一个简单的Maven扩展(完整代码见github):

@Component(role = RepositorySessionDecorator.class)
public class FakeRepositorySessionDecorator implements RepositorySessionDecorator {

    @Requirement
    protected ArtifactHandlerManager artifactHandlerManager;

    @Override
    public RepositorySystemSession decorate(MavenProject project, RepositorySystemSession session) {
        String enabled = session.getUserProperties().get("fakerepo");
        if (!"true".equalsIgnoreCase(enabled)) {
            return null;
        }
        MavenProject root = project;
        while (root != null && !root.isExecutionRoot()) {
            root = root.getParent();
        }
        if (root != null) {
            WorkspaceReader workspaceReader = session.getWorkspaceReader();
            workspaceReader = new FakeWorkspaceReader(workspaceReader, root, artifactHandlerManager);
            return new DefaultRepositorySystemSession(session)
                    .setWorkspaceReader(workspaceReader);
        }
        return null;
    }

}

这个扩展的逻辑是:当执行Maven插件目标时指定-Dfakerepo参数,扩展会将workspace范围从单个模块扩展到项目根目录,扩展后的workspace会在子模块文件夹中查找已打包的产物。执行以下命令即可达到预期效果:

mvn clean package
mvn exec:exec -pl submodule -Dfakerepo

核心问题

如果移除-Dfakerepo的要求,默认启用上述行为(即对所有Maven目标和生命周期阶段应用该逻辑),会破坏哪些现有功能?我认为优先在子模块文件夹中查找已打包产物比从本地仓库查找更合理,但有没有忽略某些场景?


更新:潜在的不符合预期场景

我想到一种可能出现问题的情况:

  • 多模块项目中有两个子模块A和B,且B依赖A
  • 开发者修改了A的代码,执行命令mvn -am test -pl B

此时如果A之前已打包,我的扩展会迫使Maven使用旧的产物;而默认实现会使用A/target/classes作为类路径,但如果未执行clean,A/target/classes中也可能存在旧的类文件,因此默认实现的行为也并非完全理想。


更新2:痛点场景说明

我需要明确这个问题的实际痛点:

  1. 开发者希望维护自有基础设施(主要是数据库),比如启动/停止多个实例、执行数据库迁移、调试等,同时避免CI环境的问题(比如“CI流水线莫名出错”)。我们希望操作尽可能简单,例如在dev子模块中通过exec目标执行数据库迁移:
<dependencies>
    <dependency>
        <groupId>tld.project</groupId>
        <artifactId>another-submodule</artifactId>
    </dependency>
</dependencies>

<execution>
    <id>liquibase-update-primary</id>
    <phase>install</phase>
    <goals>
        <goal>exec</goal>
    </goals>
    <configuration>
        <executable>java</executable>
        <arguments>
            <argument>-classpath</argument>
            <!-- expecting to get module dependencies there -->
            <classpath/>
            <!-- main class -->
            <argument>liquibase.integration.commandline.Main</argument>
            <!-- covered by project properties -->
            <argument>--changeLogFile=${primary.changeLogFile}</argument>
            <!-- covered by profile properties -->
            <argument>--url=${jdbc.url}</argument>
            <argument>--driver=${jdbc.driver}</argument>
            <argument>--username=${jdbc.username}</argument>
            <argument>--password=${jdbc.password}</argument>
            <argument>--logLevel=info</argument>
            <argument>update</argument>
        </arguments>
    </configuration>
</execution>

这在Maven 3中无法正常工作,因为它会期望在本地仓库找到tld.project-another-submodule的产物,只能通过maven-dependency-plugin做如下变通:

<execution>
    <id>liquibase-dependencies</id>
    <phase>package</phase>
    <goals>
        <goal>copy</goal>
    </goals>
    <configuration>
        <artifactItems>
            <artifactItem>
                <!-- 
                    now we may tell liquibase to load extra jars
                    from  ${project.build.directory}/liquibase
                -->
                <groupId>tld.project</groupId>
                <artifactId>another-submodule</artifactId>
                <type>jar</type>
                <destFileName>another-submodule.jar</destFileName>
                <outputDirectory>${project.build.directory}/liquibase</outputDirectory>
            </artifactItem>
        </artifactItems>
    </configuration>
</execution>
  1. 我们希望无需重新编译/打包整个项目,就能单独运行集成测试,例如执行mvn verify -pl it-submodule,这对开发者和CI环境都有用:

    • 开发者和DevOps可以在package和verify阶段之间执行基础设施相关操作
    • CI环境可以多次运行verify(确认是否存在不稳定测试)
  2. 大型项目中,额外的生命周期步骤会耗费大量时间


内容的提问来源于stack exchange,提问作者Andrey B. Panfilov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 11:54:15