引言
-
在“基本使用”一文中介绍过
greenDao
的两种代码生成方式,一种是在gradle
配置一下,然后编译期间就会在指定文件夹中生成DaoMaster
、Dao
、Session
等.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
等。起初我去定位Configuration
和Template
,发现没法直接定位,于是查看它的的包归属,发现它是来自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
模板引擎的简单入门,具体更多特性还请自行查看文档,下面附上greenDao
中dao-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>
}
综述
网友评论