学过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
二、代码实现
我们来用代码实现上面的场景二,下图是代码目录结构
2-1、技术实现
所谓的插件也无非是来执行我们的脚本,maven 插件也提供了一个入口 我们继承 AbstractMojo 重写 execute 方法,当运行这个插件的时候,就会去执行这个方法。
既然是根据数据库表来生成代码,那一定是少不了JDBC驱动了。
我们可以基于 字符串拼接 来生成我们想要的代码,但那太麻烦了,我们可以使用 freemarker 来生成我们想要的代码,更优雅,更具维护性。
2-2、插件入口
其实就是继承 AbstractMojo 重写execute方法
获取maven参数命令
mvn clean test -P test -DautoType=client
// 可以通过获取
System.getProperty("autoType");
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();
}
}
}
2-3、获取MySQL 表结构信息
获取某个库中的数据表
SELECT
table_name
FROM
information_schema.tables
WHERE table_schema = #{dataBase} AND table_type = 'base table'
获取表结构信息
SELECT
*
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = #{dataBase} and TABLE_NAME = #{tableName}
2-4、Java代码执行命令
我们既然需要自动提交、自动打包,那肯定是要使用脚本执行 git 和 maven 命令的。
在Java里面我们可以使用下面的代码来执行命令
Runtime.getRuntime().exec();
比如我们执行 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);
在mac下
pwd = IOUtil.toString(Runtime.getRuntime().exec("pwd").getInputStream())
.replaceAll("\n", "")
.replaceAll("\r", "");
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();
}
}
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");
}
}
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();
}
}
}
模板是根据 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};
</#list>
三、使用
3-1、提交maven插件
直接 mvn install 安装到本地即可
3-2、引入 pom 文件
<plugin>
<groupId>cn.ideamake</groupId>
<artifactId>business-generation-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<configuration>
<charset>UTF-8</charset>
<url>jdbc:mysql://127.0.0.1:3306/xdx_blog?serverTimezone=Asia/Shanghai</url>
<userName>root</userName>
<password>123456</password>
</configuration>
</plugin>
刷新后就可以看到我们的 插件了
3-3、引入模板文件
模板是从我们当前的 resources
文件夹下面读取的,所以我们需要把模板引入
3-4、执行命令
mvn business-generation:business-generation -DautoType=client -DtableName=xdx_test_one
这个命令有两个参数,一个是表示生成的方式(客户端),一个是要生成的表名。
错误
如果你执行命令后报错,可能是当前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
四、Maven 插件执行周期
上面只是对maven插件的一个简单的应用,但基于此你已经明白了 maven插件的执行原理。
所谓的插件,无非就是 在什么时间、什么地点、做什么事情。 (mybatis的插件也是如此)
五、源码获取
关注微信公众号:小道仙97, 回复关键字获取:maven-generation