Maven & JSR-330

为什么选择 JSR-330?

Maven 通过Plexus使用依赖注入 (DI) 的历史由来已久,因此使用 JSR-330的目的是用某种标准替换自定义 DI 机制。Maven 使用的实现——从 3.0-beta-3 开始——被命名为Sisu,它基于直接支持 JSR-330的Guice 3.x。

如果您当前正在使用Plexus 注释和 API,则无需急于切换,也无需大刀阔斧的转换:Plexus、JSR-330 和 Guice API 都在 Maven 的核心中愉快地共存,您可以选择在以下情况下使用 JSR-330你希望。有数百个组件是使用 Plexus API 编写的。

如果您想使用 JSR-330,您必须了解您的代码不会与 Maven 3.0.x 兼容,而只能与 Maven 3.1.0 及更高版本兼容。尽管 JSR-330 自 Maven 3.0-beta-3 以来已在核心中可用,但它仅在 Maven 3.1.0 中可用于插件和扩展(有关详细信息,请参阅MNG-5343 )。

如果您对从 Plexus 迁移到 Guice 和 JSR-330 的背景感兴趣,可以参考以下文章:

如果您有兴趣从 Plexus 迁移到 Sisu,Sisu 有一个为您完成的Plexus 迁移文档。

如何使用 JSR-330

当您在 Maven 插件或扩展中使用 JSR-330 时,您需要在构建中设置两件事:

  1. 首先你需要一个依赖,javax.inject这样你就可以在你的插件和扩展中使用@Inject, @Named, 和注解。@Singleton

  2. 其次,您需要设置sisu-maven-plugin索引以供 Maven 使用的 JSR-330 组件。在sisu-maven-plugin中创建其索引META-INF/sisu/javax.inject.Named

如果您使用下一段中的示例查看该文件,您将看到如下内容:

org.apache.maven.lifecycle.profiler.LifecycleProfiler
org.apache.maven.lifecycle.profiler.internal.DefaultSessionProfileRenderer
org.apache.maven.lifecycle.profiler.internal.DefaultTimer

枚举实现意味着在运行时不需要类路径扫描来找到它们,这使 Maven 的启动时间保持很快。请注意,我们的容器默认配置为仅使用索引。虽然这可以保持速度,但如果您在不包含索引的依赖项中使用 JSR-330 组件,则当前不会发现这些实现。这是一个合理的折衷方案,因为 Maven 是一个命令行工具,其中启动速度很重要。

如何在扩展中使用 JSR-330

让我们看一个示例扩展。我们将看一下 POM 和一些实现,以便您了解 JSR-330 扩展是如何工作的。实际上,它只是一个简单的 JSR-330 组件。如果您想查看完整的实现,可以在 Github 上找到

好的,让我们看一下POM:

<?xml version="1.0"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.apache.maven</groupId>
  <artifactId>maven-profiler</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Maven :: Profiler</name>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-core</artifactId>
      <version>3.0.3</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.2</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.eclipse.sisu</groupId>
        <artifactId>sisu-maven-plugin</artifactId>
        <version>0.3.3</version>
        <executions>
          <execution>
            <id>generate-index</id>
            <goals>
              <goal>main-index</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

因此,如前所述,我们有javax.inject依赖项和sisu-maven-plugin配置来创建 JSR-330 组件索引。当您构建扩展 JAR 并将其放置在${MAVEN_HOME}/lib/ext文件夹中时,它将自动被 Maven 拾取。在这个例子中,我们有一个实现,EventSpy它对生命周期中一个阶段内单个 mojo 的执行次数进行计时。

package org.apache.maven.lifecycle.profiler;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.maven.eventspy.AbstractEventSpy;
import org.apache.maven.execution.ExecutionEvent;

@Named
@Singleton
public class LifecycleProfiler extends AbstractEventSpy {

  //
  // Components
  //
  private SessionProfileRenderer sessionProfileRenderer;
  
  //
  // Profile data
  //
  private SessionProfile sessionProfile;
  private ProjectProfile projectProfile;
  private PhaseProfile phaseProfile;
  private MojoProfile mojoProfile;  
  
  @Inject
  public LifecycleProfiler(SessionProfileRenderer sessionProfileRenderer) {
    this.sessionProfileRenderer = sessionProfileRenderer;
  }
  
  @Override
  public void init(Context context) throws Exception {
  }

  @Override
  public void onEvent(Object event) throws Exception {
    if (event instanceof ExecutionEvent) {
      
      
      ExecutionEvent executionEvent = (ExecutionEvent) event;
      if (executionEvent.getType() == ExecutionEvent.Type.SessionStarted) {
        //
        //
        //
        sessionProfile = new SessionProfile();
      } else if (executionEvent.getType() == ExecutionEvent.Type.SessionEnded) {
        //
        //
        //
        sessionProfile.stop();
        
        sessionProfileRenderer.render(sessionProfile);
        
      } else if (executionEvent.getType() == ExecutionEvent.Type.ProjectStarted) {
        //
        // We need to collect the mojoExecutions within each project
        //
        projectProfile = new ProjectProfile(executionEvent.getProject());
      } else if (executionEvent.getType() == ExecutionEvent.Type.ProjectSucceeded || executionEvent.getType() == ExecutionEvent.Type.ProjectFailed) {
        //
        //
        //
        projectProfile.stop();
        sessionProfile.addProjectProfile(projectProfile);
      } else if (executionEvent.getType() == ExecutionEvent.Type.MojoStarted) {
        String phase = executionEvent.getMojoExecution().getLifecyclePhase();
        //
        // Create a new phase profile if one doesn't exist or the phase has changed.
        //
        if(phaseProfile == null) {
          phaseProfile = new PhaseProfile(phase);
        } else if (!phaseProfile.getPhase().equals(phase)) {
          phaseProfile.stop();
          projectProfile.addPhaseProfile(phaseProfile);
          phaseProfile = new PhaseProfile(phase);          
        }
        mojoProfile = new MojoProfile(executionEvent.getMojoExecution());
      } else if (executionEvent.getType() == ExecutionEvent.Type.MojoSucceeded || executionEvent.getType() == ExecutionEvent.Type.MojoFailed) {
        //
        //
        //
        mojoProfile.stop();
        phaseProfile.addMojoProfile(mojoProfile);        
      }
    }
  }
}

如何在插件中使用 JSR-330

让我们看一个示例插件。POM 的设置方式与扩展类似,但我们在示例中添加了一个依赖项来扩展maven-plugin-api和使用 Java 5 插件注释。maven-plugin-annotationsAbstractMojo

<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-jsr330-plugin</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>maven-plugin</packaging>

  <name>maven-jsr330-plugin Maven Plugin</name>
  <url>https://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <mavenVersion>3.0.4</mavenVersion>
    <mavenPluginPluginVersion>3.2</mavenPluginPluginVersion>
  </properties>

  <dependencies>
    <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>${mavenVersion}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven.plugin-tools</groupId>
      <artifactId>maven-plugin-annotations</artifactId>
      <version>${mavenPluginPluginVersion}</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-plugin-plugin</artifactId>
        <version>${mavenPluginPluginVersion}</version>
        <configuration>
          <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
        </configuration>
        <executions>
          <execution>
            <id>mojo-descriptor</id>
            <goals>
              <goal>descriptor</goal>
            </goals>
          </execution>
          <execution>
            <id>help-goal</id>
            <goals>
              <goal>helpmojo</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.eclipse.sisu</groupId>
        <artifactId>sisu-maven-plugin</artifactId>
        <version>0.3.3</version>
        <executions>
          <execution>
            <id>generate-index</id>
            <goals>
              <goal>main-index</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

现在让我们看一下插件代码。您会注意到我们正在使用构造函数注入,这使测试变得更加容易。如果你想测试你的Jsr330Component,你不需要容器来实例化Mojo. 在这个简单的例子中,您实际上可以在不使用插件测试工具的情况下测试此插件,因为您可以直接实例化Jsr330ComponentandJsr330Mojo并使用构造函数手动连接所有内容。Plexus 所缺乏的构造函数注入极大地简化了测试。

package org.apache.maven.plugins;

import javax.inject.Inject;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;

@Mojo( name = "hello", defaultPhase = LifecyclePhase.VALIDATE, requiresProject = false )
public class Jsr330Mojo
    extends AbstractMojo
{

    private Jsr330Component component;

    @Inject
    public Jsr330Mojo( Jsr330Component component )
    {
        this.component = component;    
    }

    public void execute()
        throws MojoExecutionException
    {    
        //
        // Say hello to the world, my little constructor injected component!
        //
        component.hello();
    }
}

如果您想查看这个示例项目,您可以在 Maven Core ITs中找到代码。