本文我们将分析一下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语法标记替换掉,最终就得到了源码文件。