代码生成rapid-generator源码分析

时间:2022-05-21 09:19:21

之前有个项目比较紧急,基本上开发和设计同步进行,所以设计和数据模型基本上每天都会变,每天上班的第一件事就是检查数据模型的变动,内心一万头神兽奔腾,后来忙完想到写个代码生成工具,每天过来自己跑下程序,自行检查,搜索发现这个开源的rapid-generator工具,只要编写模板文件就可以,完全满足需求。看下源码,学习之。

原理

废话没有,就是利用freemarker来实现的,关于freemarker不多说,记住一点就行:输出 = 模板 + 模型,详细的自己搜索学习。
写个freemarker的demo理解下,假如我们要生成这样一个java类:

package com.su.autocode;

/**
* @author:admin
* @version:1.0
* @since:1.0
* @createTime:2016-10-26 09:36:11
*/

pulic class User {
private String username;
private String password;

public User(){}

public User(String username, String password){
this.username = username;
this.password = password;
}

public void setUsername(String username){
this.username = username;
}

public String getUsername(){
return this.username;
}

public void setPassword(String password){
this.password = password;
}

public String getPassword(){
return this.password;
}

}

先抽象成一个demo.ftl模板:

package ${basePackage_dir};

/**
* @author:${author}
* @version:1.0
* @since:1.0
* @createTime:<#if now??>${now?string('yyyy-MM-dd HH:mm:ss')}</#if>
*/

pulic class ${className} {

<#list attrs as attr>
private ${attr.javaType} ${attr.name};
</#list>

public ${className}(){}

<#list attrs as attr>
public void set${attr.name?cap_first}(String ${attr.name}){
this.${attr.name} = ${attr.name};
}

public void get${attr.name?cap_first}(}){
return this.${attr.name};
}
</#list>
}

freemarker生成代码:

public class FreemarkerDemo {

public static void main(String[] args){
Map<String, String> attr1 = new HashMap<String, String>();
attr1.put("javaType", "String");
attr1.put("name", "username");

Map<String, String> attr2 = new HashMap<String, String>();
attr2.put("javaType", "String");
attr2.put("name", "password");

List<Object> attrs = new ArrayList<Object>();
attrs.add(attr1);
attrs.add(attr2);

Map<String,Object> root = new HashMap<String, Object>();
root.put("basePackage_dir", "com.su.autocode");
root.put("author", "admin");
root.put("now", new Date());
root.put("className", "User");
root.put("attrs", attrs);

Configuration cfg = new Configuration();
try {
FileTemplateLoader[] templateLoaders = new FileTemplateLoader[1];
templateLoaders[0] = new FileTemplateLoader(new File("C:\\Users\\chris\\Desktop"));
MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader(templateLoaders);

cfg.setTemplateLoader(multiTemplateLoader);
Template template = cfg.getTemplate("demo.ftl"); //获取模板
StringWriter out = new StringWriter(); //out可以输出到file
template.process(root, out);

System.out.println(out.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (TemplateException e) {
e.printStackTrace();
}
}
}

那位哥们写的代码生成,基本原理也就是这样,一切看起来都是那么美好,也似乎很简单,不过上面的也只是demo,想做好,就要考虑很多细节了。

rapid-generator

这个代码生成我主要是用来对数据模型生成sqlmap、bean、dao、service,因为用到了公司的组件内容,所以重新编写了模板,不过这个框架也的确做的好,基本上只是编写个代码模板,都不用更改框架代码。
我是用maven从公司私服下添加的依赖。需要3个jar包:rapid-generator.jar, freemarker.jar和你数据库的驱动包。
先来个demo看这个框架怎么用。

rapidDemo

假设有mysql数据库和表:

CREATE TABLE `user` (
`id` bigint(20) NOT NULL auto_increment,
`name` varchar(20) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

修改generator.xml(文件发在代码根目录就行,生成代码会在classes目录加载)配置文件,主要是:

  1. 数据库配置;
  2. basepackage:输出的包名配置;
  3. outRoot:输出的文件目录。
    其他字段一般不用更改。

模板文件

假设我们要生成表对应的bean,模板为:

<#assign className = table.className>   
<#assign classNameLower = className?uncap_first>
package ${basepackage}.bean;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.apache.ibatis.type.Alias;

/**
<#if table.remarks?exists && table.remarks != '' && table.remarks != 'null'>
* ${table.remarks}
</#if>
* @author:admin
* @version:1.0
* @since:1.0
* @createTime:<#if now??>${now?string('yyyy-MM-dd HH:mm:ss')}</#if>
*/

@Alias("${classNameLower}")
@Entity(name = "${table.sqlName}")
public class ${className} implements java.io.Serializable{

private static final long serialVersionUID = 1L;

<#list table.columns as column>
<#if column.remarks?exists && column.remarks != '' && column.remarks != 'null'>
/** ${column.remarks} */
</#if>
private ${column.javaType} ${column.columnNameLower};

</#list>
public ${className}(){
}

<#list table.columns as column>
public void set${column.columnName}(${column.javaType} ${column.columnNameLower}) {
this.${column.columnNameLower} = ${column.columnNameLower};
}

<#if column.pk>
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
</#if>
@Column(name = "${column.sqlName}")
public ${column.javaType} get${column.columnName}() {
return this.${column.columnNameLower};
}

</#list>
}

生成代码

public class RapidDemo {

public static void main(String[] args) throws Exception {
GeneratorFacade g = new GeneratorFacade();
/** 代码模板文件根目录 */
g.getGenerator().addTemplateRootDir("D:\\workspaces\\NettyRpc-master\\template");
/** 删除代码生成输出目录,配置在generator.xml的outRoot */
g.deleteOutRootDir();
/** 只尝试了下面2中,rapid-framework还支持根据sql,类生成等方式 */
/** 所有表对应代码 */
// g.generateByAllTable();
/** 指定表对应代码 */
g.generateByTable("user");
}

}

rapid生成的代码文件

package com.su.chris.bean;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.apache.ibatis.type.Alias;

/**
* @author:admin
* @version:1.0
* @since:1.0
* @createTime:2016-10-26 11:16:28
*/

@Alias("user")
@Entity(name = "user")
public class User implements java.io.Serializable{

private static final long serialVersionUID = 1L;

private java.lang.Long id;

private java.lang.String name;

public User(){
}

public void setId(java.lang.Long id) {
this.id = id;
}

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
public java.lang.Long getId() {
return this.id;
}

public void setName(java.lang.String name) {
this.name = name;
}

@Column(name = "name")
public java.lang.String getName() {
return this.name;
}

}

Ok,完全没问题。

源码分析

既然是用freemarker,那么还是之前那就话,输出 = 模板 + 模型。看完这个代码生成的源码,整个流程可以概括为下面几步:
1. 加载generator.xml配置文件;
2. 获取数据库元数据;
3. 构建输出模型和处理输出。

常用类

根据我的实际使用情况,删除了部分源码,保留主流程。

代码生成rapid-generator源码分析

generator包下删除了context类,生成代码不需要context。
1. Generator:代码生成的核心,最终代码生成在这里;
2. GeneratorConstants:常量类,那些参数可以在配置文件在配置,提供了默认值;
3. GeneratorControl:控制生成器的过程中一些动作,有一些参数,如是否容许覆盖,生成目录等;
4. GeneratorFacade:facade模式,入口类;
5. GeneratorProperties:处理配置文件的加载,有参数PropertiesHelper,大多数方法通过委托持有的PropertiesHelper实现。这里我觉得有点设计不好,因为PropertiesHelper有点像工具类,但是又持有真正的Properties,不如Properties持有GeneratorProperties,把PropertiesHelper处理成真正的工具类;
6. DataSourceProvider:主要是通过配置文件获取数据源,支持jndi和常用的链接配置;
7. TableFactory:获取数据库元数据,创建成Table对象;
8. Table、Column等:就是对应数据库的表和列的一些属性;
9. 其他的就是一些工具类;

加载配置文件

GeneratorFacade g = new GeneratorFacade();
GeneratorFacade:
private Generator generator = new Generator();
Generator:
private String removeExtensions = GeneratorProperties.getProperty(GENERATOR_REMOVE_EXTENSIONS);

触发:

GeneratorProperties:
/** 配置文件名称,看样子作者是支持2种格式的 */
static final String PROPERTIES_FILE_NAMES[] = new String[]{"generator.properties","generator.xml"};

static PropertiesHelper propertiesHelper;

static {
reload();
}

这里我改了下,原本propertiesHelper这里会直接实例化下,但是我觉得跟下面的static重复,所以删除了,用static代码块直接加载配置文件数据。

public static void reload() {
try {
GLogger.println("Start Load GeneratorPropeties from classpath:"+Arrays.toString(PROPERTIES_FILE_NAMES));
Properties p = new Properties();
/** 这里通过PropertiesHelper加载配置文件 */
String[] loadedFiles = PropertiesHelper.loadAllPropertiesFromClassLoader(p,PROPERTIES_FILE_NAMES);
GLogger.println("GeneratorPropeties Load Success,files:"+Arrays.toString(loadedFiles));

setSepicalProperties(p, loadedFiles);

setProperties(p);
}catch(IOException e) {
throw new RuntimeException("Load "+PROPERTIES_FILE_NAMES+" error",e);
}
}

/** 处理了个特殊目录,搜索代码没看见什么地方用,估计是作者其他框架用吧 */
private static void setSepicalProperties(Properties p, String[] loadedFiles) {
if(loadedFiles != null && loadedFiles.length > 0) {
String basedir = p.getProperty("basedir");
if(basedir != null && basedir.startsWith(".")) {
p.setProperty("basedir", new File(new File(loadedFiles[0]).getParent(),basedir).getAbsolutePath());
}
}
}

/** 这里就加载的配置文件让propertiesHelper来持有 */
public static void setProperties(Properties inputProps) {
propertiesHelper = new PropertiesHelper(inputProps,true);
for(Iterator it = propertiesHelper.entrySet().iterator();it.hasNext();) {
Map.Entry entry = (Map.Entry)it.next();
GLogger.debug("[Property] "+entry.getKey()+"="+entry.getValue());
}
GLogger.println("");
}

看下PropertiesHelper.loadAllPropertiesFromClassLoader():

public static String[] loadAllPropertiesFromClassLoader(Properties properties,String... resourceNames) throws IOException {
List successLoadProperties = new ArrayList();
for(String resourceName : resourceNames) {
/** 从classes目录获取配置文件路径 */
Enumeration urls = ClassHelper.getDefaultClassLoader().getResources(resourceName);
while (urls.hasMoreElements()) {
URL url = (URL) urls.nextElement();
successLoadProperties.add(url.getFile());
InputStream input = null;
try {
URLConnection con = url.openConnection();
con.setUseCaches(false);
input = con.getInputStream();
/** 这里判断格式,然后加载配置文件 */
if(resourceName.endsWith(".xml")){
properties.loadFromXML(input);
}else {
properties.load(input);
}
}
finally {
if (input != null) {
input.close();
}
}
}
}
return (String[])successLoadProperties.toArray(new String[0]);
}

配置文件加载后,后期就可以获取配置参数,如数据库的配置,输出目录,包名等。

获取数据库元数据

数据库元数据是在真正处理模板前才会获取,不是生成器核心Generator generator = new Generator();实例化的时候就提前处理完。
以上面的demo为例:

/** 指定表对应代码 */
g.generateByTable("user");
GeneratorFacade:
public void generateByTable(String... tableNames) throws Exception {
for(String tableName : tableNames) {
/** 如果有多个表名,循环处理 */
new ProcessUtils().processByTable(tableName,false);
}
}

内部类ProcessUtils处理:
public void processByTable(String tableName,boolean isDelete) throws Exception {
if("*".equals(tableName)) {
if(isDelete)
deleteByAllTable();
else
generateByAllTable();
return;
}
Generator g = getGenerator();
/** 获取数据库表Table元数据 */
Table table = TableFactory.getInstance().getTable(tableName);
try {
/** 根据table信息生成 */
processByTable(g,table,isDelete);
}catch(GeneratorException ge) {
PrintUtils.printExceptionsSumary(ge.getMessage(),getGenerator().getOutRootDir(),ge.getExceptions());
throw ge;
}
}

转到TableFactory,真正处理数据库元数据的地方:

/** 单例获取实例化类,注意有的数据库有schema和catalog,有的话需要在配置文件中配置 */
public synchronized static TableFactory getInstance() {
if(instance == null) instance = new TableFactory(GeneratorProperties.getNullIfBlank(GeneratorConstants.JDBC_SCHEMA),GeneratorProperties.getNullIfBlank(GeneratorConstants.JDBC_CATALOG));
return instance;
}

public Table getTable(String tableName) {
return getTable(getSchema(),tableName);
}

private Table getTable(String schema,String tableName) {
return getTable(getCatalog(),schema,tableName);
}

private Table getTable(String catalog,String schema,String tableName) {
Table t = null;
try {
t = _getTable(catalog,schema,tableName);
/** 上面是根据你给的表名获取,获取不到就转换大小写重新获取下 */
if(t == null && !tableName.equals(tableName.toUpperCase())) {
t = _getTable(catalog,schema,tableName.toUpperCase());
}
if(t == null && !tableName.equals(tableName.toLowerCase())) {
t = _getTable(catalog,schema,tableName.toLowerCase());
}
}catch(Exception e) {
throw new RuntimeException(e);
}
if(t == null) {
Connection conn = DataSourceProvider.getConnection();
try {
throw new NotFoundTableException("not found table with give name:"+tableName+ (DatabaseMetaDataUtils.isOracleDataBase(DatabaseMetaDataUtils.getMetaData(conn)) ? " \n databaseStructureInfo:"+DatabaseMetaDataUtils.getDatabaseStructureInfo(DatabaseMetaDataUtils.getMetaData(conn),schema,catalog) : "")+"\n current "+DataSourceProvider.getDataSource()+" current schema:"+getSchema()+" current catalog:"+getCatalog());
}finally {
DBHelper.close(conn);
}
}
return t;
}

/** 真正获取的地方 */
private Table _getTable(String catalog,String schema,String tableName) throws SQLException {
if(tableName== null || tableName.trim().length() == 0)
throw new IllegalArgumentException("tableName must be not empty");
catalog = StringHelper.defaultIfEmpty(catalog, null);
schema = StringHelper.defaultIfEmpty(schema, null);

/** DataSourceProvider获取连接,很简单,也支持jndi方式 */
Connection conn = DataSourceProvider.getConnection();
DatabaseMetaData dbMetaData = conn.getMetaData();
ResultSet rs = dbMetaData.getTables(catalog, schema, tableName, null);
try {
while(rs.next()) {
/** 内部类TableCreateProcessor创建Table,主要是构建table、column结构和获取元数据,跟着看就行 */
Table table = new TableCreateProcessor(conn,getSchema(),getCatalog()).createTable(rs);
return table;
}
}finally {
DBHelper.close(conn,rs);
}
return null;
}

OK,到这里我们的数据模型的元数据就有,下面就是构建输出模型和处理输出

构建输出模型和处理输出

以demo为例,上面代码到了:

GeneratorFacade内部类ProcessUtils:
/** 获取数据库表Table元数据 */
Table table = TableFactory.getInstance().getTable(tableName);
try {
/** 根据table信息生成 */
processByTable(g,table,isDelete);
}catch(GeneratorException ge) {
PrintUtils.printExceptionsSumary(ge.getMessage(),getGenerator().getOutRootDir(),ge.getExceptions());
throw ge;
}

public void processByTable(Generator g, Table table,boolean isDelete) throws Exception {
/** 构建输出模型 */
GeneratorModel m = GeneratorModelUtils.newGeneratorModel("table",table);
PrintUtils.printBeginProcess(table.getSqlName()+" => "+table.getClassName(),isDelete);
if(isDelete)
g.deleteBy(m.templateModel,m.filePathModel); //删除
else
g.generateBy(m.templateModel,m.filePathModel); //处理输出
}

构建模型:

/** 构建输出模型GeneratorModel,这个类持有2个变量:
*用于存放'模板'可以引用的变量templateModel,
*用于存放'文件路径'可以引用的变量 filePathModel,这个变量主要是路径也可以用freemarker来配置变量
*/

public static GeneratorModel newGeneratorModel(String key,Object valueObject) {
GeneratorModel gm = newDefaultGeneratorModel();
gm.templateModel.put(key, valueObject); //这里讲table加入,挺好的处理,key是不同的入参
gm.filePathModel.putAll(BeanHelper.describe(valueObject));
return gm;
}

public static GeneratorModel newDefaultGeneratorModel() {
Map templateModel = new HashMap();
templateModel.putAll(getShareVars()); //模型加入共享数据

Map filePathModel = new HashMap();
filePathModel.putAll(getShareVars()); //路径加入共享数据
return new GeneratorModel(templateModel,filePathModel);
}

/** 加入一些共享数据 */
public static Map getShareVars() {
Map templateModel = new HashMap();
/** GeneratorProperties增加方法替换所有点号为下划线 */
templateModel.putAll(GeneratorProperties.resolveKeyPlaceholder(System.getProperties())); //系统参数
templateModel.putAll(GeneratorProperties.getProperties()); //配置文件中参数
templateModel.put("env", System.getenv()); //环境变量
templateModel.put("now", new Date());
templateModel.put(GeneratorConstants.DATABASE_TYPE.code, GeneratorProperties.getDatabaseType(GeneratorConstants.DATABASE_TYPE.code));
templateModel.putAll(getToolsMap()); //模板中可以使用的工具类,这个我没试过,freemarker一般的处理个人感觉基本够用,就没管这个
return templateModel;
}

现在输出模型有了,接下来就是输出文件了:

Generator:
/**
* 生成文件
* @param templateModel 生成器模板可以引用的变量
* @param filePathModel 文件路径可以引用的变量
* @throws Exception
*/

public Generator generateBy(Map templateModel,Map filePathModel) throws Exception {
processTemplateRootDirs(templateModel, filePathModel,false);
return this;
}

@SuppressWarnings("unchecked")
private void processTemplateRootDirs(Map templateModel,Map filePathModel,boolean isDelete) throws Exception {
if(StringHelper.isBlank(getOutRootDir())) throw new IllegalStateException("'outRootDir' property must be not empty.");
if(templateRootDirs == null || templateRootDirs.size() == 0) throw new IllegalStateException("'templateRootDirs' must be not empty");

GLogger.debug("******* Template reference variables *********",templateModel);
GLogger.debug("\n\n******* FilePath reference variables *********",filePathModel);

//生成 路径值,如 pkg=com.company.project 将生成 pkg_dir=com/company/project的值
/** 这里是将所有key,再额外生成一个key_dir的数据,感觉没必要,不如直接配置文件限制一些可以配置路径 */
templateModel.putAll(GeneratorHelper.getDirValuesMap(templateModel));
filePathModel.putAll(GeneratorHelper.getDirValuesMap(filePathModel));

GeneratorException ge = new GeneratorException("generator occer error, Generator BeanInfo:"+BeanHelper.describe(this));
List<File> processedTemplateRootDirs = processTemplateRootDirs(); //模板根路径

for(int i = 0; i < processedTemplateRootDirs.size(); i++) {
File templateRootDir = (File)processedTemplateRootDirs.get(i);
/** 扫描根目录下所有模板文件并处理 */
List<Exception> exceptions = scanTemplatesAndProcess(templateRootDir,processedTemplateRootDirs,templateModel,filePathModel,isDelete);
ge.addAll(exceptions);
}
if(!ge.exceptions.isEmpty()) throw ge;
}

/**
* 用于子类覆盖,预处理模板目录,如执行文件解压动作
* 这里本来还处理了一种场景,就是模板压缩的情况,有个解压的过程,我直接删掉了,太费事,不如简单点,所以我直接返回模板根目录
**/

protected List<File> processTemplateRootDirs() throws Exception {
return templateRootDirs;
}

/**
* 搜索templateRootDir目录下的所有文件并生成东西
* @param templateRootDir 用于搜索的模板目录
* @param templateRootDirs freemarker用于装载模板的目录
*/

private List<Exception> scanTemplatesAndProcess(File templateRootDir,List<File> templateRootDirs,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());
/** 获取模板根目录下所有模板文件,忽略一些不需要的文件 ,入svn文件等,可配*/
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(templateRootDirs).executeDelete(templateRootDir, templateModel,filePathModel, srcFile); //删除
}else {
long start = System.currentTimeMillis();
new TemplateProcessor(templateRootDirs).executeGenerate(templateRootDir, templateModel,filePathModel, srcFile); //创建
GLogger.perf("genereate by tempate cost time:"+(System.currentTimeMillis() - start)+"ms");
}
}catch(Exception e) {
if (ignoreTemplateGenerateException) {
GLogger.warn("iggnore generate error,template is:" + srcFile+" cause:"+e);
exceptions.add(e);
} else {
throw e;
}
}
}
return exceptions;
}

把最后创建的语句提出来:

Generator:
new TemplateProcessor(templateRootDirs).executeGenerate(templateRootDir, templateModel,filePathModel, srcFile); //创建

Generator内部类TemplateProcessor:
private void executeGenerate(File templateRootDir,Map templateModel, Map filePathModel ,File srcFile) throws SQLException, IOException,TemplateException {
String templateFile = FileHelper.getRelativePath(templateRootDir, srcFile);
/** 配置文件可以配置哪些模板文件需要处理,哪些些不需要,这里是check下 */
if(GeneratorHelper.isIgnoreTemplateProcess(srcFile, templateFile,includes,excludes)) {
return;
}
/** 这里是处理一些二进制文件,直接copy过去 */
if(isCopyBinaryFile && FileHelper.isBinaryFile(srcFile)) {
String outputFilepath = proceeForOutputFilepath(filePathModel, templateFile);
File outputFile = new File(getOutRootDir(),outputFilepath);
GLogger.println("[copy binary file by extention] from:"+srcFile+" => "+outputFile);
FileHelper.parentMkdir(outputFile);
IOHelper.copyAndClose(new FileInputStream(srcFile), new FileOutputStream(outputFile));
return;
}

try {
/** 处理文件路径的变量变成输出路径,假如路径为${basepackage_dir^cap_first}/sit 就需要处理成真正的路径
* 注意路径配置如果?这个符号要转为^
*/

String outputFilepath = proceeForOutputFilepath(filePathModel,templateFile);
/** 就是生成过程中的一些配置 */
initGeneratorControlProperties(srcFile,outputFilepath);
processTemplateForGeneratorControl(templateModel, templateFile); //处理模板,会有freemarker的configuration设置

if(gg.isIgnoreOutput()) {
GLogger.println("[not generate] by gg.isIgnoreOutput()=true on template:"+templateFile);
return;
}

if(StringHelper.isNotBlank(gg.getOutputFile())) {
generateNewFileOrInsertIntoFile(templateFile,gg.getOutputFile(), templateModel); //生成文件
}
}catch(Exception e) {
throw new RuntimeException("generate oucur error,templateFile is:" + templateFile+" => "+ gg.getOutputFile()+" cause:"+e, e);
}
}

/** 生成过程中的一些配置 */
private void initGeneratorControlProperties(File srcFile,String outputFile) throws SQLException {
gg.setSourceFile(srcFile.getAbsolutePath());
gg.setSourceFileName(srcFile.getName());
gg.setSourceDir(srcFile.getParent());
gg.setOutRoot(getOutRootDir());
gg.setOutputEncoding(outputEncoding);
gg.setSourceEncoding(sourceEncoding);
gg.setMergeLocation(GENERATOR_INSERT_LOCATION);
gg.setOutputFile(outputFile);
}

private void processTemplateForGeneratorControl(Map templateModel,String templateFile) throws IOException, TemplateException {
templateModel.put("gg", gg);
Template template = getFreeMarkerTemplate(templateFile); //获取模板,会有freemarker的configuration设置
template.process(templateModel, IOHelper.NULL_WRITER); //这里没搞懂为什么process下
}

有些代码没贴出来,跟着看就是。OK,生成结束,facade里面还有其他一些方法,还好,看完这个应该再去看应该没什么问题。

总结

一个bug

如果你在模板中想获取系统变量的值,会出错。例如,你的javadoc里面,你生成的时候想说明是谁生成的,用系统变量user.name,模板里面你写成author:${user.name},那么就出错。
这是因为freemarker对user.name的解析有问题,freemarker不是把user.name整体作为一个key,认为user.name的点号存在下一层关系,所以出错。
后来把框架这里的处理代码改了把点号全部替换为下划线,然后模板取user_name就没问题了。作者本人在配置文件全部用下划线,不知道为什么这里不改。
GeneratorFacade.java代码所在:

public static Map getShareVars() {
Map templateModel = new HashMap();
/** 原代码
templateModel.putAll(System.getProperties());
*/

/** GeneratorProperties增加方法替换所有点号为下划线 */
templateModel.putAll(GeneratorProperties.resolveKeyPlaceholder(System.getProperties()));
templateModel.putAll(GeneratorProperties.getProperties());
templateModel.put("env", System.getenv());
templateModel.put("now", new Date());
templateModel.put(GeneratorConstants.DATABASE_TYPE.code, GeneratorProperties.getDatabaseType(GeneratorConstants.DATABASE_TYPE.code));
templateModel.putAll(getToolsMap());
return templateModel;
}

感谢那位哥们提供了这样的好工具