代码生成器(记录一次兴趣代码,多多指教。转载请标明作者)
在我们开始实现代码生成器之前我们先来对代码生成器有一个简单的了解。
1.什么是代码生成器?
故名思义,也就是生成代码的一个程序。那它是一个什么样的机制?怎么体现这种机制?
比如:
我们学习的 JSP 技术,浏览器并不能直接识别 JSP 文件。当用户通过浏览器访问某个 JSP 页
面的时候,其中的交互分为分为几个步骤:
用户通过浏览器访问 JSP 页面
Tomcat 找到对应 JSP 资源,将 JSP 资源转化(编译,翻译)成为 HTML 数据
Tomcat 将 HTML 数据进行返回
在此,我们就可以这样理解
我们所编写 JSP ===》 HTML 的模板。
JSP 页面的标签 ===》模板中的占位符。
Tomcat 根据 JSP 模板,生成 HTML 页面。 数据库的数据不同,则生成的 HTML 页面也不同。
最后我们得出一个结论:
Tomcat 可以根据 JSP 模板和数据生成不同的 HTML 页面。
所以 Tomcat 对 JSP 的处理过程我们就可以认为是一个代码生成器的机制,其实也就是代码生成器的原理。
2.我们为什么要编写代码生成器?
我们先不说它在我们软件之中处处可见,单单就作为一个程序员的辅助工具而言,也是极大的提高了开发效率。
比如我们的实体类,一个项目中表的数量不会少于二三十个吧,倘若每一个实体类,都手动编写,这种重复的代码编写,嗯,作为程序员应该都懂得。
所以,代码生成器节省人力成本,提高了开发效率。
3.开始编写代码生成器。
前面铺垫了那么多,干货终于要来了。
3.1 编写工具:idea
3.2 依赖模板引擎: FreeMarker(FreeMarker 所需要的功能简介:请点击我的另一篇博客 FreeMarker)
maven依赖freemarker jar
<!-- https://mvnrepository.com/artifact/freemarker/freemarker --> <dependency> <groupId>freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.8</version> </dependency>
3.3 代码体系组成:
- 模板:生成文件的模板文件。
- 数据:生成文件所需要的关键数据。
- 合成机制:使用数据置换模板中的占位符,生成新的文件的机制。
3.4 所有实现代码
---------存放数据的对象
package cn.itrip.vo; /** * 列对象 */ public class ColumnVo { private String name;//数据库中的列名 private String fieldName;//对应的Java属性名 private String dbType;//数据中的记录类型 private String javaType;//对应的Java属性类型 private String comment;//数据库中的注释 private String upperCaseColumnName;//转成帕斯卡后的属性名 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getDbType() { return dbType; } public void setDbType(String dbType) { this.dbType = dbType; } public String getJavaType() { return javaType; } public void setJavaType(String javaType) { this.javaType = javaType; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public String getUpperCaseColumnName() { return upperCaseColumnName; } public void setUpperCaseColumnName(String upperCaseColumnName) { this.upperCaseColumnName = upperCaseColumnName; } }
package cn.itrip.vo; import java.util.ArrayList; import java.util.List; /** * 表对象 */ public class TableVo { private String className;//帕斯卡风格的类名 private String tableName;//表名 private String comment;//注释 private String lowerClassName;//骆驼命名法 private List<ColumnVo> columns=new ArrayList<ColumnVo>();//列对象集合 public String getLowerClassName() { return lowerClassName; } public void setLowerClassName(String lowerClassName) { this.lowerClassName = lowerClassName; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public List<ColumnVo> getColumns() { return columns; } public void setColumns(List<ColumnVo> columns) { this.columns = columns; } }
--------------帮助类
package cn.itrip.util; /** * 命名转换工具类 */ public class JavaNameUtil { public static String translate(String underscoreName,boolean isPascal){ StringBuilder result = new StringBuilder();//返回字符结果 if(underscoreName!=null && underscoreName.length()>0){ boolean flag=false; char firstChar =underscoreName.charAt(0);//判断获取首字母 if(isPascal){ result.append(Character.toUpperCase(firstChar));//将首字母转换成大写 }else{ result.append(firstChar); } //从第二个字符开始循环 for (int i=1;i<underscoreName.length();i++){ char ch=underscoreName.charAt(i); //判断是否出现下划线 if('_'==ch){ flag=true; }else { if(flag){ result.append(Character.toUpperCase(ch)); flag=false; }else { result.append(ch); } } } } return result.toString();//返回转换字符 } /** * 骆驼命名法 * @param str * @return */ public static String toCamel(String str){ return translate(str,false); } /** * 帕斯卡命名法 * @param str * @return */ public static String toPascal(String str){ return translate(str,true); } /** * 转换数据类型 * @param dbTypeName * @return */ public static String dbtype2JavaType(String dbTypeName){ String javaType=null; switch (dbTypeName){ case "VARCHAR":javaType="String";break; case "BIGINT":javaType="Long";break; case "INT":javaType="Integer";break; case "DATETIME":javaType="Date";break; default: javaType="String";break; } return javaType; } public static void main(String[] arg){ String testName =toCamel("user_liu_jh"); System.out.println(testName); String testName2=toPascal("user_liu_jh"); System.out.println(testName2); } }
package cn.itrip.util; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * 负责连接数据库、获取表的元信息、列的元信息 */ public class MetadataUtil { private static Connection conn;//连接对象 /*2. DatabaseMetaData接口常用的方法:获取数据库元数据 (1) ResultSet getTables(String catalog,String schemaPattern,String tableNamePattern,String[] types); //获取表信息 (2) ResultSet getPrimaryKeys(String catalog,String schema,String table); //获取表主键信息 (3) ResultSet getIndexInfo(String catalog,String schema,String table,boolean unique,boolean approximate); //获取表索引信息 (4) ResultSet getColumns(String catalog,String schemaPattern,String tableNamePattern,String columnNamePattern); //获取表列信息*/ private static DatabaseMetaData meta; static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); System.out.println("数据库连接失败!"); } } /** * 连接数据库获取数据库元数据 */ public static void openConnection() { try { if (conn == null || conn.isClosed()) {//isClosed:判断连接是否关闭 conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/XXX?useUnicode=true&characterEncoding=utf-8" , "XXX", "XXX");//连接数据库 XXX表示你自己的数据库名称 、用户名、密码 meta = conn.getMetaData();//获取元数据 } } catch (SQLException e) { e.printStackTrace(); } } /** * 获取表的注释 * @param * @return */ public static String getCommentByTableName(String tableName) throws SQLException { openConnection();//打开连接 Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SHOW CREATE TABLE " + tableName); String comment = null; if (rs != null && rs.next()) { comment=rs.getString(2); //判断字符,只获取注解部分 int index = comment.lastIndexOf("="); comment = comment.substring(index+1); } rs.close(); stmt.close(); conn.close(); return comment; } /** * 获取所有的表名 * @return */ public static String[] getTableNames(){ openConnection(); ResultSet rs=null; List<String> nameList = new ArrayList<>(); try{ rs=meta.getTables(null, null, null, new String[]{"TABLE"}); while (rs.next()){ String tName =rs.getString("TABLE_NAME"); nameList.add(tName);//将取出来的表名放入集合中 } }catch (SQLException e){ e.printStackTrace(); } return (String[])nameList.toArray(new String[]{}); } /** * 获取某个表中所有的列信息 * @param tableName * @return * @throws Exception */ public static List<String[]> getTableColumnsInfo(String tableName)throws Exception{ openConnection(); ResultSet rs= meta.getColumns(null, "%",tableName,"%"); List<String[]> columnInfoList =new ArrayList<>(); while (rs.next()){ String[] colInfo = new String[3]; colInfo[0]=rs.getString("COLUMN_NAME");//COLUMN_NAME 列名 colInfo[1]=rs.getString("REMARKS");//REMARKS 获取列注释 colInfo[2]=rs.getString("TYPE_NAME");//TYPE_NAME 列类型 columnInfoList.add(colInfo); } return columnInfoList; } }
---------合成机制
package cn.itrip.generator; import cn.itrip.util.JavaNameUtil; import cn.itrip.util.MetadataUtil; import cn.itrip.vo.ColumnVo; import cn.itrip.vo.TableVo; import freemarker.template.Configuration; import freemarker.template.Template; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 抽象生成器 */ public abstract class AbstractGenerator { protected Configuration cfg;//Freemarker配置对象 protected Map valueMap;//填充到模板的数据 protected Template template;//模板对象 protected String savePath;//保存生成文件的路径 protected List<TableVo> tableList;//表信息 public AbstractGenerator() throws Exception { cfg = new Configuration(); cfg.setClassForTemplateLoading(this.getClass(), "/templates/"); //获取模板文件位置 valueMap = new HashMap(); init();//初始化数据 } /** * 获取所有的表信息列信息 * @throws Exception */ public void init()throws Exception{ tableList=new ArrayList<>(); String[] tableNameArr =MetadataUtil.getTableNames();//获取表名称 for(String tName:tableNameArr){ String tComment = MetadataUtil.getCommentByTableName(tName);//获取注释 TableVo table = new TableVo();//创建表信息对象 table.setClassName(JavaNameUtil.toPascal(tName)); table.setComment(tComment); table.setTableName(tName); List<String[]> colInfoList =MetadataUtil.getTableColumnsInfo(tName); for (String[] colInfo:colInfoList){ String cName =colInfo[0]; String cComment =colInfo[1]; String cType =colInfo[2]; ColumnVo column = new ColumnVo(); column.setComment(cComment); column.setName(cName); column.setFieldName(JavaNameUtil.toCamel(cName)); column.setDbType(cType); column.setJavaType(JavaNameUtil.dbtype2JavaType(cType)); column.setUpperCaseColumnName(JavaNameUtil.toPascal(cName)); table.getColumns().add(column); } tableList.add(table); System.out.println(table); } System.out.println("构建元数据成功!"); } //抽象生成代码文件,各个子类实现 public abstract void generateCode() throws Exception; public void setSavePath(String savePath) { this.savePath = savePath; } }
package cn.itrip.generator; import cn.itrip.vo.TableVo; import freemarker.template.TemplateException; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; /** * 实体类生成器 */ public class EntityGenerator extends AbstractGenerator{ public EntityGenerator(String ftl) throws Exception {
try{
this.savePath="D:\\object\\itripljh\\itripbeans\\src\\main\\java\\cn\\itrip\\pojo";
//加载freemarker模板文件
super.template = cfg.getTemplate(ftl);
}catch (IOException e){
e.printStackTrace();
}
} public void generateCode() throws Exception { System.out.println("---------------开始生成代码"); valueMap.put("package","cn.itrip");//自定义包的位置 OutputStreamWriter writer=null; for(TableVo table:tableList) { valueMap.put("table",table); try{ //创建一个写入器 writer = new FileWriter(savePath+"/"+table.getClassName()+".java");//根据不同的文件自行修改 //合成数据和模板,写入到文件 this.template.process(valueMap,writer); writer.flush(); }catch (TemplateException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); }finally { writer.close(); } } System.out.println("生成代码成功!"); } }
----------------模板文件(pojo.ftl)
package ${package}.pojo; import java.io.Serializable; import java.util.Date; /** * ${table.comment} */ public class ${table.className} implements Serializable{ //生成私有属性 <#list table.columns as col> //${col.comment} private ${col.javaType} ${col.fieldName}; </#list> //生成getter、setter方法 <#list table.columns as col> public void set${col.upperCaseColumnName} (${col.javaType} ${col.fieldName}){ this.${col.fieldName} =${col.fieldName}; } public ${col.javaType} get${col.upperCaseColumnName} (){ return this.${col.fieldName}; } </#list> }
---------------入口
package cn.itrip; import cn.itrip.generator.EntityGenerator;public class App { public static void main(String[] args)throws Exception{ System.out.println("开始启动---------------"); String path=App.class.getClassLoader().getResource("").getPath(); System.out.println(path); EntityGenerator generator =new EntityGenerator("pojo.ftl"); try { generator.setSavePath("D:\\object\\itripljh\\itripbeans\\src\\main\\java\\pojo2"); generator.generateCode(); }catch (Exception e){ e.printStackTrace(); }
}
-----------最后执行的结果如下
完成。