引言
在“基本使用”一文中介紹過
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>
}
綜述