Maven插件开发由浅入深教程

内容分享1个月前发布
0 0 0

第一部分:Maven插件基础概念

1.1 什么是Maven插件

Maven插件是Maven的核心扩展机制,用于执行特定的构建任务。每个插件包含一个或多个目标(goal),可以绑定到Maven的生命周期阶段。

1.2 插件组成结构

  • Mojo: Maven Plain Old Java Object,插件的基本执行单元
  • Annotations: 用于配置Mojo的注解
  • Descriptor: 插件描述文件

第二部分:简单插件开发

2.1 创建插件项目

Maven插件开发由浅入深教程

<!-- pom.xml -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>hello-maven-plugin</artifactId>
    <version>1.0.0</version>
    <packaging>maven-plugin</packaging>

    <dependencies>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.6.0</version>
            </plugin>
        </plugins>
    </build>
</project>

2.2 编写第一个Mojo

package com.example;

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

/**
 * 简单的Hello World插件
 */
@Mojo(name = "sayhello")
public class HelloMojo extends AbstractMojo {
    
    /**
     * 消息参数,用户可配置
     */
    @Parameter(property = "message", defaultValue = "Hello World!")
    private String message;
    
    /**
     * 重复次数
     */
    @Parameter(property = "repeat", defaultValue = "1")
    private int repeat;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        for (int i = 0; i < repeat; i++) {
            getLog().info(message);
        }
    }
}

2.3 安装和测试插件

# 安装插件到本地仓库
mvn clean install

# 在另一个项目中使用插件
mvn com.example:hello-maven-plugin:1.0.0:sayhello

# 带参数执行
mvn com.example:hello-maven-plugin:1.0.0:sayhello -Dmessage="Custom Message" -Drepeat=3

Maven插件开发由浅入深教程

第三部分:中级插件功能

3.1 访问Maven项目信息

package com.example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

/**
 * 访问项目信息的插件
 */
@Mojo(name = "projectinfo")
public class ProjectInfoMojo extends AbstractMojo {
    
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;
    
    @Parameter(property = "showDependencies", defaultValue = "false")
    private boolean showDependencies;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("Project: " + project.getName());
        getLog().info("GroupId: " + project.getGroupId());
        getLog().info("ArtifactId: " + project.getArtifactId());
        getLog().info("Version: " + project.getVersion());
        getLog().info("Packaging: " + project.getPackaging());
        
        if (showDependencies) {
            getLog().info("Dependencies:");
            project.getDependencies().forEach(dep -> 
                getLog().info("  " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getVersion())
            );
        }
    }
}

3.2 文件操作插件

package com.example;

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

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 文件操作插件
 */
@Mojo(name = "fileops")
public class FileOperationsMojo extends AbstractMojo {
    
    @Parameter(property = "sourceDir", defaultValue = "${project.basedir}/src")
    private String sourceDir;
    
    @Parameter(property = "targetDir", defaultValue = "${project.build.directory}/backup")
    private String targetDir;
    
    @Parameter(property = "backupEnabled", defaultValue = "false")
    private boolean backupEnabled;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            Path sourcePath = Paths.get(sourceDir);
            Path targetPath = Paths.get(targetDir);
            
            if (!Files.exists(sourcePath)) {
                getLog().warn("Source directory does not exist: " + sourceDir);
                return;
            }
            
            if (backupEnabled) {
                getLog().info("Creating backup from " + sourceDir + " to " + targetDir);
                copyDirectory(sourcePath, targetPath);
            } else {
                getLog().info("Backup is disabled. Set backupEnabled=true to enable.");
            }
            
            // 统计文件信息
            long fileCount = Files.walk(sourcePath)
                    .filter(Files::isRegularFile)
                    .count();
            getLog().info("Total files in source: " + fileCount);
            
        } catch (IOException e) {
            throw new MojoExecutionException("File operation failed", e);
        }
    }
    
    private void copyDirectory(Path source, Path target) throws IOException {
        Files.walk(source).forEach(sourcePath -> {
            Path targetPath = target.resolve(source.relativize(sourcePath));
            try {
                Files.copy(sourcePath, targetPath);
            } catch (IOException e) {
                getLog().error("Failed to copy: " + sourcePath, e);
            }
        });
    }
}

3.3 配置复杂参数

package com.example;

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

import java.util.List;
import java.util.Map;

/**
 * 复杂参数配置示例
 */
@Mojo(name = "complexconfig")
public class ComplexConfigMojo extends AbstractMojo {
    
    // 列表参数
    @Parameter
    private List<String> fileExtensions;
    
    // Map参数
    @Parameter
    private Map<String, String> propertiesMapping;
    
    // 嵌套对象参数
    @Parameter
    private ReportConfig reportConfig;
    
    public static class ReportConfig {
        private String format;
        private boolean includeTimestamp;
        private List<String> sections;
        
        // getters and setters
        public String getFormat() { return format; }
        public void setFormat(String format) { this.format = format; }
        
        public boolean isIncludeTimestamp() { return includeTimestamp; }
        public void setIncludeTimestamp(boolean includeTimestamp) { 
            this.includeTimestamp = includeTimestamp; 
        }
        
        public List<String> getSections() { return sections; }
        public void setSections(List<String> sections) { this.sections = sections; }
    }
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        getLog().info("File Extensions: " + fileExtensions);
        getLog().info("Properties Mapping: " + propertiesMapping);
        
        if (reportConfig != null) {
            getLog().info("Report Format: " + reportConfig.getFormat());
            getLog().info("Include Timestamp: " + reportConfig.isIncludeTimestamp());
            getLog().info("Sections: " + reportConfig.getSections());
        }
    }
}

对应的pom配置:

<plugin>
    <groupId>com.example</groupId>
    <artifactId>hello-maven-plugin</artifactId>
    <version>1.0.0</version>
    <configuration>
        <fileExtensions>
            <fileExtension>.java</fileExtension>
            <fileExtension>.xml</fileExtension>
        </fileExtensions>
        <propertiesMapping>
            <key1>value1</key1>
            <key2>value2</key2>
        </propertiesMapping>
        <reportConfig>
            <format>HTML</format>
            <includeTimestamp>true</includeTimestamp>
            <sections>
                <section>summary</section>
                <section>details</section>
            </sections>
        </reportConfig>
    </configuration>
</plugin>

第四部分:高级插件开发

4.1 生命周期绑定

package com.example;

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

/**
 * 绑定到生命周期的插件
 */
@Mojo(
    name = "validate-project",
    defaultPhase = LifecyclePhase.VALIDATE,
    requiresDependencyResolution = ResolutionScope.COMPILE,
    threadSafe = true
)
public class ValidateProjectMojo extends AbstractMojo {
    
    @Parameter(defaultValue = "${project}", readonly = true)
    private org.apache.maven.project.MavenProject project;
    
    @Parameter(property = "minJavaVersion", defaultValue = "11")
    private int minJavaVersion;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        // 验证Java版本
        String javaVersion = System.getProperty("java.version");
        int majorVersion = getMajorVersion(javaVersion);
        
        if (majorVersion < minJavaVersion) {
            throw new MojoFailureException(
                "Java version " + javaVersion + " is below minimum required version " + minJavaVersion
            );
        }
        
        // 验证项目属性
        if (project.getVersion().endsWith("-SNAPSHOT")) {
            getLog().warn("Project is using SNAPSHOT version");
        }
        
        getLog().info("Project validation completed successfully");
    }
    
    private int getMajorVersion(String version) {
        if (version.startsWith("1.")) {
            return Integer.parseInt(version.substring(2, 3));
        } else {
            int dotIndex = version.indexOf(".");
            if (dotIndex > -1) {
                return Integer.parseInt(version.substring(0, dotIndex));
            }
            return Integer.parseInt(version);
        }
    }
}

4.2 依赖注入和组件使用

package com.example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.project.MavenProject;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.codehaus.plexus.components.interactivity.Prompter;
import org.codehaus.plexus.components.interactivity.PrompterException;

import java.util.List;

/**
 * 使用Maven组件的插件
 */
@Mojo(name = "interactive")
public class InteractiveMojo extends AbstractMojo {
    
    @Parameter(defaultValue = "${project}", readonly = true)
    private MavenProject project;
    
    @Component
    private ArtifactFactory artifactFactory;
    
    @Component
    private ArtifactResolver artifactResolver;
    
    @Component
    private Prompter prompter;
    
    @Parameter(property = "interactive", defaultValue = "true")
    private boolean interactive;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            if (interactive) {
                String action = prompter.prompt("Choose action (validate/analyze/report): ");
                
                switch (action.toLowerCase()) {
                    case "validate":
                        performValidation();
                        break;
                    case "analyze":
                        performAnalysis();
                        break;
                    case "report":
                        generateReport();
                        break;
                    default:
                        getLog().warn("Unknown action: " + action);
                }
            } else {
                // 非交互模式执行所有操作
                performValidation();
                performAnalysis();
                generateReport();
            }
        } catch (PrompterException e) {
            throw new MojoExecutionException("Interaction failed", e);
        }
    }
    
    private void performValidation() {
        getLog().info("Performing project validation...");
        // 验证逻辑
    }
    
    private void performAnalysis() {
        getLog().info("Analyzing dependencies...");
        // 分析逻辑
    }
    
    private void generateReport() {
        getLog().info("Generating report...");
        // 报告生成逻辑
    }
}

4.3 多线程插件

package com.example;

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

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 多线程处理插件
 */
@Mojo(name = "parallel", threadSafe = true)
public class ParallelProcessingMojo extends AbstractMojo {
    
    @Parameter(property = "threadCount", defaultValue = "4")
    private int threadCount;
    
    @Parameter(property = "tasks")
    private List<String> tasks;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (tasks == null || tasks.isEmpty()) {
            getLog().info("No tasks specified");
            return;
        }
        
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        try {
            List<CompletableFuture<Void>> futures = tasks.stream()
                .map(task -> CompletableFuture.runAsync(() -> processTask(task), executor))
                .collect(Collectors.toList());
            
            // 等待所有任务完成
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
            
            getLog().info("All tasks completed successfully");
            
        } finally {
            executor.shutdown();
        }
    }
    
    private void processTask(String task) {
        getLog().info("Processing task: " + task + " on thread: " + Thread.currentThread().getName());
        try {
            // 模拟任务处理
            Thread.sleep(1000);
            getLog().info("Completed task: " + task);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            getLog().warn("Task interrupted: " + task);
        }
    }
}

第五部分:测试和调试

5.1 单元测试

package com.example;

import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.junit.Test;

import java.io.File;

public class HelloMojoTest extends AbstractMojoTestCase {
    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
    }
    
    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
    }
    
    @Test
    public void testMojoExecution() throws Exception {
        File pom = getTestFile("src/test/resources/test-pom.xml");
        assertNotNull(pom);
        assertTrue(pom.exists());
        
        HelloMojo mojo = (HelloMojo) lookupMojo("sayhello", pom);
        assertNotNull(mojo);
        
        // 设置参数
        setVariableValueToObject(mojo, "message", "Test Message");
        setVariableValueToObject(mojo, "repeat", 2);
        
        mojo.execute();
    }
}

5.2 集成测试

<!-- 在pom.xml中添加集成测试支持 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-invoker-plugin</artifactId>
    <version>3.2.2</version>
    <configuration>
        <projectsDirectory>src/it</projectsDirectory>
        <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
    </configuration>
    <executions>
        <execution>
            <id>integration-test</id>
            <goals>
                <goal>install</goal>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

第六部分:最佳实践

6.1 错误处理

public class RobustMojo extends AbstractMojo {
    
    @Parameter(property = "failOnError", defaultValue = "true")
    private boolean failOnError;
    
    @Parameter(property = "maxRetries", defaultValue = "3")
    private int maxRetries;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        int retryCount = 0;
        
        while (retryCount <= maxRetries) {
            try {
                performOperation();
                return; // 成功则退出
            } catch (Exception e) {
                retryCount++;
                if (retryCount > maxRetries) {
                    if (failOnError) {
                        throw new MojoExecutionException("Operation failed after " + maxRetries + " retries", e);
                    } else {
                        getLog().error("Operation failed but continuing due to configuration", e);
                        return;
                    }
                } else {
                    getLog().warn("Operation failed, retrying (" + retryCount + "/" + maxRetries + ")");
                    try {
                        Thread.sleep(1000 * retryCount); // 指数退避
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new MojoExecutionException("Operation interrupted", ie);
                    }
                }
            }
        }
    }
    
    private void performOperation() throws Exception {
        // 具体的操作逻辑
    }
}

6.2 日志记录

public class LoggingMojo extends AbstractMojo {
    
    @Parameter(property = "logLevel", defaultValue = "INFO")
    private String logLevel;
    
    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        // 根据配置调整日志级别
        switch (logLevel.toUpperCase()) {
            case "DEBUG":
                getLog().debug("Debug level logging enabled");
                break;
            case "INFO":
                getLog().info("Info level logging enabled");
                break;
            case "WARN":
                getLog().warn("Warn level logging enabled");
                break;
            case "ERROR":
                getLog().error("Error level logging enabled");
                break;
        }
        
        // 结构化日志记录
        logStructuredInfo();
    }
    
    private void logStructuredInfo() {
        getLog().info("=== Plugin Execution Summary ===");
        getLog().info("Timestamp: " + java.time.Instant.now());
        getLog().info("Java Version: " + System.getProperty("java.version"));
        getLog().info("OS: " + System.getProperty("os.name"));
        getLog().info("=================================");
    }
}

总结

这个教程从基础的Maven插件概念开始,逐步深入到高级功能开发:

  1. 基础: 创建简单插件、参数配置
  2. 中级: 访问项目信息、文件操作、复杂配置
  3. 高级: 生命周期绑定、依赖注入、多线程处理
  4. 工程化: 测试、调试、最佳实践

通过这个由浅入深的学习路径,你可以掌握Maven插件开发的完整技能栈,创建出功能强劲、稳定可靠的Maven插件。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...