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

时间:2015-01-17 00:51:59

标签: maven-3 maven-plugin fork

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

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

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

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

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>

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");
    }
}

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");
    }
}

我团队项目的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>

此插件适用于在预集成测试阶段启动Mockey,但阻止构建直到Mockey退出。我不确定为什么会这样,因为我专门添加了这个注释来防止这个问题:

 @Execute(phase= LifecyclePhase.PACKAGE)

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

以下是我想要看到的内容:

  1. Maven构建从单个线程开始。
  2. 此线程贯穿从validate到package(reference)的所有生命周期阶段,并执行所有具有绑定到这些阶段的目标的插件。
  3. 线程进入预集成测试阶段,看到mockey-maven-plugin的开始 - 曲棍球目标必然会进入预集成测试阶段,并尝试执行开始 - 曲棍球目标
  4. 开始曲棍球目标注释为在第二个线程(第一个线程)上执行,而不运行 任何 的任何其他目标新线程之前或之后的其他生命周期阶段。第二个线程通过调用JettyRunner.main(args)启动Mockey的Jetty服务器并暂时阻止该方法(它正在运行Mockey)。
  5. 第一个线程继续进行其他目标和阶段(IE:运行集成测试)。
  6. 第一个线程进入集成后测试阶段,看到mockey-maven-plugin的关闭 - 曲棍球目标必然会进入集成后测试阶段,并执行关闭 - 曲棍球目标。
  7. shutdown-mockey目标调用JettyRunner.stopServer(),它挂接到JettyRunner类中的静态对象,并通知第一个线程关闭Jetty。与此同时,第一个线程等待来自第二个线程的信号(或者可能是它的轮询,我真的不知道)Jetty已经关闭。
  8. 第二个线程完成关闭Jetty,向第一个线程发出信号,告知它可以继续,然后自杀。
  9. 第一个主题继续进行任何其他目标和Maven生命周期阶段。
  10. 以下是我实际看到的情况:

    1. Maven构建从单个线程开始。
    2. 此线程贯穿从validate到package(reference)的所有生命周期阶段,并执行所有具有绑定到这些阶段的目标的插件。
    3. 线程进入预集成测试阶段,看到mockey-maven-plugin的开始 - 曲棍球目标必然会进入预集成测试阶段,并尝试执行开始 - 曲棍球目标
    4. 开始 - 曲棍球目标注释为在第二个线程上执行。第二个线程从验证阶段开始再次启动整个Maven生命周期。
    5. 第一个线程在等待第二个线程退出时阻塞。
    6. 第二个线程一直运行到包阶段,然后自行终止。
    7. 第一个线程被解锁并从中断处继续。它自己执行start-mockey目标(从不运行第二个线程)。这会调用JettyRunner.main(args),然后线程会在运行Mockey的Jetty服务器时阻塞。
    8. 线程一直被阻塞,直到Jetty服务器手动被杀死(以及Maven生命周期的其余部分)。
    9. 这让我感到困惑,主要是因为Maven似乎有一个不同于我所熟悉的分叉概念。对我来说,分叉意味着在某个特定点发散,而不是重新开始,而不是影响原始过程。当我们在Unix中分叉一个进程时,它会复制第一个进程的堆栈和函数指针。它不会从程序的开头重新开始。类似地,当我们分叉代码存储库时,我们从当前存储库中的所有文件和目录开始。我们不会重新开始一片空白。那么,为什么,当我们&#34; fork&#34; Maven生命周期是否会放弃所有内容,重新开始并阻止原始线程?这根本不像是向我求助。这是我读过的一些描述&#34;分叉&#34;在Maven:

      剩余问题:

      • 在我熟悉的意义上,如何让Maven得分?
      • 将Maven生命周期分配到一个阶段之前是什么意思,这个阶段发生在你要分离的阶段之前?例如,从预集成测试阶段分配到包阶段是什么意思?
      • 为什么您认为Tomcat7插件正在执行此操作(从预集成测试阶段分配到封装阶段)?
      • 导致相同注释在我的插件中表现不同的Tomcat7插件有什么不同?

      已回答的问题(见下文):   - 是否有另一个阶段,我应该在我的插件的注释中指定它以使其按照需要运行,还是应该以一种根本不同的方式使用执行注释?

2 个答案:

答案 0 :(得分:4)

请参阅https://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-plugins-lifecycle.html

文档似乎表明您应该创建一个仅包含开始 - 曲棍球目标的自定义生命周期。然后,您的@Execute注释应指定目标和生命周期。这应该分开执行,但只执行你的开始曲棍球。我想你可以正常运行终极骑师。

答案 1 :(得分:2)

我仍然不明白Maven正在做什么,但我找到了解决问题的方法:

  1. 我从StartMockey类中删除了@Execute注释。
  2. 我自己用Java编写了这个过程。
  3. StartMockey.execute()中新代码的片段:

    // Start Mockey with the specified parameters on a new thread.
    MockeyRunner mockeyRunner = new MockeyRunner(args);
    mockeyRunnerThread = new Thread(mockeyRunner);
    mockeyRunnerThread.start();
    

    新的MockeyRunner课程:

    public class MockeyRunner implements Runnable
    {
        private String[] args;
        private Exception exception;
    
        /**
         * We cannot throw the Exception directly in run() since we're implementing the runnable interface which does not
         * allow exception throwing.  Instead we must store the exception locally and check for it in whatever class is
         * managing this MockeyRunner instance after the run method has returned.
         * @return Exception thrown by Mockey
         */
        public Exception getException()
        {
            return exception;
        }
    
        /**
         * Constructor
         * @param args The arguments to pass to Mockey on startup
         */
        public MockeyRunner(String[] args)
        {
            this.args = args;
        }
    
        /**
         * This method starts Mockey from inside a new Thread object in an external class.  It is called internally by Java
         * when the Thread.start() method is called on that object.
         */
        @Override
        public void run()
        {
            try
            {
                JettyRunner.main(args);
            }
            catch(Exception e)
            {
                exception = e;
            }
        }
    }
    

    我不会接受这个解决方案作为答案。虽然它解决了我的问题,但我仍然想知道Maven“forking”的全部内容!