• Maven插件初体验【附源码】


    B站视频地址


    学过Java的小伙伴对Maven一定很熟悉了,但对于Maven除了用来进行版本管理之外,你还用它做过什么呢?

    或许很多人和我一样,用了几年的Maven压根就没想过Maven除了版本管理还可以做其它事情。


    一、场景

    场景一

    比如你打包的时候需要修改某个包的名字,现在你需要把这个包版本从 1.0.0 改到1.0.1,如果只有几个服务你可以手动改,但如果有几十个服务就没办法手动改了,我们可以写一个 jib插件,在打包的时候自动去修改包名。


    场景二

    mabatis-generation 大家应该都用过,就是一个代码生成器,可以根据数据库里面的表结构生成固定代码,现在我们也有一个这样的需求,但是生成的代码结构有些不同,我们可以利用maven来实现这一功能。

    最终目的就是输入一行命令,然后自动生成代码,提交到 git仓库,deploy 到maven仓库

    mvn business-generation:business-generation -DautoType=client -DtableName=xdx_test_one
    
    • 1

    二、代码实现

    我们来用代码实现上面的场景二,下图是代码目录结构

    在这里插入图片描述


    2-1、技术实现

    所谓的插件也无非是来执行我们的脚本,maven 插件也提供了一个入口 我们继承 AbstractMojo 重写 execute 方法,当运行这个插件的时候,就会去执行这个方法。

    既然是根据数据库表来生成代码,那一定是少不了JDBC驱动了。

    我们可以基于 字符串拼接 来生成我们想要的代码,但那太麻烦了,我们可以使用 freemarker 来生成我们想要的代码,更优雅,更具维护性。


    2-2、插件入口

    其实就是继承 AbstractMojo 重写execute方法

    获取maven参数命令

    mvn clean test -P test -DautoType=client
     // 可以通过获取
    System.getProperty("autoType");
    
    • 1
    • 2
    • 3

    import cn.ideamake.excuter.ClientExcuter;
    import cn.ideamake.excuter.ServiceExcuter;
    import org.apache.maven.plugin.AbstractMojo;
    import org.apache.maven.plugins.annotations.Mojo;
    import org.apache.maven.plugins.annotations.Parameter;
    
    @Mojo(name = "business-generation")
    @SuppressWarnings("unused")
    public class WriteTextFilesMojo extends AbstractMojo {
    
        @Parameter(defaultValue = "UTF-8")
        @SuppressWarnings("unused")
        private String charset;
    
        // 读取 pom文件里面的配置
        @Parameter
        private String url;
    
        // 读取 pom文件里面的配置
        @Parameter
        private String userName;
    
        // 读取 pom文件里面的配置
        @Parameter
        private String password;
    
        @Override
        public void execute()  {
    
            // 获取 maven 命令后面的参数
            String autoType = System.getProperty("autoType");
            String tableName = System.getProperty("tableName");
    
            try {
                if ("client".equals(autoType)) {
                    new ClientExcuter().excute(tableName, url, userName, password);
                }else if ("service".equals(autoType)){
                    new ServiceExcuter().excute(tableName, url, userName, password);
                }else {
                    System.out.println("\r\n 暂不支持你想要的操作!");
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    2-3、获取MySQL 表结构信息

    获取某个库中的数据表

    SELECT
        table_name
    FROM
    information_schema.tables
    WHERE table_schema = #{dataBase} AND table_type = 'base table'
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述


    获取表结构信息

    SELECT
        *
    FROM information_schema.COLUMNS
    WHERE TABLE_SCHEMA = #{dataBase} and TABLE_NAME = #{tableName}
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    2-4、Java代码执行命令

    我们既然需要自动提交、自动打包,那肯定是要使用脚本执行 git 和 maven 命令的。

    在Java里面我们可以使用下面的代码来执行命令

    Runtime.getRuntime().exec();
    
    • 1

    比如我们执行 git add . 那肯定是要在当前目录下去执行的。


    在win下,我们可以通过获取当前目录

    String pwd = IOUtil.toString(Runtime.getRuntime().exec(new String[]{"PowerShell","pwd"}).getInputStream())
             .replaceAll("\n", "")
             .replaceAll("\r", "")
             .replaceAll(" ", "")
             .replaceAll("----", "")
             .replaceAll("Path", "");
     System.out.println(pwd);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在mac下

    pwd = IOUtil.toString(Runtime.getRuntime().exec("pwd").getInputStream())
            .replaceAll("\n", "")
            .replaceAll("\r", "");
    
    • 1
    • 2
    • 3

    exec 方法是可以传递多个命令的,它们会依次执行


    2-5、 ClientExcuter

    这其实就是我们要执行的脚本了,这里我通过jdbc获取数据库中表结构,然后基于 freemarker来生成文档,最后执行相关命令来提交代码。

    
    import org.codehaus.plexus.util.IOUtil;
    
    /**
     * 执行器的通用功能
     */
    public interface Excuter {
        
        default void gitPush() throws Exception {
    
            System.out.println("-------------------- git代码提交");
            String pwd = IOUtil.toString(Runtime.getRuntime().exec("pwd").getInputStream()).replaceAll("\n", "").replaceAll("\r", "");
            System.out.println("当前路径:"+ pwd);
            String commit = "cd "+ pwd +" && git add . && git commit -m '自动生成代码' && git push";
            System.out.println(commit);
            Process exec = Runtime.getRuntime().exec(new String[]{"bash", "-c", commit});
            int i = exec.waitFor();
            if (i == 0) {
                System.out.println("-------------------- git代码提交完毕");
            }else {
                System.out.println("-------------------- git代码提交异常");
                System.out.println(IOUtil.toString(exec.getErrorStream()));
            }
            exec.destroy();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    
    import cn.ideamake.util.JdbcUtils;
    import cn.ideamake.util.StringUtils;
    import cn.ideamake.util.TemplateUtils;
    import org.codehaus.plexus.util.IOUtil;
    
    import java.io.File;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 客户端的执行器
     */
    public class ClientExcuter implements Excuter{
    
    
        public void excute(String table_name, String url, String userName, String password) throws Exception {
            System.out.println("SDK客户端自动生成开始:" + table_name);
    
            System.out.println("-------------------- 开始生成DTO");
            String tableName = StringUtils.underlineToHump(table_name, true);
            PreparedStatement stmt = JdbcUtils.getStatement(url, userName, password, "SELECT * FROM information_schema.COLUMNS WHERE TABLE_NAME = 'yxxx'");
            Map<String, String> dataBaseParams = generationDto(stmt, table_name, tableName);
            System.out.println("DTO生成完毕 " + tableName  + "DTO.java");
            JdbcUtils.closeJdbc(stmt);
    
            String dataBase = dataBaseParams.get("dataBase");
            System.out.println("-------------------- 开始生成FeignClient");
            generationFeignClient(dataBase, tableName, table_name);
            System.out.println("FeignClient 生成完毕!");
    
            gitPush();
    
            Runtime.getRuntime().exec("mvn install");
            System.out.println("-------------------- maven install、完成");
        }
    
    
        public Map<String, String> generationDto(PreparedStatement stmt, String table_name, String tableName) throws SQLException {
            Map<String, String> result = new HashMap<>();
            stmt.setString(1, table_name);
            ResultSet resultSet = stmt.executeQuery();
            List<Map<String, Object>> list = new ArrayList<>(15);
            while (resultSet.next()) {
                Map<String, Object> map = new HashMap<>();
                map.put("column", StringUtils.underlineToHump(resultSet.getString("COLUMN_NAME"), false));
                map.put("dataType", JdbcUtils.typeChange(resultSet.getString("DATA_TYPE")));
                map.put("desc", resultSet.getString("COLUMN_COMMENT"));
                list.add(map);
    
                result.put("dataBase", resultSet.getString("TABLE_SCHEMA"));
            }
    
            // 生成模板
            Map<String, Object> root = new HashMap<>();
            root.put("className", tableName);
            root.put("packageName", "cn.ideamake.sdk.base.dto.query");
            root.put("date", "2022-01-10");
            root.put("columns", list);
            File docFile = new File("base-sdk/src/main/java/cn/ideamake/sdk/base/dto/query/" + tableName + "DTO.java");
    
            TemplateUtils.generation(root ,docFile,"client/dto.ftl");
    
            return result;
        }
    
    
        public void generationFeignClient(String dataBase,String tableName, String table_name) {
    
            Map<String, Object> root = new HashMap<>();
            root.put("className", tableName);
            root.put("class_name", table_name);
            root.put("packageName", "cn.ideamake.sdk.base.client");
            root.put("date", "2022-01-10");
            root.put("data_base", dataBase);
    
            File docFile = new File("base-sdk/src/main/java/cn/ideamake/sdk/base/client/" +tableName + "FeignClient.java");
            TemplateUtils.generation(root ,docFile,"client/feignClient.ftl");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    2-6、freeMarker

    这是一个模板引擎,可以用它来生成各色的代码,这个不是重点,而且也比较简单

    封装的模板工具

    import freemarker.template.Configuration;
    import freemarker.template.Template;
    
    import java.io.*;
    import java.util.Map;
    
    public class TemplateUtils {
    
    
        public static void generation(Map<String, Object> root, File targetFile,String templateSrc) {
    
            writeFile(root, targetFile ,templateSrc);
        }
    
        private static void writeFile(Map<String, Object> root, File targetFile,String templateSrc) {
            try {
                // 删除文件
                targetFile.delete();
                if (!targetFile.getParentFile().exists()) {
                    System.out.println("not exists");
                    //创建上级目录
                    targetFile.getParentFile().mkdirs();
                }
                Configuration configuration = new Configuration();
                configuration.setDirectoryForTemplateLoading(new File("src/main/resources"));
                Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile)));
                Template template = configuration.getTemplate("template/" + templateSrc);
                template.process(root, out);
    
                if (null != out) {
                    out.flush();
                }
            }catch (Exception e) {
                System.out.println("写文件异常!!!");
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    模板是根据 ftl 文件来生成的,我们来看一个简单的模板

    package ${packageName};
    
    import com.fasterxml.jackson.annotation.JsonFormat;
    import lombok.*;
    import java.util.Date;
    /**
    *  feign实体对象数据类
    *
    *  @author auto
    *  @date ${date}
    */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    @Builder
    public class ${className}DTO {
    
    <#list columns as attr>
        /**
         * ${attr.desc}
         */
        private ${attr.dataType} ${attr.column};
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    三、使用

    3-1、提交maven插件

    直接 mvn install 安装到本地即可


    3-2、引入 pom 文件

    <plugin>
        <groupId>cn.ideamakegroupId>
        <artifactId>business-generation-pluginartifactId>
        <version>1.0-SNAPSHOTversion>
        <configuration>
            <charset>UTF-8charset>
            <url>jdbc:mysql://127.0.0.1:3306/xdx_blog?serverTimezone=Asia/Shanghaiurl>
            <userName>rootuserName>
            <password>123456password>
        configuration>
    plugin>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    刷新后就可以看到我们的 插件了
    在这里插入图片描述


    3-3、引入模板文件

    模板是从我们当前的 resources 文件夹下面读取的,所以我们需要把模板引入


    3-4、执行命令

    mvn business-generation:business-generation -DautoType=client -DtableName=xdx_test_one
    
    • 1

    这个命令有两个参数,一个是表示生成的方式(客户端),一个是要生成的表名。


    错误

    如果你执行命令后报错,可能是当前maven仓库地址不对,加个参数强制指定即可
    在这里插入图片描述

    mvn business-generation:business-generation -DautoType=clien2 -Dmaven.repo.local=D:\software\install\maven-3.8.1\apache-maven-3.8.1-my\maven-repository
    
    • 1

    四、Maven 插件执行周期

    上面只是对maven插件的一个简单的应用,但基于此你已经明白了 maven插件的执行原理。

    所谓的插件,无非就是 在什么时间、什么地点、做什么事情。 (mybatis的插件也是如此)

    完整的生命周期参看这里


    五、源码获取

    关注微信公众号:小道仙97, 回复关键字获取:maven-generation

  • 相关阅读:
    Linux中source命令的用法
    PAM从入门到精通(三)
    UDS服务基础篇之14
    【机器学习-18】特征筛选:提升模型性能的关键步骤
    Leetcode -2
    双非渣本,奋斗3年,阿里四面终拿Offer,定级p6
    iOS视频捕获实践篇
    pycocotools安装
    r、 weka从决策树模型看员工为什么离职?
    Android Studio Iguana | 2023.2.1版本
  • 原文地址:https://blog.csdn.net/Tomwildboar/article/details/127348446