- 原理
- rapid-generator
- rapidDemo
- 假設有mysql資料庫和表
- 修改generatorxml檔案發在代碼根目錄就行生成代碼會在classes目錄加載配置檔案主要是
- 模闆檔案
- 生成代碼
- rapid生成的代碼檔案
- 源碼分析
- 常用類
- 加載配置檔案
- 擷取資料庫中繼資料
- 建構輸出模型和處理輸出
- rapidDemo
- 總結
- 一個bug
- 感謝那位哥們提供了這樣的好工具
之前有個項目比較緊急,基本上開發和設計同步進行,是以設計和資料模型基本上每天都會變,每天上班的第一件事就是檢查資料模型的變動,内心一萬頭神獸奔騰,後來忙完想到寫個代碼生成工具,每天過來自己跑下程式,自行檢查,搜尋發現這個開源的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[];
templateLoaders[] = 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() NOT NULL auto_increment,
`name` varchar() default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
修改generator.xml(檔案發在代碼根目錄就行,生成代碼會在classes目錄加載)配置檔案,主要是:
- 資料庫配置;
- basepackage:輸出的包名配置;
-
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 = L;
<#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 = L;
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. 建構輸出模型和處理輸出。
常用類
根據我的實際使用情況,删除了部分源碼,保留主流程。
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 > ) {
String basedir = p.getProperty("basedir");
if(basedir != null && basedir.startsWith(".")) {
p.setProperty("basedir", new File(new File(loadedFiles[]).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[]);
}
配置檔案加載後,後期就可以擷取配置參數,如資料庫的配置,輸出目錄,包名等。
擷取資料庫中繼資料
資料庫中繼資料是在真正處理模闆前才會擷取,不是生成器核心
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() == )
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() == ) 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 = ; 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 = ; 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;
}