代码生成rapid-generator源码分析

来源:互联网 发布:雄鹰网校大数据 编辑:程序博客网 时间:2024/06/06 09:41

  • 原理
  • rapid-generator
    • rapidDemo
      • 假设有mysql数据库和表
      • 修改generatorxml文件发在代码根目录就行生成代码会在classes目录加载配置文件主要是
      • 模板文件
      • 生成代码
      • rapid生成的代码文件
    • 源码分析
      • 常用类
      • 加载配置文件
      • 获取数据库元数据
      • 构建输出模型和处理输出
  • 总结
    • 一个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目录加载)配置文件,主要是:

  1. 数据库配置;
  2. basepackage:输出的包名配置;
  3. 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. 构建输出模型和处理输出。

常用类

根据我的实际使用情况,删除了部分源码,保留主流程。

rp常用类

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;}

感谢那位哥们提供了这样的好工具

0 0
原创粉丝点击