天天看点

代码生成rapid-generator源码分析原理rapid-generator总结

  • 原理
  • 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[];
            templateLoaders[] = 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() NOT NULL auto_increment,
  `name` varchar() 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 = L;

    <#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 = L;

    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. 构建输出模型和处理输出。

常用类

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

代码生成rapid-generator源码分析原理rapid-generator总结

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 > ) {
        String basedir = p.getProperty("basedir");
        if(basedir != null && basedir.startsWith(".")) {
            p.setProperty("basedir", new File(new File(loadedFiles[]).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[]);
}
           

配置文件加载后,后期就可以获取配置参数,如数据库的配置,输出目录,包名等。

获取数据库元数据

数据库元数据是在真正处理模板前才会获取,不是生成器核心

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() == ) 
         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() == ) 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 = ; 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 = ; 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;
}
           

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