今天看啥  ›  专栏  ›  horseLai

从使用到源码—GreenDao(代码生成即完结篇)

horseLai  · 掘金  ·  · 2019-08-21 17:52
阅读 4

从使用到源码—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分析就结束了,水平有限,欢迎指正。



原文地址:访问原文地址
快照地址: 访问文章快照