本文我們將分析一下rapid-generate源碼,看看其設計思路以及運行原理究竟是怎樣的。
我們從Test類的main函數入口說起
public static void main(String[]args)throws Exception{
GeneratorFacade g = new GeneratorFacade();
//刪除生成器的輸出目錄//
g.deleteOutRootDir();
//自動搜索數據庫中的所有表並生成文件,template爲模板的根目錄
g.generateByAllTable("template");
}
GeneratorFacade 類從命名上就能看出,運用了門面設計模式,該類作爲生成器功能的入口,統一組織管理各個模塊以及功能。
g.generateByAllTable(“template”);這一句實際上是調用GeneratorFacade 內部類ProcessUtils的processByAllTable方法。
public void generateByAllTable(String templateRootDir) throws Exception {
new ProcessUtils().processByAllTable(templateRootDir,false);
}
進一步分析processByAllTable()方法
public void processByAllTable(String templateRootDir,boolean isDelete) throws Exception {
//該方法通過讀取數據庫的metaData,獲所有數據庫表信息,並在內存中生成對應的Table對象。主要應用底層接口java.sql.DatabaseMetaData
List<Table> tables = TableFactory.getInstance().getAllTables();
List exceptions = new ArrayList();
for(int i = 0; i < tables.size(); i++ ) {
try {
processByTable(getGenerator(templateRootDir),tables.get(i),isDelete);
}catch(GeneratorException ge) {
exceptions.addAll(ge.getExceptions());
}
}
PrintUtils.printExceptionsSumary("",getGenerator(templateRootDir).getOutRootDir(),exceptions);
}
得到list之後,接下來遍歷該集合,調用processByTable()方法
public void processByTable(Generator g, Table table,boolean isDelete) throws Exception {
//生成GeneratorModel 對象,包含filePathModel和templateModel的兩個Map集合,爲下一步開始生成文件做準備
GeneratorModel m = GeneratorModelUtils.newFromTable(table);
PrintUtils.printBeginProcess(table.getSqlName()+" => "+table.getClassName(),isDelete);
if(isDelete)
g.deleteBy(m.templateModel,m.filePathModel);
else
g.generateBy(m.templateModel,m.filePathModel);
}
讀取templateModel和filePathModel兩個Map集合作爲generateBy函數的參數
/**
* 生成文件
* @param templateModel 生成器模板可以引用的變量
* @param filePathModel 文件路徑可以引用的變量
* @throws Exception
*/
public Generator generateBy(Map templateModel,Map filePathModel) throws Exception {
processTemplateRootDirs(templateModel, filePathModel,false);
return this;
}
templateRootDirs是List集合,其中只有一個元素,[F:\2013-2014\Eclipse-J2EE\workspace\Rapid-Generator-Pro\template],該路徑正是java項目中template目錄所在路徑
接着分析processTemplateRootDirs函數,由於list集合size位1,此處之循環1次,重點在scanTemplatesAndProcess函數。
private void processTemplateRootDirs(Map templateModel,Map filePathModel,boolean isDelete) throws Exception {
if(StringHelper.isBlank(getOutRootDir())) throw new IllegalStateException("'outRootDir' property must be not null.");
if(templateRootDirs.size() == 0) throw new IllegalStateException("'templateRootDirs' cannot empty");
GeneratorException ge = new GeneratorException("generator occer error, Generator BeanInfo:"+BeanHelper.describe(this));
for(int i = 0; i < this.templateRootDirs.size(); i++) {
File templateRootDir = (File)templateRootDirs.get(i);
List<Exception> exceptions = scanTemplatesAndProcess(templateRootDir,templateModel,filePathModel,isDelete);
ge.addAll(exceptions);
}
if(!ge.exceptions.isEmpty()) throw ge;
}
scanTemplatesAndProcess函數接收template所在目錄的路徑以及templateModel和filePathModel兩個Map參數
private List<Exception> scanTemplatesAndProcess(File templateRootDir, 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());
//獲取模板根目錄下所有要生成的文件模板路徑
List srcFiles = FileHelper.searchAllNotIgnoreFile(templateRootDir);
List<Exception> exceptions = new ArrayList();
for(int i = 0; i < srcFiles.size(); i++) {
File srcFile = (File)srcFiles.get(i);
try {
if(isDelete){
new TemplateProcessor().executeDelete(templateRootDir, templateModel,filePathModel, srcFile);
}else {
new TemplateProcessor().executeGenerate(templateRootDir, templateModel,filePathModel, srcFile);
}
}catch(Exception e) {
if (ignoreTemplateGenerateException) {
GLogger.warn("iggnore generate error,template is:" + srcFile+" cause:"+e);
exceptions.add(e);
} else {
throw e;
}
}
}
return exceptions;
}
分析最終生成的核心代碼,調用底層的freemarker,將模板轉換爲對應源碼文件輸出到指定目錄下面。
private void executeGenerate(File templateRootDir,Map templateModel, Map filePathModel ,File srcFile) throws SQLException, IOException,TemplateException {
String templateFile = FileHelper.getRelativePath(templateRootDir, srcFile);
//忽略目錄,以及非模板的文件 if(GeneratorHelper.isIgnoreTemplateProcess(srcFile, templateFile,includes,excludes)) {
return;
}
if(isCopyBinaryFile && FileHelper.isBinaryFile(srcFile)) {
String outputFilepath = proceeForOutputFilepath(filePathModel, templateFile);
GLogger.println("[copy binary file by extention] from:"+srcFile+" => "+new File(getOutRootDir(),outputFilepath));
IOHelper.copyAndClose(new FileInputStream(srcFile), new FileOutputStream(new File(getOutRootDir(),outputFilepath)));
return;
}
String outputFilepath = null;
try {
outputFilepath = proceeForOutputFilepath(filePathModel,templateFile);
initGeneratorControlProperties(srcFile);
processTemplateForGeneratorControl(templateModel, templateFile);
if(gg.isIgnoreOutput()) {
GLogger.println("[not generate] by gg.isIgnoreOutput()=true on template:"+templateFile);
return;
}
if(outputFilepath != null ) {
generateNewFileOrInsertIntoFile(templateFile,outputFilepath, templateModel);
}
}catch(Exception e) {
throw new RuntimeException("generate oucur error,templateFile is:" + templateFile+" => "+ outputFilepath+" cause:"+e, e);
}
}
最後附上在分析時所打的斷點
總結,原理其實很簡單,就是用freemarker語法編寫template模板文件,生成時讀取數據庫信息,動態的將freemarker語法標記替換掉,最終就得到了源碼文件。