如何从插件中分叉 Maven 生命周期(在适当的意义上)?
How to fork a Maven lifecycle (in the proper sense) from a plugin?
一般问题: 我正在一家具有面向服务架构的大公司测试 Web 应用程序。由于背景噪音,外部服务经常在我们的测试环境中失败。这会阻止 运行 正确地对我们的服务进行集成测试,因为除非对这些外部服务的调用成功,否则我们的服务将无法运行。出于这个原因,我们希望能够模拟来自外部服务的响应,这样我们就不必依赖它们并可以单独测试我们自己的服务。
我们希望使用一个名为 Mockey 的工具。这是一个 Java 程序,它 运行 通过嵌入式 Jetty 服务器运行,并充当服务调用的代理。我们的 Web 应用程序 re-configured 调用 Mockey 而不是外部服务。然后将 Mockey 配置为根据传入的 URL 和 header 数据为这些调用提供动态模拟响应。
为了利用这个工具,我们希望能够在 Maven 生命周期的 pre-integration-test 阶段启动 Mockey,以便在 integration-test 阶段可以使用它。
具体问题: 为了在 Maven 生命周期的 pre-integration-test 和 post-integration-test 阶段启动和关闭 Mockey 我写了一个名为 mockey-maven-plugin:
的 Maven 3 插件
mockey-maven-pluginpom.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-pluginStartMockeyclass:
@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 class:
@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>
此插件在 pre-integration-test 阶段启动 Mockey 时工作正常,但会阻止构建,直到 Mockey 退出。我不确定为什么会发生这种情况,因为我专门添加了此注释以防止出现该问题:
@Execute(phase= LifecyclePhase.PACKAGE)
我实际上是从另一个插件复制了这个注解,它做的正是我在这里想做的(我们使用 maven-tomcat7-plugin 在 pre-integration-test 阶段在本地启动我们的 Web 应用程序并关闭它在 post-integration-test 阶段)。我以为这会以同样的方式工作,但我看到了不同的行为。
这是我希望看到的结果:
- Maven 构建在单线程上开始。
- 此线程 运行 经历了从验证到打包 (reference) 的所有生命周期阶段,并执行目标与这些阶段绑定的所有插件。
- 线程进入 pre-integration-test 阶段,发现 mockey-maven-plugin 的 start-mockey 目标绑定到 pre-integration-test 阶段,并尝试执行 start-mockey目标。
- start-mockey目标被注释为在第二个线程上执行(不是第一个线程)没有运行宁任何 在新线程之前或之后 any 其他生命周期阶段的其他目标。第二个线程通过调用 JettyRunner.main(args) 启动 Mockey 的 Jetty 服务器并暂时阻塞该方法(它是 运行ning Mockey)。
- 第一个线程继续其他目标和阶段(即:运行 集成测试)。
- 第一个线程进入 post-integration 测试阶段,看到 mockey-maven-plugin 的 shutdown-mockey 目标绑定到 post-integration-test阶段,并执行 shutdown-mockey 目标。
- shutdown-mockey 目标调用 JettyRunner.stopServer() 挂钩到 JettyRunner class 内部的静态 object 并向第一个线程发出关闭 Jetty 的信号。与此同时,第一个线程等待来自第二个线程的信号(或者可能是轮询,我真的不知道)Jetty 已经关闭。
- 第二个线程完成关闭 Jetty,向第一个线程发出可以继续的信号,然后自行终止。
- 第一个线程继续任何其他目标和 Maven 生命周期阶段。
这是我实际看到的情况:
- Maven 构建在单线程上开始。
- 此线程 运行 经历了从验证到打包 (reference) 的所有生命周期阶段,并执行目标与这些阶段绑定的所有插件。
- 线程进入 pre-integration-test 阶段,发现 mockey-maven-plugin 的 start-mockey 目标绑定到 pre-integration-test 阶段,并尝试执行 start-mockey目标。
- start-mockey 目标被注释为在第二个线程上执行。第二个线程从验证阶段开始重新开始整个 Maven 生命周期。
- 第一个线程在等待第二个线程退出时阻塞。
- 第二个线程 运行 一直通过打包阶段,然后自行终止。
- 第一个线程已解除阻塞并从中断处继续。它自己执行 start-mockey 目标(第二个线程从不 运行 )。这会调用 JettyRunner.main(args),然后线程会在 运行ning Mockey 的 Jetty 服务器时阻塞。
- 线程保持阻塞,直到 Jetty 服务器被手动 终止(连同 Maven 生命周期的其余部分)。
这让我很困惑,主要是因为 Maven 似乎有一个与我所熟悉的不同的分叉概念。对我来说,分叉意味着在特定点发散,不重新开始,也不影响原来的过程。当我们在 Unix 中 fork 一个进程时,它会复制第一个进程的堆栈和函数指针。它不会从程序的开头重新开始。类似地,当我们 fork 一个代码存储库时,我们从当前在原始存储库中的所有文件和目录开始。我们不会从头开始。那么,为什么当我们 "fork" Maven 生命周期时,它会放弃一切,重新开始并阻塞原始线程?在我看来,这根本不像是分叉。这是我读过的一些描述 Maven 中 "forking" 的文档:
- "Running the ZipForkMojo will fork the lifecycle"
- "Any plugin that declares @execute [phase] will cause the build to fork"
- "goal=goal to fork... lifecycle=lifecycle id to fork... phase=lifecycle phase to fork..."
剩余问题:
- 如何让 Maven 在我熟悉的意义上进行分叉?
- 将 Maven 生命周期分叉到发生在您分叉的阶段之前的阶段是什么意思?例如,从 pre-integration-test 阶段分叉到 package 阶段是什么意思?
- 为什么您认为 Tomcat7 插件会这样做(从 pre-integration 测试阶段分叉到包阶段)?
- 导致相同注释在我的插件中表现不同的 Tomcat7 插件有何不同?
已回答问题(见下文):
- 我是否应该在我的插件的注释中指定另一个阶段以使其按预期运行,或者我应该以根本不同的方式使用执行注释?
我仍然不明白 Maven 在做什么,但我确实找到了解决它的方法:
- 我从 StartMockey class 中删除了 @Execute 注释。
- 我自己在 Java.
中创建了进程
StartMockey.execute():
中的新代码片段
// Start Mockey with the specified parameters on a new thread.
MockeyRunner mockeyRunner = new MockeyRunner(args);
mockeyRunnerThread = new Thread(mockeyRunner);
mockeyRunnerThread.start();
新的 MockeyRunner class:
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" 是什么!
见https://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-plugins-lifecycle.html
文档似乎表明您应该创建一个仅包含 start-mockey 目标的自定义生命周期。然后你的@Execute 注释应该指定目标和生命周期。那应该分叉执行但只执行你的开始嘲笑。我想你可以像往常一样 运行 end-mockey。
一般问题: 我正在一家具有面向服务架构的大公司测试 Web 应用程序。由于背景噪音,外部服务经常在我们的测试环境中失败。这会阻止 运行 正确地对我们的服务进行集成测试,因为除非对这些外部服务的调用成功,否则我们的服务将无法运行。出于这个原因,我们希望能够模拟来自外部服务的响应,这样我们就不必依赖它们并可以单独测试我们自己的服务。
我们希望使用一个名为 Mockey 的工具。这是一个 Java 程序,它 运行 通过嵌入式 Jetty 服务器运行,并充当服务调用的代理。我们的 Web 应用程序 re-configured 调用 Mockey 而不是外部服务。然后将 Mockey 配置为根据传入的 URL 和 header 数据为这些调用提供动态模拟响应。
为了利用这个工具,我们希望能够在 Maven 生命周期的 pre-integration-test 阶段启动 Mockey,以便在 integration-test 阶段可以使用它。
具体问题: 为了在 Maven 生命周期的 pre-integration-test 和 post-integration-test 阶段启动和关闭 Mockey 我写了一个名为 mockey-maven-plugin:
的 Maven 3 插件mockey-maven-pluginpom.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-pluginStartMockeyclass:
@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 class:
@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>
此插件在 pre-integration-test 阶段启动 Mockey 时工作正常,但会阻止构建,直到 Mockey 退出。我不确定为什么会发生这种情况,因为我专门添加了此注释以防止出现该问题:
@Execute(phase= LifecyclePhase.PACKAGE)
我实际上是从另一个插件复制了这个注解,它做的正是我在这里想做的(我们使用 maven-tomcat7-plugin 在 pre-integration-test 阶段在本地启动我们的 Web 应用程序并关闭它在 post-integration-test 阶段)。我以为这会以同样的方式工作,但我看到了不同的行为。
这是我希望看到的结果:
- Maven 构建在单线程上开始。
- 此线程 运行 经历了从验证到打包 (reference) 的所有生命周期阶段,并执行目标与这些阶段绑定的所有插件。
- 线程进入 pre-integration-test 阶段,发现 mockey-maven-plugin 的 start-mockey 目标绑定到 pre-integration-test 阶段,并尝试执行 start-mockey目标。
- start-mockey目标被注释为在第二个线程上执行(不是第一个线程)没有运行宁任何 在新线程之前或之后 any 其他生命周期阶段的其他目标。第二个线程通过调用 JettyRunner.main(args) 启动 Mockey 的 Jetty 服务器并暂时阻塞该方法(它是 运行ning Mockey)。
- 第一个线程继续其他目标和阶段(即:运行 集成测试)。
- 第一个线程进入 post-integration 测试阶段,看到 mockey-maven-plugin 的 shutdown-mockey 目标绑定到 post-integration-test阶段,并执行 shutdown-mockey 目标。
- shutdown-mockey 目标调用 JettyRunner.stopServer() 挂钩到 JettyRunner class 内部的静态 object 并向第一个线程发出关闭 Jetty 的信号。与此同时,第一个线程等待来自第二个线程的信号(或者可能是轮询,我真的不知道)Jetty 已经关闭。
- 第二个线程完成关闭 Jetty,向第一个线程发出可以继续的信号,然后自行终止。
- 第一个线程继续任何其他目标和 Maven 生命周期阶段。
这是我实际看到的情况:
- Maven 构建在单线程上开始。
- 此线程 运行 经历了从验证到打包 (reference) 的所有生命周期阶段,并执行目标与这些阶段绑定的所有插件。
- 线程进入 pre-integration-test 阶段,发现 mockey-maven-plugin 的 start-mockey 目标绑定到 pre-integration-test 阶段,并尝试执行 start-mockey目标。
- start-mockey 目标被注释为在第二个线程上执行。第二个线程从验证阶段开始重新开始整个 Maven 生命周期。
- 第一个线程在等待第二个线程退出时阻塞。
- 第二个线程 运行 一直通过打包阶段,然后自行终止。
- 第一个线程已解除阻塞并从中断处继续。它自己执行 start-mockey 目标(第二个线程从不 运行 )。这会调用 JettyRunner.main(args),然后线程会在 运行ning Mockey 的 Jetty 服务器时阻塞。
- 线程保持阻塞,直到 Jetty 服务器被手动 终止(连同 Maven 生命周期的其余部分)。
这让我很困惑,主要是因为 Maven 似乎有一个与我所熟悉的不同的分叉概念。对我来说,分叉意味着在特定点发散,不重新开始,也不影响原来的过程。当我们在 Unix 中 fork 一个进程时,它会复制第一个进程的堆栈和函数指针。它不会从程序的开头重新开始。类似地,当我们 fork 一个代码存储库时,我们从当前在原始存储库中的所有文件和目录开始。我们不会从头开始。那么,为什么当我们 "fork" Maven 生命周期时,它会放弃一切,重新开始并阻塞原始线程?在我看来,这根本不像是分叉。这是我读过的一些描述 Maven 中 "forking" 的文档:
- "Running the ZipForkMojo will fork the lifecycle"
- "Any plugin that declares @execute [phase] will cause the build to fork"
- "goal=goal to fork... lifecycle=lifecycle id to fork... phase=lifecycle phase to fork..."
剩余问题:
- 如何让 Maven 在我熟悉的意义上进行分叉?
- 将 Maven 生命周期分叉到发生在您分叉的阶段之前的阶段是什么意思?例如,从 pre-integration-test 阶段分叉到 package 阶段是什么意思?
- 为什么您认为 Tomcat7 插件会这样做(从 pre-integration 测试阶段分叉到包阶段)?
- 导致相同注释在我的插件中表现不同的 Tomcat7 插件有何不同?
已回答问题(见下文): - 我是否应该在我的插件的注释中指定另一个阶段以使其按预期运行,或者我应该以根本不同的方式使用执行注释?
我仍然不明白 Maven 在做什么,但我确实找到了解决它的方法:
- 我从 StartMockey class 中删除了 @Execute 注释。
- 我自己在 Java. 中创建了进程
StartMockey.execute():
中的新代码片段// Start Mockey with the specified parameters on a new thread.
MockeyRunner mockeyRunner = new MockeyRunner(args);
mockeyRunnerThread = new Thread(mockeyRunner);
mockeyRunnerThread.start();
新的 MockeyRunner class:
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" 是什么!
见https://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-plugins-lifecycle.html
文档似乎表明您应该创建一个仅包含 start-mockey 目标的自定义生命周期。然后你的@Execute 注释应该指定目标和生命周期。那应该分叉执行但只执行你的开始嘲笑。我想你可以像往常一样 运行 end-mockey。