写点什么

从 mybatis-plus-generator 看如何编写代码生成器

作者:Rubble
  • 2022 年 4 月 22 日
  • 本文字数:9030 字

    阅读完需:约 30 分钟

项目中常常用到代码生成器生成代码,下面介绍 velocity 代码生成原理,及如何编写代码生成器。

Velocity 介绍

​ Velocity 是一个基于 Java 的模板引擎,基于 MVC 模型实现,其提供了一个 Context 容器(相当于 Spring 的 Model),在 java 代码里面我们可以往容器中存值,然后在 vm 文件中使用特定的语法获取(相当于 Spring 页面中取值如 freemarker、thymeleaf)。


官网:http://velocity.apache.org/


maven 引入


<!-- https://mvnrepository.com/artifact/org.apache.velocity/velocity --><dependency>    <groupId>org.apache.velocity</groupId>    <artifactId>velocity</artifactId>    <version>1.7</version></dependency>
复制代码

velocity 基本语法

变量

设置变量 #set($foo =“hello”) 取值 $foo


访问对象属性 $user.name ${user.name}


使用!{}的形式可以将不存在的变量变成空白输出. 见示例 ${notExist} $!{notExistEmpty}


velocity 中大小写敏感

循环

#foreach($i in $list)    $i#end
复制代码


velocity 只会替换变量,所以 velocity 的语句一般顶行写,以保持文件格式


如上 $i 前的空格将会原样输出

条件

#if(condition)...dosonmething...#elseif(condition)...dosomething...#else...dosomething...#end
复制代码

hello world generator

  1. 初始化了VelocityEngine这个模板引擎,对其设置参数进行初始化,指定使用 ClasspathResourceLoader 来加载 vm 文件。

  2. VelocityContext这个 Velocity 容器中存放对象了。

  3. .vm文件中我们可以取出这些变量,

  4. Template模板输出 template.merge(ctx,sw)


public class HelloWorldVelocity {
public static void main(String[] args) { // 初始化模板引擎 VelocityEngine velocityEngine = new VelocityEngine(); velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); velocityEngine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); velocityEngine.init(); // 获取模板文件 Template template = velocityEngine.getTemplate("helloVelocity.vm"); // 设置变量 VelocityContext ctx = new VelocityContext(); ctx.put("name", "Velocity"); User user = new User(); user.setName("zhang san"); user.setPhone("18612345678"); ctx.put("user", user); List list = new ArrayList(); list.add("1"); list.add("2"); ctx.put("list", list); // 输出 StringWriter sw = new StringWriter(); template.merge(ctx,sw); System.out.println(sw.toString()); }
复制代码


resouces 目录下的模板文件helloVelocity.vm


#set($foo = 'hello')$foo $name${notExist}$!{notExistEmpty}$user.name${user.name}#foreach($i in $list)    $i#end
复制代码


Gitee: https://gitee.com/tg_seahorse/paw-demos/tree/master/paw-generator

mybatis-plus-generator

Mybatsi-plus官网文档


官网源码Git

generator 使用

<dependency>    <groupId>com.baomidou</groupId>    <artifactId>mybatis-plus-generator</artifactId>    <version>3.4.1</version></dependency>
复制代码


MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl 模板引擎


<dependency>    <groupId>org.apache.velocity</groupId>    <artifactId>velocity-engine-core</artifactId>    <version>2.3</version></dependency>
复制代码


参照官网修改的生成代码类


  1. 配置信息写在了 main 方法的开头,项目路径、包名、要生成的表、表前缀

  2. 配置数据源 mysql 引入依赖mysql-connector-java

  3. cfg.setFileCreate 文件生成策略,return true 会生成文件,覆盖原有文件。

  4. 开启 swaggergc.setSwagger2(true);代码使用时需引入 swagger 依赖


   <dependency>     <groupId>com.github.xiaoymin</groupId>     <artifactId>knife4j-spring-boot-starter</artifactId>   </dependency>
复制代码


代码生成类


public class CodeGenerator {
public static void main(String[] args) {
String projectPath = "/Users/rubble/workSpace/paw/paw-demos/paw-generator"; String author = "Rubble"; String packageParent = "com.paw.generator"; String module = "system"; // 多个用,分隔 String tables = "sys_user"; String tablePrefix = "sys_";
// 代码生成器 AutoGenerator mpg = new AutoGenerator();
// 全局配置 GlobalConfig gc = new GlobalConfig(); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor(author); gc.setOpen(false); // gc.setSwagger2(true); 实体属性 Swagger2 注解 mpg.setGlobalConfig(gc);
// 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/ry?useUnicode=true&useSSL=false&characterEncoding=utf8"); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); mpg.setDataSource(dsc);
// 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(module); pc.setParent(packageParent); mpg.setPackageInfo(pc);
// 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } };
// 如果模板引擎是 freemarker// String templatePath = "/templates/mapper.xml.ftl"; // 如果模板引擎是 velocity String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /* 允许生成模板文件 */ cfg.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // 判断自定义文件夹是否需要创建 checkDir(projectPath); if (fileType == FileType.MAPPER) { // 已经生成 mapper 文件判断存在,不想重新生成返回 false return !new File(filePath).exists(); } // 允许生成模板文件 return true; } });
cfg.setFileOutConfigList(focList); mpg.setCfg(cfg);
// 配置模板 TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController();
templateConfig.setXml(null); mpg.setTemplate(templateConfig);
// 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel);// strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); // 公共父类// strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!"); // 写于父类中的公共字段 strategy.setSuperEntityColumns("id"); strategy.setInclude(tables.split(",")); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(tablePrefix); mpg.setStrategy(strategy); mpg.setTemplateEngine(new VelocityTemplateEngine()); mpg.execute(); }
}
复制代码


CodeGenerator可以用于日常基于 mybatis-plus 项目的开发中。

自定义模板

  1. 扩展 control 模板

  2. Mybatis-plus 模板默认位置 resources/templates 下,可在配置中进行修改 templatePath

  3. 从 git 项目或 jar 包中复制controller.java.vm 增加 CRUD 的方法,只写了简单的 add、list 方法,可自行就行扩展,如增加分页查找。


   package ${package.Controller};      import java.util.List;   import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;   import com.paw.generator.system.entity.User;   import com.paw.generator.system.service.IUserService;   import org.springframework.beans.factory.annotation.Autowired;   import org.springframework.web.bind.annotation.GetMapping;   import org.springframework.web.bind.annotation.RequestMapping;      #if(${restControllerStyle})   import org.springframework.web.bind.annotation.RestController;   #else   import org.springframework.stereotype.Controller;   #end   #if(${superControllerClassPackage})   import ${superControllerClassPackage};   #end      /**    * <p>    * $!{table.comment} 前端控制器    * </p>    *    * @author ${author}    * @since ${date}    */   #if(${restControllerStyle})   @RestController   #else   @Controller   #end   @RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")   #if(${kotlin})   class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end      #else   #if(${superControllerClass})   public class ${table.controllerName} extends ${superControllerClass} {   #else   public class ${table.controllerName} {   #end          @Autowired       private ${table.serviceName} service;             @GetMapping("add")       public Object add(${entity} entity){           boolean saved = service.save(entity);           return entity;       }          @GetMapping("list")       public List<${entity}> list(${entity} entity){           return service.list(new QueryWrapper<>(entity));       }   }      #end
复制代码


生成代码


   /**    * <p>    * 用户信息表 前端控制器    * </p>    *    * @author Rubble    * @since 2021-07-07    */   @RestController   @RequestMapping("/system/user")   public class UserController {          @Autowired       private IUserService service;             @GetMapping("add")       public Object add(User entity){           boolean saved = service.save(entity);           return entity;       }          @GetMapping("list")       public List<User> list(User entity){           return service.list(new QueryWrapper<>(entity));       }   }
复制代码


  1. 自定义模板 hello

  2. 配置中增加模板hello.java.vm,定义文件输出位置


   String helloTemplatePath = "/templates/hello.java.vm";   focList.add(new FileOutConfig(helloTemplatePath) {     @Override     public String outputFile (TableInfo tableInfo) {       return projectPath + "/src/main/java/" + packageParent.replace(".", File.separator) + File.separator + pc.getModuleName() + File.separator + "entity"           + File.separator + "Hello" + tableInfo.getEntityName() + StringPool.DOT_JAVA;     }   });
复制代码


最简单的模板


   package ${package.Entity};      public class Hello${entity}{      }
复制代码


执行输出


   package com.paw.generator.system.entity;      public class HelloUser{      }
复制代码

mybatis-plus-generator 解析

git 下载项目,用 jdk8, gradle 6.3 编译通过。


自动配置类AutoGenerator, 除 datasource 外其他均可默认设置。


/** * 配置信息 */protected ConfigBuilder config;/** * 注入配置 */protected InjectionConfig injection;/** * 数据源配置 */private DataSourceConfig dataSource;/** * 数据库表配置 */private StrategyConfig strategy;/** * 包 相关配置 */private PackageConfig packageInfo;/** * 模板 相关配置 */private TemplateConfig template;/** * 全局 相关配置 */private GlobalConfig globalConfig;
复制代码


模板引擎



AbstractTemplateEngine 实现了文件的输出 controller、service、 entity、mapper。


VelocityTemplateEngine 模板引擎


init()初始化VelocityEngine指定文件位置、编码等;


writer 引擎模板的渲染 template.merge(new VelocityContext(objectMap), writer);


Map<String, Object> objectMap = this.getObjectMap(config, tableInfo);


@Overridepublic @NotNull VelocityTemplateEngine init(@NotNull ConfigBuilder configBuilder) {    if (null == velocityEngine) {        Properties p = new Properties();        p.setProperty(ConstVal.VM_LOAD_PATH_KEY, ConstVal.VM_LOAD_PATH_VALUE);        p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, StringPool.EMPTY);        p.setProperty(Velocity.ENCODING_DEFAULT, ConstVal.UTF8);        p.setProperty(Velocity.INPUT_ENCODING, ConstVal.UTF8);        p.setProperty("file.resource.loader.unicode", StringPool.TRUE);        velocityEngine = new VelocityEngine(p);    }    return this;}

@Overridepublic void writer(@NotNull Map<String, Object> objectMap, @NotNull String templatePath, @NotNull File outputFile) throws Exception { Template template = velocityEngine.getTemplate(templatePath, ConstVal.UTF8); try (FileOutputStream fos = new FileOutputStream(outputFile); OutputStreamWriter ow = new OutputStreamWriter(fos, ConstVal.UTF8); BufferedWriter writer = new BufferedWriter(ow)) { template.merge(new VelocityContext(objectMap), writer); }}
复制代码


// objectMap 在 AbstractTemplateEngine 类中 从配置文件中生成上下文变量加入到 context 中


objectMap.put 的即在模板中可用的属性。


主要属性 PackageInfo, TableInfo


@NotNullpublic Map<String, Object> getObjectMap(@NotNull ConfigBuilder config, @NotNull TableInfo tableInfo) {    GlobalConfig globalConfig = config.getGlobalConfig();    Map<String, Object> controllerData = config.getStrategyConfig().controller().renderData(tableInfo);    Map<String, Object> objectMap = new HashMap<>(controllerData);    Map<String, Object> mapperData = config.getStrategyConfig().mapper().renderData(tableInfo);    objectMap.putAll(mapperData);    Map<String, Object> serviceData = config.getStrategyConfig().service().renderData(tableInfo);    objectMap.putAll(serviceData);    Map<String, Object> entityData = config.getStrategyConfig().entity().renderData(tableInfo);    objectMap.putAll(entityData);    objectMap.put("config", config);    objectMap.put("package", config.getPackageConfig().getPackageInfo());    objectMap.put("author", globalConfig.getAuthor());    objectMap.put("kotlin", globalConfig.isKotlin());    objectMap.put("swagger", globalConfig.isSwagger());    objectMap.put("date", globalConfig.getCommentDate());    // 存在 schemaName 设置拼接 . 组合表名    String schemaName = config.getDataSourceConfig().getSchemaName();    if (StringUtils.isNotBlank(schemaName)) {        schemaName += ".";        tableInfo.setConvert(true);    } else {        schemaName = "";    }    objectMap.put("schemaName", schemaName);    objectMap.put("table", tableInfo);    objectMap.put("entity", tableInfo.getEntityName());    return objectMap;}
复制代码


下载源码的方式扩展可以任意的 put 你想要的属性。


引用 jar 的方式可以增加全局自定义配置, 模板中使用 ${cfg.abc}


// 自定义配置InjectionConfig cfg = new InjectionConfig() {  @Override  public void initMap () {    // to do nothing    Map<String,Object> map = new HashMap<>();    map.put("abc","123");    setMap(map);  }};
复制代码


Mybatis-plus-generator 在通用性、扩展性做了很多工作,如父类、驼峰、swagger、各种数据库的适配等等,是一个很优秀的工具,向作者致敬。

若依(ruoyi)框架中的 generator

本文是若依单体项目 thymeleaf 版本。module: ruoyi-generator.


生成工具通过后台管理界面的方式让用户进行设置,配置信息保存在数据库中,根据配置生成一套 CRUD 代码,很是方便。


生成代码总体配置类 GenConfig,设置了包名称规则,作者等全局信息。


入口控制类GenController preview 预览代码, download 下载 zip 包,genCode 生成代码。


  1. 数据库查询配置信息 GenTable 加入到 VelocityContext 中,在 vm 模板中即可取值

  2. 对定义的模板进行渲染 tpl.merge(context, sw)


public Map<String, String> previewCode(Long tableId){    Map<String, String> dataMap = new LinkedHashMap<>();    // 查询表信息    GenTable table = genTableMapper.selectGenTableById(tableId);    // 设置主子表信息    setSubTable(table);    // 设置主键列信息    setPkColumn(table);    VelocityInitializer.initVelocity();
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表 List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory()); for (String template : templates) { // 渲染模板 StringWriter sw = new StringWriter(); Template tpl = Velocity.getTemplate(template, Constants.UTF8); tpl.merge(context, sw); dataMap.put(template, sw.toString()); } return dataMap;}
复制代码


Put 到 VelocityContext 中的变量


public static VelocityContext prepareContext(GenTable genTable){    String moduleName = genTable.getModuleName();    String businessName = genTable.getBusinessName();    String packageName = genTable.getPackageName();    String tplCategory = genTable.getTplCategory();    String functionName = genTable.getFunctionName();
VelocityContext velocityContext = new VelocityContext(); velocityContext.put("tplCategory", genTable.getTplCategory()); velocityContext.put("tableName", genTable.getTableName()); velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); velocityContext.put("ClassName", genTable.getClassName()); velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); velocityContext.put("moduleName", genTable.getModuleName()); velocityContext.put("businessName", genTable.getBusinessName()); velocityContext.put("basePackage", getPackagePrefix(packageName)); velocityContext.put("packageName", packageName); velocityContext.put("author", genTable.getFunctionAuthor()); velocityContext.put("datetime", DateUtils.getDate()); velocityContext.put("pkColumn", genTable.getPkColumn()); velocityContext.put("importList", getImportList(genTable)); velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); velocityContext.put("columns", genTable.getColumns()); velocityContext.put("table", genTable); setMenuVelocityContext(velocityContext, genTable); if (GenConstants.TPL_TREE.equals(tplCategory)) { setTreeVelocityContext(velocityContext, genTable); } if (GenConstants.TPL_SUB.equals(tplCategory)) { setSubVelocityContext(velocityContext, genTable); } return velocityContext;}
复制代码


生成代码的模板,


html: crud 的方法 add.html,edit.html,list.html,list-tree.html


Java: controller,service,domain,mapper,serviceImpl


Sql:生成菜单用


自定义的 mapper.xml


若依框架 generator 为前端页面框架 ruoyi-admin 生成了一套完美契合的快速开发的 CRUD 代码,并支持的用户的选择配置,页面的查询功能都已封装,在此学习,向大神致敬。

总结:

velocity 模板引擎分三步,1.初始化 配置模板加载地址;2.放置上下文变量 velocityContext;3.渲染模板。


有框架的项目一般会为,框架定制一个代码生成器,以使代码规范化,同时提高开发效率。


个人项目或中小项目开发应用现成框架即可,若不满足需求,要进行修改,了解模板原理,即可快速扩展。


撸文不易,感谢您的鼓励。

用户头像

Rubble

关注

还未添加个人签名 2021.06.01 加入

还未添加个人简介

评论

发布
暂无评论
从mybatis-plus-generator看如何编写代码生成器_4月日更_Rubble_InfoQ写作社区