天天看點

代碼生成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;
}
           

感謝那位哥們提供了這樣的好工具