如何从插件分叉 Maven 生命周期(在适当的意义上)?

Ale*_*sen 5 fork maven-plugin maven-3

一般问题: 我正在一家具有面向服务架构的大公司测试 Web 应用程序。由于背景噪音,外部服务在我们的测试环境中经常失败。这会阻止我们服务的集成测试正常运行,因为除非对这些外部服务的调用成功,否则我们的服务将无法工作。出于这个原因,我们希望能够模拟来自外部服务的响应,这样我们就不必依赖它们并且可以单独测试我们自己的服务。

我们希望使用一个名为Mockey的工具。它是一个 Java 程序,通过嵌入式 Jetty 服务器运行并充当服务调用的代理。我们的 Web 应用程序被重新配置为调用 Mockey 而不是外部服务。然后,Mockey 被配置为根据传入的 URL 和标头数据为这些调用提供动态模拟响应。

为了利用这个工具,我们希望能够在 Maven 生命周期的预集成测试阶段启动 Mockey,以便它可以在集成测试阶段使用。

具体问题: 为了在 Maven 生命周期的预集成测试和后集成测试阶段启动和关闭 Mockey,我编写了一个名为 mockey-maven-plugin 的 Maven 3 插件:

mockey-maven-plugin pom.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mycompany.mockey</groupId>
    <artifactId>mockey-maven-plugin</artifactId>
    <packaging>maven-plugin</packaging>
    <version>1.3</version>

    <dependencies>

        <!-- Maven plugin dependencies -->
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.4</version>
            <scope>provided</scope>
        </dependency>

        <!-- Mockey dependency -->
        <dependency>
            <groupId>com.mycompany.mockey</groupId>
            <artifactId>Mockey</artifactId>
            <version>1.16.2015</version>
        </dependency>

    </dependencies>

    <build>

        <plugins>

            <!-- This plugin is used to generate a plugin descriptor
                 xml file which will be packaged with the plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.4</version>
            </plugin>

        </plugins>

    </build>

</project>
Run Code Online (Sandbox Code Playgroud)

mockey-maven-plugin StartMockey 类:

@Mojo(name="start-mockey")
@Execute(phase= LifecyclePhase.PACKAGE) // Not sure about this annotation
public class StartMockey extends AbstractMojo
{
    /**
     * Flag which controls Mockey startup.
     */
    @Parameter(property="mockey.skipStartup", defaultValue="false", required=true)
    private Boolean skipStartup;

    // Do I need these getters and setters or does Maven ignore them?

    public Boolean getSkipStartup()
    {
        return skipStartup;
    }

    public void setSkipStartup(Boolean skipStartup)
    {
        this.skipStartup = skipStartup;
    }

    // *SNIP* Defining Mockey parameters...

    // Maven will call this method to start the mockey-maven-plugin
    public void execute()
    {
        if(skipStartup)
        {
            getLog().info("Skipping Mockey startup");
            return;
        }

        getLog().info("Starting Mockey");

        // Load specified parameters into array
        List<String> argsList = new ArrayList<>();

        // *SNIP* Adding Mockey parameters to argList...

        String[] args = new String[argsList.size()];
        argsList.toArray(args);

        // Start Mockey with specified parameters and wait for it to return
        try
        {
            JettyRunner.main(args);
        }
        catch(Exception e)
        {
            getLog().error("Mockey died... :(");
        }
        getLog().info("mockey-maven-plugin now exiting");
    }
}
Run Code Online (Sandbox Code Playgroud)

mockey-maven-plugin ShutdownMockey 类:

@Mojo(name="shutdown-mockey")
public class ShutdownMockey extends AbstractMojo
{
    /**
     * Flag which controls Mockey shutdown.
     */
    @Parameter(property="mockey.skipShutdown")
    private Boolean skipShutdown;

    // Again, Do I need these getters and setters or does Maven ignore them?

    public Boolean getSkipShutdown()
    {
        return skipShutdown;
    }

    public void setSkipShutdown(Boolean skipShutdown)
    {
        this.skipShutdown = skipShutdown;
    }

    public void execute()
    {
        if(skipShutdown)
        {
            getLog().info("Skipping Mockey shutdown");
            return;
        }
        getLog().info("Shutting down Mockey");
        JettyRunner.stopServer();
        getLog().info("mockey-maven-plugin now exiting");
    }
}
Run Code Online (Sandbox Code Playgroud)

我的团队项目的 pom.xml 文件中的 mockey-maven-plugin 插件条目:

<plugin>
    <groupId>com.mycompany.mockey</groupId>
    <artifactId>mockey-maven-plugin</artifactId>
    <version>1.3</version>
    <configuration>
        <skipShutdown>${keepMockeyRunning}</skipShutdown>
        <skipStartup>${skipMockey}</skipStartup>

        <!-- *SNIP* Other Mockey parameters... -->

    </configuration>
    <executions>
        <execution>
            <id>start-mockey</id>
            <goals>
                <goal>start-mockey</goal>
            </goals>
            <phase>pre-integration-test</phase>
        </execution>
        <execution>
            <id>shutdown-mockey</id>
            <goals>
                <goal>shutdown-mockey</goal>
            </goals>
            <phase>post-integration-test</phase>
        </execution>
    </executions>
</plugin>
Run Code Online (Sandbox Code Playgroud)

这个插件可以很好地在预集成测试阶段启动 Mockey,但会阻止构建,直到 Mockey 退出。我不确定为什么会发生这种情况,因为我专门添加了此注释以防止出现该问题:

 @Execute(phase= LifecyclePhase.PACKAGE)
Run Code Online (Sandbox Code Playgroud)

我实际上是从另一个插件复制了这个注释,这正是我在这里尝试做的(我们使用 maven-tomcat7-plugin 在预集成测试阶段在本地启动我们的 Web 应用程序,并在后期关闭它 -集成测试阶段)。我认为这会以相同的方式工作,但我看到了不同的行为。

这是我希望看到的:

  1. Maven 构建从单线程开始。
  2. 该线程贯穿从验证到包(参考)的所有生命周期阶段,并执行所有具有绑定到这些阶段的目标的插件。
  3. 线程进入pre-integration-test 阶段,看到mockey-maven-plugin 的start-mockey 目标绑定到pre-integration-test 阶段,并尝试执行start-mockey 目标。
  4. start-mockey 目标被注释为在第二个线程(而不是第一个线程)上执行,而不在新线程上运行任何其他生命周期阶段之前或之后的任何其他目标。第二个线程通过调用 JettyRunner.main(args) 来启动 Mockey 的 Jetty 服务器并暂时阻止该方法(它正在运行 Mockey)。
  5. 第一个线程继续其他目标和阶段(即:运行集成测试)。
  6. 第一个线程进入post-integration-test阶段,看到mockey-maven-plugin的shutdown-mockey目标绑定到post-integration-test阶段,执行shutdown-mockey目标。
  7. shutdown-mockey 目标调用 JettyRunner.stopServer(),它挂钩到 JettyRunner 类中的一个静态对象,并通知第一个线程关闭 Jetty。与此同时,第一个线程等待来自第二个线程的信号(或者它可能正在轮询,我真的不知道)Jetty 已经关闭。
  8. 第二个线程完成关闭 Jetty,向第一个线程发出信号表示它可以继续,然后杀死自己。
  9. 第一个线程继续任何其他目标和 Maven 生命周期阶段。

这是我实际看到的情况:

  1. Maven 构建从单线程开始。
  2. 该线程贯穿从验证到包(参考)的所有生命周期阶段,并执行所有具有绑定到这些阶段的目标的插件。
  3. 线程进入pre-integration-test 阶段,看到mockey-maven-plugin 的start-mockey 目标绑定到pre-integration-test 阶段,并尝试执行start-mockey 目标。
  4. start-mockey 目标被注释为在第二个线程上执行。第二个线程从验证阶段重新开始整个 Maven 生命周期。
  5. 第一个线程在等待第二个线程退出时阻塞。
  6. 第二个线程一直运行到打包阶段,然后杀死自己。
  7. 第一个线程被解锁并从它停止的地方开始。它自己执行 start-mockey 目标(从不由第二个线程运行)。这会调用 JettyRunner.main(args) 并且线程在运行 Mockey 的 Jetty 服务器时会阻塞。
  8. 在手动终止 Jetty 服务器(以及 Maven 生命周期的其余部分)之前,该线程一直处于阻塞状态。

这让我感到困惑,主要是因为 Maven 似乎有一个与我熟悉的不同的分叉概念。对我来说,分叉意味着在特定点发散,而不是重新开始,也不影响原始过程。当我们在 Unix 中 fork 一个进程时,它会复制第一个进程的堆栈和函数指针。它不会从程序的开头重新开始。类似地,当我们 fork 代码存储库时,我们从当前在原始存储库中的所有文件和目录开始。我们不会白手起家。那么,为什么当我们“分叉”一个 Maven 生命周期时,它会放弃一切,重新开始,并阻塞原始线程?这对我来说似乎一点也不像分叉。这是我读过的一些描述 Maven 中“分叉”的文档:

剩余问题:

  • 我怎样才能让 Maven 在我熟悉的意义上分叉?
  • 将 Maven 生命周期分叉到在您分叉的那个阶段之前发生的阶段是什么意思?例如,从预集成-测试阶段分叉到打包阶段是什么意思?
  • 为什么你认为Tomcat7插件会这样做(从预集成测试阶段分叉到打包阶段)?
  • Tomcat7 插件有什么不同导致相同的注释在我的插件中表现不同?

已回答的问题(见下文): - 我是否应该在我的插件的注释中指定另一个阶段以使其按预期运行,或者我应该以完全不同的方式使用执行注释?