前言
项目组但愿能有一个比较完善,能够生成各种代码的工具,由于以前写过因此趁这两天从新弄了个。java
代码生成工具命名为dgen
-> dexcoder-generator
,旨在提升开发人员效率,避免重复劳动。mysql
理论上能够生成任何想要的代码文件,包括实体类、dao、service及页面文件等,另外能够方便的实现扩展生成本身想要的东西。sql
为了方便开发避免重复造轮子,依赖了一些开源的第三方组件,主要为dom4j
,velocity
。数据库
dgen
的使用步骤为:编写配置文件 -> 编写模板文件 -> 运行。xcode
下面就按这个顺序对dgen
进行说明,并会在最后介绍如何对dgen
进行自定义的扩展。dom
配置文件说明
dgen
默认的配置文件名为dgen.xml
,固然能够随意改变它,只需在启动时指定文件名便可。ide
dgen.xml
文件是全部配置的入口,里面包含了工具运行时须要的各种信息,当配置信息较多时能够拆分红多个文件,使用include
标签引入。工具
dgen.xml
文件样例:ui
<?xml version="1.0" encoding="UTF-8"?><config> <constants> <!--生成时文件已存在是否覆盖 默认true--> <constant name="overwrite" value="true"/> <!--生成目标文件夹 不指定默认为运行目录--> <!--<constant name="targetDir" value="/Users/liyd/project"/>--> <!--是否运行在子模块 为true则生成代码的文件夹会到上一层(父模块)为基准 默认false --> <constant name="runOnChildModule" value="false"/> </constants> <!--jdbc链接信息配置--> <jdbc> <property name="dialect" value="mysql"/> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/dexcoder?useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value=""/> </jdbc> <!--数据类型转换映射--> <converters> <convert dbType="number" javaType="java.lang.Long"/> <convert dbType="VARCHAR2" javaType="java.lang.String"/> <convert dbType="SYS.XMLTYPE" javaType="java.lang.String"/> <convert dbType="TIMESTAMP" javaType="java.util.Date"/> <convert dbType="datetime" javaType="java.util.Date"/> <convert dbType="BLOB" javaType="java.lang.Byte[]"/> </converters> <include file="demo/tableConfig.xml"/></config>
config
根标签,固定。constants
常量标签,能够包含任意数量的constant
子标签,每个constant
子标签都定义了一个常量。jdbc
数据库链接配置信息。converters
数据类型转换映射。dbType
指定数据库的类型,javaType
指定你指望在生成Java类中的类型。include
标签,引入其它的配置文件(固然也能够不引入所有写在当前配置文件当中)。
这里把它拆分红了2个文件,使用了include
标签,引入的tableConfig.xml
文件样例:this
<?xml version="1.0" encoding="UTF-8"?><config> <table name="USER" desc="用户"> <task name="model"/> <task name="dao"/> </table> <tasks> <task name="model" class="com.dexcoder.dgen.generator.DefaultCodeGenerator"> <property name="template" value="demo/template/model.vm"/> <property name="beginFix" value=""/> <property name="endFix" value=""/> <property name="suffix" value=".java"/> <property name="moduleName" value="dexcoder-core"/> <property name="srcDir" value="src/main/java"/> <property name="package" value="com.dexcoder.model"/> </task> <task name="dao"> <property name="template" value="demo/template/dao.vm"/> <property name="beginFix" value=""/> <property name="endFix" value="Dao"/> <property name="suffix" value=".java"/> <property name="moduleName" value="dexcoder-core"/> <property name="srcDir" value="src/main/java"/> <property name="package" value="com.dexcoder.dao"/> </task> <task name="daoImpl"> <property name="template" value="demo/template/daoImpl.vm"/> <property name="beginFix" value=""/> <property name="endFix" value="DaoImpl"/> <property name="suffix" value=".java"/> <property name="moduleName" value="daoDir"/> <property name="srcDir" value="src/main/java"/> <property name="package" value="com.dexcoder.dao.impl"/> </task> <task name="service"> <property name="template" value="demo/template/service.vm"/> <property name="beginFix" value=""/> <property name="endFix" value="Service"/> <property name="suffix" value=".java"/> <property name="moduleName" value="serviceDir"/> <property name="srcDir" value="src/main/java"/> <property name="package" value="com.dexcoder.service"/> </task> </tasks></config>
里面主要配置了各项任务的生成参数信息,及指定了哪张表须要生成哪些代码任务。
config
标签,一样固定。table
指定要生成代码的表名,desc
是表的备注。能够配置多个多张表一块儿生成。task
指定了该表须要运行哪几个生成任务来生成相应的代码。
tasks
任务信息配置根标签。task
具体的任务信息,name
指定任务名。property
指定运行该任务所须要的属性信息。如下几个为必须的固定属性:template
指定模板文件。beginFix
生成代码文件前缀。endFix
生成代码文件后缀。suffix
生成代码文件的扩展名。moduleName
生成代码所在模块的模块名,多模块项目时有用。srcDir
生成的源代码文件夹。package
包名。
运行时参数说明
模板文件的编写须要依赖于一些运行时的动态参数,因此先对运行时的参数做一下说明。
这里的运行时参数,主要是为模板文件服务的,也就是在模板文件中能够直接使用的数据。主要有下面这些:
packageName
生成的代码文件所在包名。importClasses
须要导入的类,是一个List<String>
集合。注意这里的导入类只是生成过程当中产生的一些类型(如model中的变量类型),其它的须要在模板中自行书写。date
当前日期信息。table
生成表的信息。name
表名。desc
表备注。columns
包含的列信息。name
列名。camelName
骆驼命名法名称,首字母小写。firstUpperName
骆驼命名法名称,首字母大写。isPrimaryKey
是否主键。comment
列备注。dbType
数据库类型。jdbcType
jdbc类型。javaType
Java类型,例:String。javaClass
Java类型class名,例:java.lang.String。
tasks
当前表拥有的代码生成任务。task
当前运行的任务信息。name
任务名。clazz
任务处理的class。pluginMap
任务包含的插件,Map<String,String>
类型。properties
任务包含的属性,Map<String,String>
类型。
除了以上固定信息外,对于在任务运行时生成的一些类,能够用如下方式访问生成的类信息,这里假设生成的类为java.model.UserInfo
。
taskName
请替换成实际的任务名,另外任务须要在运行以后才能用该方式访问,对于一些有类依赖的生成,例如dao
最好放在service
以前生成以便在service中使用生成的dao类信息。
taskName.generatedLongClassName
全类名,返回java.model.UserInfo
。taskName.generatedShortClassName
类名,返回UserInfo
。taskName.firstLowerGeneratedClassName
首字母小写类名,返回userInfo
。taskName.lineThroughClassName
中划线分隔小写类名,返回user-info
。
编写模板文件
模板采用了velocity
组件来实现,根据上面介绍的动态参数能够很容易的完成模板的编写。下面是几个模板的样例。
model
类模板:
package ${packageName};#foreach($im in ${importClasses})import ${im};#end/*** ${table.desc}** Author: Created by code generator* Date: ${date}*/public class ${model.generatedShortClassName} { /** serialVersionUID */ private static final long serialVersionUID = ${serialVersionUID}L;#foreach($column in ${table.columns})#if(${column.comment}) /** ${column.comment} */#end private ${column.javaType} ${column.camelName};#end#foreach($column in ${table.columns}) public ${column.javaType} get${column.firstUpperCamelName}() { return ${column.camelName}; } public void set${column.firstUpperCamelName}(${column.javaType} ${column.camelName}) { this.${column.camelName} = ${column.camelName}; }#end}
dao
接口模板,这里只生成了一个insert方法。
package ${packageName};import ${model.generatedLongClassName};/*** ${table.desc} dao接口** Author: Created by code generator* Date: ${date}*/public interface ${dao.generatedShortClassName} { /** * 新增记录 * * @param ${model.firstLowerGeneratedClassName} */ void insert(${model.generatedShortClassName} ${model.firstLowerGeneratedClassName});}
运行
代码调用方式
只需一行代码:
StartupGenerator.run();
指定配置文件名,文件路径相对于当前运行项目目录
:
StartupGenerator.run("demo/dgen.xml");
jar包执行方式
直接运行:
java -jar codeGenerator.jar
指定配置文件名,文件路径相对于当前运行项目目录
:
java -jar codeGenerator.jar demo/dgen.xml
扩展
有些时候可能默认的功能没法知足项目的需求,你能够经过编写一些自定义扩展来加强dgen
的功能。
插件实现
插件演示一
这里以model
类实现了序列化,生成须要serialVersionUID
为例,dgen
默认并无提供,能够经过简单的插件方式来实现(该插件已内置实现)。
首先实现GeneratorPlugin
接口,该接口只有一个方法:
/** * 插件执行方法 * * @param table 当前运行任务表配置信息 * @param task 当前运行任务配置信息 * @param configuration 全部的配置信息 * @param templateText 模板内容 * @param context 模板解析时的VelocityContext */void execute(Table table, Task task, Configuration configuration, StringBuilder templateText, VelocityContext context);
方法的参数中能够拿到全部的想要的信息,包括表信息、任务信息、dgen
配置信息、模板内容、模板解析时的VelocityContext等,按需使用便可。
下面是serialVersionUID
插件的实现代码:
public void execute(Table table, Task task, Configuration configuration, StringBuilder templateText, VelocityContext context) { long serialVersionUID = Math.abs(UUID.randomUUID().getLeastSignificantBits()); context.put("serialVersionUID", serialVersionUID);}
只是添加了serialVersionUID
变量,这样在model
模板中就能够使用了,添加下面代码便可:
/** serialVersionUID */private static final long serialVersionUID = ${serialVersionUID}L;
固然别忘了把它添加到任务配置中:
<task name="model" class="com.dexcoder.dgen.generator.DefaultCodeGenerator"> <plugin name="serialVersionUIDPlugin" value="com.dexcoder.dgen.plugins.SerialVersionUIDPlugin"/> <property name="template" value="demo/template/model.vm"/> <property name="beginFix" value=""/> <property name="endFix" value=""/> <property name="suffix" value=".java"/> <property name="moduleName" value="dexcoder-core"/> <property name="srcDir" value="src/main/java"/> <property name="package" value="com.dexcoder.model"/></task>
插件演示二
咱们再来实现一个Excel
表格生成插件,假设咱们在生成model
类的同时想生成一份表结构的Excel文件。
实现的步骤上相同,只须要修改方法实现部分便可,如下是源代码,这里对Excel生成时行了封装,不在本文讨论范围:
public void execute(Table table, Task task, Configuration configuration, StringBuilder templateText, VelocityContext context) { ExcelSheet excelSheet = new ExcelSheet(); excelSheet.setSheetName("表信息"); List<String> rowTitles = new ArrayList<String>(); rowTitles.add("列名"); rowTitles.add("列备注"); rowTitles.add("数据库类型"); rowTitles.add("是否主键"); excelSheet.setRowTitles(rowTitles); List<ExcelRow> rowList = new ArrayList<ExcelRow>(); for (Column column : table.getColumns()) { ExcelRow excelRow = new ExcelRow(); excelRow.addCell(column.getName()); excelRow.addCell(column.getComment()); excelRow.addCell(column.getDbType()); excelRow.addCell(column.getIsPrimaryKey()); rowList.add(excelRow); } excelSheet.setRows(rowList); List<ExcelSheet> sheetList = new ArrayList<ExcelSheet>(); sheetList.add(excelSheet); ExcelWriteTools.write(sheetList, new File("/Users/liyd/Desktop/db.xls"));}
代码生成简单自定义重写
若是使用插件的方式没法知足你的需求,能够本身实现代码生成的方法,只须要继承AbstractCodeGenerator
类实现generate
方法便可。
默认的生成类为DefaultCodeGenerator
:
/*** 默认代码生成实现类** User: liyd* Date: 13-12-16* Time: 下午4:28*/public class DefaultCodeGenerator extends AbstractCodeGenerator { /** * 代码生成方法 * * @param table * @param task * @param context * @param template */ @Override public void generate(Table table, Task task, VelocityContext context, StringBuilder template) { Set<String> importClasses = this.getColumnsImportClass(table.getColumns()); context.put("importClasses", importClasses); context.put("date", new Date()); context.put("table", table); context.put("task", task); context.put("packageName", task.getPackageName()); //任务生成的类信息 String generatedLongClassName = task.getGeneratedReferenceClassName(table.getName()); String generatedShotClassName = task.getGeneratedShotClassName(table.getName()); String firstLowerGeneratedClassName = NameUtils.getFirstLowerName(generatedShotClassName); String lineThroughClassName = NameUtils.getLineThroughName(generatedShotClassName); Map<String, String> map = new HashMap<String, String>(); map.put("generatedLongClassName", generatedLongClassName); map.put("generatedShortClassName", generatedShotClassName); map.put("firstLowerGeneratedClassName", firstLowerGeneratedClassName); map.put("lineThroughClassName", lineThroughClassName); context.put(task.getName(), map); }}
能够看到只是往VelocityContext
中添加了一些数据而已,在这里彻底能够根据你本身的想法来实现一些特别的功能,至于如何读取模板、生成文件则不用去关心,默认都会帮你完成。
重写代码生成接口实现重写
假设上面的简单重写还没法知足需求,例如你想要对模板读取、插件运行、文件生成等方式作一些改变,那么能够直接实现接口CodeGenerator
,只须要实现一个方法:
/** * 代码生成方法 * @param table 当前运行表信息 * @param task 当前运行任务信息 * @param configuration 全部的配置信息 */void doGenerate(Table table, Task task, Configuration configuration);
参数中能够获取到运行的表、任务及全部的配置信息,能够根据本身的须要彻底改写dgen
的代码生成行为。
能够参考AbstractCodeGenerator
和DefaultCodeGenerator
中的实现。
固然,最后仍然是别忘了在任务配置中指定实现类:
<task name="model" class="com.dexcoder.dgen.test.CustomCodeGenerator"> ......</task>
扩展xml配置文件
有时候可能会须要在dgen.xml
配置文件中添加一些新的标签以使增长的扩展功能实现的更加优雅,这个一样能够很好的支持。
只须要实现XmlElementParser
接口:
public interface XmlElementParser { /** * 解析的元素名称 * * @return */ String getParseElementName(); /** * 解析元素 * * @param element * @param configuration */ void parseElement(Element element, Configuration configuration);}
getParseElementName
方法只须要返回该解析类解析的xml元素标签名称,当dgen
运行时解析到该元素标签时便会调用parseElement
方法,以完成你想要的操做。
如下是jdbc
标签的解析类实现,供参考:
public class JdbcParser extends AbstractXmlElementParser { public String getParseElementName() { return "jdbc"; } @Override public void doParseElement(Element element, Configuration configuration) { List<Element> elements = element.elements("property"); if (CollectionUtils.isEmpty(elements)) { return; } for (Element el : elements) { String name = el.attributeValue("name"); String value = el.attributeValue("value"); configuration.addJdbcConfig(name, value); } }}
最后,用如下代码运行:
ConfigParser configParser = new ConfigParser();//这里注册实现的xml标签解析器 JdbcParser是内置解析器默认已注册//configParser.registerParser(new JdbcParser());Configuration configuration = configParser.parseConfig(configFile);GenerationManager generationManager = new GenerationManager(configuration);generationManager.doGenerate();