從使用到源碼—GreenDao(代碼生成即完結篇)

引言

  • 在此之前,鄙人寫過greenDao“基本使用”“核心類理解”兩篇文章,感興趣的童鞋可以看看。

  • “基本使用”一文中介紹過greenDao的兩種代碼生成方式,一種是在gradle配置一下,然後編譯期間就會在指定文件夾中生成DaoMasterDaoSession.java文件;還有一種是手寫代碼生成器,然後填寫相關屬性後就能自動生成包括Entity在內的.java文件。嗯,這也難怪,之所以greenDao效率這麼高,是因爲它在編譯期間就生成了和我們手動編寫一致的.java代碼文件,然後編譯成.class,這樣運行時就和運行普通靜態程序一致了。

  • 然後我就很好奇它是怎麼自動生成代碼的了,畢竟這種方式是很值得學習的,這也就是編寫本文的目的:分析greenDao代碼生成過程,學習如何自動生成代碼

源碼分析

  • greenDao代碼生成相關源碼在"這裏",代碼很簡短。

  • DaoGenerator

    我們先簡要回顧一下手動生成代碼時編寫的代碼,主控代碼如下,可以發現我們是通過DaoGenerator.generateAll開始執行生成的。

    public class MyDaoGenerator
    { 
        public static void main(String[] args)
        { 
            Schema schema = new Schema(1, "com.example.laixiaolong.greendaotraning.greenDao.db"); 
             addUser(schema);  
            new DaoGenerator().generateAll(schema, "./app/src/main/java");
        }
        // ...
    }
    

    於是我們找到DaoGenerator類,然後重現一下上面使用到的調用鏈,包括構造器和generateAll等。起初我去定位ConfigurationTemplate,發現沒法直接定位,於是查看它的的包歸屬,發現它是來自Apache的叫做FreeMarker的模板引擎。

    import freemarker.template.Configuration;
    import freemarker.template.Template;
    
    • 這說明greenDao基於這個模板引擎來自動生成代碼的,下面看看它的使用過程:
      • 創建Configuration實例,指定版本,並設置模板所在的文件夾
      • 然後通過Configuration#getTemplate它去加載模板,比如加載dao.ftl模板文件。
      • 最後通過Template#process執行生成引擎,這個過程會替換掉模板(如dao.ftl)中的模板變量,然後將替換後的模板數據寫入到指定的文件夾中,也就是.java文件。
    • 構造器:
    public DaoGenerator() throws IOException {  
        patternKeepIncludes = compilePattern("INCLUDES");
        patternKeepFields = compilePattern("FIELDS");
        patternKeepMethods = compilePattern("METHODS");
        // 1. 配置 Configuration
        Configuration config = getConfiguration("dao.ftl");
        // 2. 加載模板
        templateDao = config.getTemplate("dao.ftl");
        templateDaoMaster = config.getTemplate("dao-master.ftl"); 
        // ...
    }
    
    • generateAll
    public void generateAll(Schema schema, String outDir, String outDirEntity, String outDirTest) throws Exception {
         // ... 
        List<Entity> entities = schema.getEntities();
        for (Entity entity : entities) {
            generate(templateDao, outDirFile, entity.getJavaPackageDao(), entity.getClassNameDao(), schema, entity); 
            // ... 
        }
        generate(templateDaoMaster, outDirFile, schema.getDefaultJavaPackageDao(),schema.getPrefix() + "DaoMaster", schema, null); 
        // ...
    } 
    
    • generate
    private void generate(Template template, File outDirFile, String javaPackage, String javaClassName, Schema schema, Entity entity, Map<String, Object> additionalObjectsForTemplate) throws Exception {
        // 構建模板數據
        Map<String, Object> root = new HashMap<>();
        root.put("schema", schema);
        root.put("entity", entity);
        if (additionalObjectsForTemplate != null) {
            root.putAll(additionalObjectsForTemplate);
        }
        try {
           // ...
            Writer writer = new FileWriter(file);
            try {
                // 3. 執行模板引擎, 替換掉模板中的模板變量
                template.process(root, writer);
                writer.flush();
                System.out.println("Written " + file.getCanonicalPath());
            } finally {
                writer.close();
            }
        } catch (Exception ex) {//...}
    }
    

FreeMark模板引擎入門

  • 通過上面的分析,我們已經知道了greenDao是使用FreeMark模板引擎,並且知道了它的使用流程,那麼這裏我們也來用用,走一遍上面介紹的流程。

首先去官網下載jar包,然後按上述流程走一遍:

  • 配置Configuration
private static Configuration sConfiguration;
public static void initializeConfig() {

    sConfiguration = new Configuration(Configuration.VERSION_2_3_28);
    try {
        // 也就是放置模板的文件夾
        sConfiguration.setDirectoryForTemplateLoading(new File("src/templates/"));
    } catch (IOException e) {
        e.printStackTrace();
    }
    sConfiguration.setDefaultEncoding("UTF-8");
    sConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER); 
} 
  • 創建模板數據,也就是我們需要填充到模板上的真實數據,有兩種方式:

    • Java Bean形式,雖說是Java bean形式,但是依然需要以Map爲載體。
    public static Map<String, Object> createDataModelFromBean() {
        Author author = new Author();
        Person girlFriend = new Person()
                .setName("lalala")
                .setGender("女")
                .setAge("10");
    
        author.setGirlFriend(girlFriend)
                .setGender("男")
                .setAge("24")
                .setName("horseLai");
        // 雖說是Java bean形式,但是依然需要以 Map爲載體
        Map<String, Object> root = new HashMap<>();
        root.put("author", author);
        return root;
    }
    
    • 全部使用Map的形式
    public static  Map<String, Object> createDataModelFromMap() {
    
        Map<String, Object> author = new HashMap<>();
    
        author.put("name", "horseLai");
        author.put("age", "100");
        author.put("gender", "男");
    
        Map<String, Object> girlFriend = new HashMap<>();
        girlFriend.put("name", "lalala");
        girlFriend.put("age", "10");
        girlFriend.put("gender", "女"); 
        
        author.put("girlFriend", girlFriend); 
        return author;
    }
    
  • 創建模板,後綴名爲.ftl,比方說我們這裏創建一個名爲Author.ftl的模板,對應上面的兩種模板數據,關於建立模板的更多規則可以查閱手冊

    • 對應於Java bean形式,需要先申明一下變量,然後就可以引用了,這裏可以類比一下DataBinding的使用方式;
    <#-- @ftlvariable name="author" type="Main.Author" -->
    
    大家好,我是${author.name},性別${author.gender},今年${author.age}歲.
    
    <#if author.girlFriend.name != "null">
    這是我姑娘${author.girlFriend.name},性別${author.girlFriend.gender},今年${author.girlFriend.age}歲.
    </#if> 
    
    • 對應於Map形式,則是這樣的。
    大家好,我是${name},性別${gender},今年${age}歲.
    
    <#if girlFriend.name != "null">
    這是我姑娘${girlFriend.name},性別${girlFriend.gender},今年${girlFriend.age}歲.
    </#if>  
    
  • 最後就是讓引擎執行起來

public static void go(){

    try (OutputStreamWriter writer = new OutputStreamWriter(System.out);){
        // 加載模板
        Template authorTemplate = sConfiguration.getTemplate("Author.ftl");
        // 執行模板引擎
        authorTemplate.process(createDataModelFromMap(), writer); 
    } catch (IOException e) {
        e.printStackTrace();
    } catch (TemplateException e) {
        e.printStackTrace();
    }
}
  • 輸出結果:
大家好,我是horseLai,性別男,今年100歲.

這是我姑娘lalala,性別女,今年10歲. 
  • 以上便是FreeMark模板引擎的簡單入門,具體更多特性還請自行查看文檔,下面附上greenDaodao-session.ftl的模板代碼,有助於我們在理解greenDao代碼生成原理的同時學習如何製作模板:
package ${schema.defaultJavaPackageDao}; 
import java.util.Map; 
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.AbstractDaoSession;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.identityscope.IdentityScopeType;
import org.greenrobot.greendao.internal.DaoConfig;

<#list schema.entities as entity>
import ${entity.javaPackage}.${entity.className};
</#list>

<#list schema.entities as entity>
import ${entity.javaPackageDao}.${entity.classNameDao};
</#list>

// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.

/**
 * {@inheritDoc}
 * 
 * @see org.greenrobot.greendao.AbstractDaoSession
 */
public class ${schema.prefix}DaoSession extends AbstractDaoSession {

<#list schema.entities as entity>
    private final DaoConfig ${entity.classNameDao?uncap_first}Config;
</#list>        

<#list schema.entities as entity>
    private final ${entity.classNameDao} ${entity.classNameDao?uncap_first};
</#list>        

    public ${schema.prefix}DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
            daoConfigMap) {
        super(db);

<#list schema.entities as entity>
        ${entity.classNameDao?uncap_first}Config = daoConfigMap.get(${entity.classNameDao}.class).clone();
        ${entity.classNameDao?uncap_first}Config.initIdentityScope(type);

</#list>        
<#list schema.entities as entity>
        ${entity.classNameDao?uncap_first} = new ${entity.classNameDao}<#--
-->(${entity.classNameDao?uncap_first}Config, this);
</#list>        

<#list schema.entities as entity>
        registerDao(${entity.className}.class, ${entity.classNameDao?uncap_first});
</#list>        
    }
    
    public void clear() {
<#list schema.entities as entity>
        ${entity.classNameDao?uncap_first}Config.clearIdentityScope();
</#list>    
    }

<#list schema.entities as entity>
    public ${entity.classNameDao} get${entity.classNameDao?cap_first}() {
        return ${entity.classNameDao?uncap_first};
    } 
</#list>        
} 

綜述

  • 通過以上分析,我們已經瞭解了greenDao生成代碼的方式,對於greenDao而言,代碼生成原理即事先對DaoMasterDaoSessionXxxDao等建立好模板,然後藉助於FreeMark模板引擎,修改模板中設定好的變量即可達到指定的效果。
  • 至此,從“基本使用”“核心類理解”,再到本文,對greenDao分析就結束了,水平有限,歡迎指正。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章