代码生成rapid-generator源码分析
来源:互联网 发布:雄鹰网校大数据 编辑:程序博客网 时间:2024/06/06 09:41
- 原理
- rapid-generator
- rapidDemo
- 假设有mysql数据库和表
- 修改generatorxml文件发在代码根目录就行生成代码会在classes目录加载配置文件主要是
- 模板文件
- 生成代码
- rapid生成的代码文件
- 源码分析
- 常用类
- 加载配置文件
- 获取数据库元数据
- 构建输出模型和处理输出
- rapidDemo
- 总结
- 一个bug
- 感谢那位哥们提供了这样的好工具
之前有个项目比较紧急,基本上开发和设计同步进行,所以设计和数据模型基本上每天都会变,每天上班的第一件事就是检查数据模型的变动,内心一万头神兽奔腾,后来忙完想到写个代码生成工具,每天过来自己跑下程序,自行检查,搜索发现这个开源的rapid-generator工具,只要编写模板文件就可以,完全满足需求。看下源码,学习之。
原理
废话没有,就是利用freemarker来实现的,关于freemarker不多说,记住一点就行:输出 = 模板 + 模型,详细的自己搜索学习。
写个freemarker的demo理解下,假如我们要生成这样一个java类:
package com.su.autocode;/** * @author:admin * @version:1.0 * @since:1.0 * @createTime:2016-10-26 09:36:11 */pulic class User { private String username; private String password; public User(){} public User(String username, String password){ this.username = username; this.password = password; } public void setUsername(String username){ this.username = username; } public String getUsername(){ return this.username; } public void setPassword(String password){ this.password = password; } public String getPassword(){ return this.password; }}
先抽象成一个demo.ftl模板:
package ${basePackage_dir};/** * @author:${author} * @version:1.0 * @since:1.0 * @createTime:<#if now??>${now?string('yyyy-MM-dd HH:mm:ss')}</#if> */pulic class ${className} { <#list attrs as attr> private ${attr.javaType} ${attr.name}; </#list> public ${className}(){} <#list attrs as attr> public void set${attr.name?cap_first}(String ${attr.name}){ this.${attr.name} = ${attr.name}; } public void get${attr.name?cap_first}(}){ return this.${attr.name}; } </#list> }
freemarker生成代码:
public class FreemarkerDemo { public static void main(String[] args){ Map<String, String> attr1 = new HashMap<String, String>(); attr1.put("javaType", "String"); attr1.put("name", "username"); Map<String, String> attr2 = new HashMap<String, String>(); attr2.put("javaType", "String"); attr2.put("name", "password"); List<Object> attrs = new ArrayList<Object>(); attrs.add(attr1); attrs.add(attr2); Map<String,Object> root = new HashMap<String, Object>(); root.put("basePackage_dir", "com.su.autocode"); root.put("author", "admin"); root.put("now", new Date()); root.put("className", "User"); root.put("attrs", attrs); Configuration cfg = new Configuration(); try { FileTemplateLoader[] templateLoaders = new FileTemplateLoader[1]; templateLoaders[0] = new FileTemplateLoader(new File("C:\\Users\\chris\\Desktop")); MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader(templateLoaders); cfg.setTemplateLoader(multiTemplateLoader); Template template = cfg.getTemplate("demo.ftl"); //获取模板 StringWriter out = new StringWriter(); //out可以输出到file template.process(root, out); System.out.println(out.toString()); } catch (IOException e) { e.printStackTrace(); } catch (TemplateException e) { e.printStackTrace(); } }}
那位哥们写的代码生成,基本原理也就是这样,一切看起来都是那么美好,也似乎很简单,不过上面的也只是demo,想做好,就要考虑很多细节了。
rapid-generator
这个代码生成我主要是用来对数据模型生成sqlmap、bean、dao、service,因为用到了公司的组件内容,所以重新编写了模板,不过这个框架也的确做的好,基本上只是编写个代码模板,都不用更改框架代码。
我是用maven从公司私服下添加的依赖。需要3个jar包:rapid-generator.jar, freemarker.jar和你数据库的驱动包。
先来个demo看这个框架怎么用。
rapidDemo
假设有mysql数据库和表:
CREATE TABLE `user` ( `id` bigint(20) NOT NULL auto_increment, `name` varchar(20) default NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8
修改generator.xml(文件发在代码根目录就行,生成代码会在classes目录加载)配置文件,主要是:
- 数据库配置;
- basepackage:输出的包名配置;
- outRoot:输出的文件目录。
其他字段一般不用更改。
模板文件
假设我们要生成表对应的bean,模板为:
<#assign className = table.className> <#assign classNameLower = className?uncap_first> package ${basepackage}.bean;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import org.apache.ibatis.type.Alias;/** <#if table.remarks?exists && table.remarks != '' && table.remarks != 'null'> * ${table.remarks} </#if> * @author:admin * @version:1.0 * @since:1.0 * @createTime:<#if now??>${now?string('yyyy-MM-dd HH:mm:ss')}</#if> */@Alias("${classNameLower}")@Entity(name = "${table.sqlName}")public class ${className} implements java.io.Serializable{ private static final long serialVersionUID = 1L; <#list table.columns as column> <#if column.remarks?exists && column.remarks != '' && column.remarks != 'null'> /** ${column.remarks} */ </#if> private ${column.javaType} ${column.columnNameLower}; </#list> public ${className}(){ } <#list table.columns as column> public void set${column.columnName}(${column.javaType} ${column.columnNameLower}) { this.${column.columnNameLower} = ${column.columnNameLower}; } <#if column.pk> @Id @GeneratedValue(strategy = GenerationType.AUTO) </#if> @Column(name = "${column.sqlName}") public ${column.javaType} get${column.columnName}() { return this.${column.columnNameLower}; } </#list> }
生成代码
public class RapidDemo { public static void main(String[] args) throws Exception { GeneratorFacade g = new GeneratorFacade(); /** 代码模板文件根目录 */ g.getGenerator().addTemplateRootDir("D:\\workspaces\\NettyRpc-master\\template"); /** 删除代码生成输出目录,配置在generator.xml的outRoot */ g.deleteOutRootDir(); /** 只尝试了下面2中,rapid-framework还支持根据sql,类生成等方式 */ /** 所有表对应代码 */// g.generateByAllTable(); /** 指定表对应代码 */ g.generateByTable("user"); }}
rapid生成的代码文件
package com.su.chris.bean;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import org.apache.ibatis.type.Alias;/** * @author:admin * @version:1.0 * @since:1.0 * @createTime:2016-10-26 11:16:28 */@Alias("user")@Entity(name = "user")public class User implements java.io.Serializable{ private static final long serialVersionUID = 1L; private java.lang.Long id; private java.lang.String name; public User(){ } public void setId(java.lang.Long id) { this.id = id; } @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") public java.lang.Long getId() { return this.id; } public void setName(java.lang.String name) { this.name = name; } @Column(name = "name") public java.lang.String getName() { return this.name; }}
Ok,完全没问题。
源码分析
既然是用freemarker,那么还是之前那就话,输出 = 模板 + 模型。看完这个代码生成的源码,整个流程可以概括为下面几步:
1. 加载generator.xml配置文件;
2. 获取数据库元数据;
3. 构建输出模型和处理输出。
常用类
根据我的实际使用情况,删除了部分源码,保留主流程。
generator包下删除了context类,生成代码不需要context。
1. Generator:代码生成的核心,最终代码生成在这里;
2. GeneratorConstants:常量类,那些参数可以在配置文件在配置,提供了默认值;
3. GeneratorControl:控制生成器的过程中一些动作,有一些参数,如是否容许覆盖,生成目录等;
4. GeneratorFacade:facade模式,入口类;
5. GeneratorProperties:处理配置文件的加载,有参数PropertiesHelper,大多数方法通过委托持有的PropertiesHelper实现。这里我觉得有点设计不好,因为PropertiesHelper有点像工具类,但是又持有真正的Properties,不如Properties持有GeneratorProperties,把PropertiesHelper处理成真正的工具类;
6. DataSourceProvider:主要是通过配置文件获取数据源,支持jndi和常用的链接配置;
7. TableFactory:获取数据库元数据,创建成Table对象;
8. Table、Column等:就是对应数据库的表和列的一些属性;
9. 其他的就是一些工具类;
加载配置文件
GeneratorFacade g = new GeneratorFacade();
GeneratorFacade:private Generator generator = new Generator();
Generator:private String removeExtensions = GeneratorProperties.getProperty(GENERATOR_REMOVE_EXTENSIONS);
触发:
GeneratorProperties:/** 配置文件名称,看样子作者是支持2种格式的 */static final String PROPERTIES_FILE_NAMES[] = new String[]{"generator.properties","generator.xml"};static PropertiesHelper propertiesHelper;static { reload();}
这里我改了下,原本propertiesHelper这里会直接实例化下,但是我觉得跟下面的static重复,所以删除了,用static代码块直接加载配置文件数据。
public static void reload() { try { GLogger.println("Start Load GeneratorPropeties from classpath:"+Arrays.toString(PROPERTIES_FILE_NAMES)); Properties p = new Properties(); /** 这里通过PropertiesHelper加载配置文件 */ String[] loadedFiles = PropertiesHelper.loadAllPropertiesFromClassLoader(p,PROPERTIES_FILE_NAMES); GLogger.println("GeneratorPropeties Load Success,files:"+Arrays.toString(loadedFiles)); setSepicalProperties(p, loadedFiles); setProperties(p); }catch(IOException e) { throw new RuntimeException("Load "+PROPERTIES_FILE_NAMES+" error",e); }}/** 处理了个特殊目录,搜索代码没看见什么地方用,估计是作者其他框架用吧 */private static void setSepicalProperties(Properties p, String[] loadedFiles) { if(loadedFiles != null && loadedFiles.length > 0) { String basedir = p.getProperty("basedir"); if(basedir != null && basedir.startsWith(".")) { p.setProperty("basedir", new File(new File(loadedFiles[0]).getParent(),basedir).getAbsolutePath()); } }}/** 这里就加载的配置文件让propertiesHelper来持有 */public static void setProperties(Properties inputProps) { propertiesHelper = new PropertiesHelper(inputProps,true); for(Iterator it = propertiesHelper.entrySet().iterator();it.hasNext();) { Map.Entry entry = (Map.Entry)it.next(); GLogger.debug("[Property] "+entry.getKey()+"="+entry.getValue()); } GLogger.println("");}
看下PropertiesHelper.loadAllPropertiesFromClassLoader()
:
public static String[] loadAllPropertiesFromClassLoader(Properties properties,String... resourceNames) throws IOException { List successLoadProperties = new ArrayList(); for(String resourceName : resourceNames) { /** 从classes目录获取配置文件路径 */ Enumeration urls = ClassHelper.getDefaultClassLoader().getResources(resourceName); while (urls.hasMoreElements()) { URL url = (URL) urls.nextElement(); successLoadProperties.add(url.getFile()); InputStream input = null; try { URLConnection con = url.openConnection(); con.setUseCaches(false); input = con.getInputStream(); /** 这里判断格式,然后加载配置文件 */ if(resourceName.endsWith(".xml")){ properties.loadFromXML(input); }else { properties.load(input); } } finally { if (input != null) { input.close(); } } } } return (String[])successLoadProperties.toArray(new String[0]);}
配置文件加载后,后期就可以获取配置参数,如数据库的配置,输出目录,包名等。
获取数据库元数据
数据库元数据是在真正处理模板前才会获取,不是生成器核心Generator generator = new Generator();
实例化的时候就提前处理完。
以上面的demo为例:
/** 指定表对应代码 */g.generateByTable("user");
GeneratorFacade:public void generateByTable(String... tableNames) throws Exception { for(String tableName : tableNames) { /** 如果有多个表名,循环处理 */ new ProcessUtils().processByTable(tableName,false); }}内部类ProcessUtils处理:public void processByTable(String tableName,boolean isDelete) throws Exception { if("*".equals(tableName)) { if(isDelete) deleteByAllTable(); else generateByAllTable(); return; } Generator g = getGenerator(); /** 获取数据库表Table元数据 */ Table table = TableFactory.getInstance().getTable(tableName); try { /** 根据table信息生成 */ processByTable(g,table,isDelete); }catch(GeneratorException ge) { PrintUtils.printExceptionsSumary(ge.getMessage(),getGenerator().getOutRootDir(),ge.getExceptions()); throw ge; }}
转到TableFactory,真正处理数据库元数据的地方:
/** 单例获取实例化类,注意有的数据库有schema和catalog,有的话需要在配置文件中配置 */public synchronized static TableFactory getInstance() { if(instance == null) instance = new TableFactory(GeneratorProperties.getNullIfBlank(GeneratorConstants.JDBC_SCHEMA),GeneratorProperties.getNullIfBlank(GeneratorConstants.JDBC_CATALOG)); return instance;}public Table getTable(String tableName) { return getTable(getSchema(),tableName);}private Table getTable(String schema,String tableName) { return getTable(getCatalog(),schema,tableName);}private Table getTable(String catalog,String schema,String tableName) { Table t = null; try { t = _getTable(catalog,schema,tableName); /** 上面是根据你给的表名获取,获取不到就转换大小写重新获取下 */ if(t == null && !tableName.equals(tableName.toUpperCase())) { t = _getTable(catalog,schema,tableName.toUpperCase()); } if(t == null && !tableName.equals(tableName.toLowerCase())) { t = _getTable(catalog,schema,tableName.toLowerCase()); } }catch(Exception e) { throw new RuntimeException(e); } if(t == null) { Connection conn = DataSourceProvider.getConnection(); try { throw new NotFoundTableException("not found table with give name:"+tableName+ (DatabaseMetaDataUtils.isOracleDataBase(DatabaseMetaDataUtils.getMetaData(conn)) ? " \n databaseStructureInfo:"+DatabaseMetaDataUtils.getDatabaseStructureInfo(DatabaseMetaDataUtils.getMetaData(conn),schema,catalog) : "")+"\n current "+DataSourceProvider.getDataSource()+" current schema:"+getSchema()+" current catalog:"+getCatalog()); }finally { DBHelper.close(conn); } } return t;}/** 真正获取的地方 */private Table _getTable(String catalog,String schema,String tableName) throws SQLException { if(tableName== null || tableName.trim().length() == 0) throw new IllegalArgumentException("tableName must be not empty"); catalog = StringHelper.defaultIfEmpty(catalog, null); schema = StringHelper.defaultIfEmpty(schema, null); /** DataSourceProvider获取连接,很简单,也支持jndi方式 */ Connection conn = DataSourceProvider.getConnection(); DatabaseMetaData dbMetaData = conn.getMetaData(); ResultSet rs = dbMetaData.getTables(catalog, schema, tableName, null); try { while(rs.next()) { /** 内部类TableCreateProcessor创建Table,主要是构建table、column结构和获取元数据,跟着看就行 */ Table table = new TableCreateProcessor(conn,getSchema(),getCatalog()).createTable(rs); return table; } }finally { DBHelper.close(conn,rs); } return null;}
OK,到这里我们的数据模型的元数据就有,下面就是构建输出模型和处理输出
构建输出模型和处理输出
以demo为例,上面代码到了:
GeneratorFacade内部类ProcessUtils:/** 获取数据库表Table元数据 */Table table = TableFactory.getInstance().getTable(tableName);try { /** 根据table信息生成 */ processByTable(g,table,isDelete);}catch(GeneratorException ge) { PrintUtils.printExceptionsSumary(ge.getMessage(),getGenerator().getOutRootDir(),ge.getExceptions()); throw ge;}public void processByTable(Generator g, Table table,boolean isDelete) throws Exception { /** 构建输出模型 */ GeneratorModel m = GeneratorModelUtils.newGeneratorModel("table",table); PrintUtils.printBeginProcess(table.getSqlName()+" => "+table.getClassName(),isDelete); if(isDelete) g.deleteBy(m.templateModel,m.filePathModel); //删除 else g.generateBy(m.templateModel,m.filePathModel); //处理输出}
构建模型:
/** 构建输出模型GeneratorModel,这个类持有2个变量: *用于存放'模板'可以引用的变量templateModel, *用于存放'文件路径'可以引用的变量 filePathModel,这个变量主要是路径也可以用freemarker来配置变量 */public static GeneratorModel newGeneratorModel(String key,Object valueObject) { GeneratorModel gm = newDefaultGeneratorModel(); gm.templateModel.put(key, valueObject); //这里讲table加入,挺好的处理,key是不同的入参 gm.filePathModel.putAll(BeanHelper.describe(valueObject)); return gm;}public static GeneratorModel newDefaultGeneratorModel() { Map templateModel = new HashMap(); templateModel.putAll(getShareVars()); //模型加入共享数据 Map filePathModel = new HashMap(); filePathModel.putAll(getShareVars()); //路径加入共享数据 return new GeneratorModel(templateModel,filePathModel);}/** 加入一些共享数据 */public static Map getShareVars() { Map templateModel = new HashMap(); /** GeneratorProperties增加方法替换所有点号为下划线 */ templateModel.putAll(GeneratorProperties.resolveKeyPlaceholder(System.getProperties())); //系统参数 templateModel.putAll(GeneratorProperties.getProperties()); //配置文件中参数 templateModel.put("env", System.getenv()); //环境变量 templateModel.put("now", new Date()); templateModel.put(GeneratorConstants.DATABASE_TYPE.code, GeneratorProperties.getDatabaseType(GeneratorConstants.DATABASE_TYPE.code)); templateModel.putAll(getToolsMap()); //模板中可以使用的工具类,这个我没试过,freemarker一般的处理个人感觉基本够用,就没管这个 return templateModel;}
现在输出模型有了,接下来就是输出文件了:
Generator:/** * 生成文件 * @param templateModel 生成器模板可以引用的变量 * @param filePathModel 文件路径可以引用的变量 * @throws Exception */public Generator generateBy(Map templateModel,Map filePathModel) throws Exception { processTemplateRootDirs(templateModel, filePathModel,false); return this;}@SuppressWarnings("unchecked")private void processTemplateRootDirs(Map templateModel,Map filePathModel,boolean isDelete) throws Exception { if(StringHelper.isBlank(getOutRootDir())) throw new IllegalStateException("'outRootDir' property must be not empty."); if(templateRootDirs == null || templateRootDirs.size() == 0) throw new IllegalStateException("'templateRootDirs' must be not empty"); GLogger.debug("******* Template reference variables *********",templateModel); GLogger.debug("\n\n******* FilePath reference variables *********",filePathModel); //生成 路径值,如 pkg=com.company.project 将生成 pkg_dir=com/company/project的值 /** 这里是将所有key,再额外生成一个key_dir的数据,感觉没必要,不如直接配置文件限制一些可以配置路径 */ templateModel.putAll(GeneratorHelper.getDirValuesMap(templateModel)); filePathModel.putAll(GeneratorHelper.getDirValuesMap(filePathModel)); GeneratorException ge = new GeneratorException("generator occer error, Generator BeanInfo:"+BeanHelper.describe(this)); List<File> processedTemplateRootDirs = processTemplateRootDirs(); //模板根路径 for(int i = 0; i < processedTemplateRootDirs.size(); i++) { File templateRootDir = (File)processedTemplateRootDirs.get(i); /** 扫描根目录下所有模板文件并处理 */ List<Exception> exceptions = scanTemplatesAndProcess(templateRootDir,processedTemplateRootDirs,templateModel,filePathModel,isDelete); ge.addAll(exceptions); } if(!ge.exceptions.isEmpty()) throw ge;}/** * 用于子类覆盖,预处理模板目录,如执行文件解压动作 * 这里本来还处理了一种场景,就是模板压缩的情况,有个解压的过程,我直接删掉了,太费事,不如简单点,所以我直接返回模板根目录 **/protected List<File> processTemplateRootDirs() throws Exception { return templateRootDirs;}/** * 搜索templateRootDir目录下的所有文件并生成东西 * @param templateRootDir 用于搜索的模板目录 * @param templateRootDirs freemarker用于装载模板的目录 */private List<Exception> scanTemplatesAndProcess(File templateRootDir,List<File> templateRootDirs,Map templateModel,Map filePathModel,boolean isDelete) throws Exception { if(templateRootDir == null) throw new IllegalStateException("'templateRootDir' must be not null"); GLogger.println("-------------------load template from templateRootDir = '"+templateRootDir.getAbsolutePath()+"' outRootDir:"+new File(outRootDir).getAbsolutePath()); /** 获取模板根目录下所有模板文件,忽略一些不需要的文件 ,入svn文件等,可配*/ List srcFiles = FileHelper.searchAllNotIgnoreFile(templateRootDir); List<Exception> exceptions = new ArrayList(); /** 对所有模板文件轮询处理 */ for(int i = 0; i < srcFiles.size(); i++) { File srcFile = (File)srcFiles.get(i); try { if(isDelete){ new TemplateProcessor(templateRootDirs).executeDelete(templateRootDir, templateModel,filePathModel, srcFile); //删除 }else { long start = System.currentTimeMillis(); new TemplateProcessor(templateRootDirs).executeGenerate(templateRootDir, templateModel,filePathModel, srcFile); //创建 GLogger.perf("genereate by tempate cost time:"+(System.currentTimeMillis() - start)+"ms"); } }catch(Exception e) { if (ignoreTemplateGenerateException) { GLogger.warn("iggnore generate error,template is:" + srcFile+" cause:"+e); exceptions.add(e); } else { throw e; } } } return exceptions;}
把最后创建的语句提出来:
Generator:new TemplateProcessor(templateRootDirs).executeGenerate(templateRootDir, templateModel,filePathModel, srcFile); //创建Generator内部类TemplateProcessor:private void executeGenerate(File templateRootDir,Map templateModel, Map filePathModel ,File srcFile) throws SQLException, IOException,TemplateException { String templateFile = FileHelper.getRelativePath(templateRootDir, srcFile); /** 配置文件可以配置哪些模板文件需要处理,哪些些不需要,这里是check下 */ if(GeneratorHelper.isIgnoreTemplateProcess(srcFile, templateFile,includes,excludes)) { return; } /** 这里是处理一些二进制文件,直接copy过去 */ if(isCopyBinaryFile && FileHelper.isBinaryFile(srcFile)) { String outputFilepath = proceeForOutputFilepath(filePathModel, templateFile); File outputFile = new File(getOutRootDir(),outputFilepath); GLogger.println("[copy binary file by extention] from:"+srcFile+" => "+outputFile); FileHelper.parentMkdir(outputFile); IOHelper.copyAndClose(new FileInputStream(srcFile), new FileOutputStream(outputFile)); return; } try { /** 处理文件路径的变量变成输出路径,假如路径为${basepackage_dir^cap_first}/sit 就需要处理成真正的路径 * 注意路径配置如果?这个符号要转为^ */ String outputFilepath = proceeForOutputFilepath(filePathModel,templateFile); /** 就是生成过程中的一些配置 */ initGeneratorControlProperties(srcFile,outputFilepath); processTemplateForGeneratorControl(templateModel, templateFile); //处理模板,会有freemarker的configuration设置 if(gg.isIgnoreOutput()) { GLogger.println("[not generate] by gg.isIgnoreOutput()=true on template:"+templateFile); return; } if(StringHelper.isNotBlank(gg.getOutputFile())) { generateNewFileOrInsertIntoFile(templateFile,gg.getOutputFile(), templateModel); //生成文件 } }catch(Exception e) { throw new RuntimeException("generate oucur error,templateFile is:" + templateFile+" => "+ gg.getOutputFile()+" cause:"+e, e); }}/** 生成过程中的一些配置 */private void initGeneratorControlProperties(File srcFile,String outputFile) throws SQLException { gg.setSourceFile(srcFile.getAbsolutePath()); gg.setSourceFileName(srcFile.getName()); gg.setSourceDir(srcFile.getParent()); gg.setOutRoot(getOutRootDir()); gg.setOutputEncoding(outputEncoding); gg.setSourceEncoding(sourceEncoding); gg.setMergeLocation(GENERATOR_INSERT_LOCATION); gg.setOutputFile(outputFile);}private void processTemplateForGeneratorControl(Map templateModel,String templateFile) throws IOException, TemplateException { templateModel.put("gg", gg); Template template = getFreeMarkerTemplate(templateFile); //获取模板,会有freemarker的configuration设置 template.process(templateModel, IOHelper.NULL_WRITER); //这里没搞懂为什么process下}
有些代码没贴出来,跟着看就是。OK,生成结束,facade里面还有其他一些方法,还好,看完这个应该再去看应该没什么问题。
总结
一个bug
如果你在模板中想获取系统变量的值,会出错。例如,你的javadoc里面,你生成的时候想说明是谁生成的,用系统变量user.name,模板里面你写成author:${user.name},那么就出错。
这是因为freemarker对user.name的解析有问题,freemarker不是把user.name整体作为一个key,认为user.name的点号存在下一层关系,所以出错。
后来把框架这里的处理代码改了把点号全部替换为下划线,然后模板取user_name就没问题了。作者本人在配置文件全部用下划线,不知道为什么这里不改。
GeneratorFacade.java代码所在:
public static Map getShareVars() { Map templateModel = new HashMap(); /** 原代码 templateModel.putAll(System.getProperties()); */ /** GeneratorProperties增加方法替换所有点号为下划线 */ templateModel.putAll(GeneratorProperties.resolveKeyPlaceholder(System.getProperties())); templateModel.putAll(GeneratorProperties.getProperties()); templateModel.put("env", System.getenv()); templateModel.put("now", new Date()); templateModel.put(GeneratorConstants.DATABASE_TYPE.code, GeneratorProperties.getDatabaseType(GeneratorConstants.DATABASE_TYPE.code)); templateModel.putAll(getToolsMap()); return templateModel;}
感谢那位哥们提供了这样的好工具
- 代码生成rapid-generator源码分析
- Maven自定义插件开发(rapid-generator自动生成代码)
- rapid-generator JAVA代码生成器
- Java 代码生成器 rapid-generator 配置
- 【rapid-generator】代码生成器工具使用
- rapid-generator
- 代码生成工具 rapid-framework
- MyBatis-Generator 代码生成
- mybatis-generator 代码生成
- mybatis-generator生成代码
- mybatis generator生成代码
- Mybatis generator生成代码
- Mybatis generator 生成代码
- MyBatis Generator 源码分析
- Java 生成器 rapid-generator 配置
- Java代码生成利器之rapid-generate应用一
- Java代码生成利器之rapid-generate应用二
- Java代码生成利器之rapid-generate应用三
- reinforcement learning,增强学习:Policy Gradient
- 关于网站速度优化的一点建议
- 大话C语言指针变量
- sqoop-1.4.6的安装
- 从网页Web上调用本地应用程序(.jar、.exe)的主流处理方法
- 代码生成rapid-generator源码分析
- 视频编码解码sdk
- C语言指针变量的运算
- 智能指针auto_ptr详解
- C语言数组指针(指向数组的指针)
- python2.7+Selenium3+firefox49
- tomcat日志解析
- C语言字符串指针(指向字符串的指针)
- DEV TreeList的节点设置