前言風(fēng)油精的電y電妙用(1):在電風(fēng)扇的葉子上灑上幾滴風(fēng)油精,隨著風(fēng)葉的腦配腦配不停轉(zhuǎn)動(dòng),可使?jié)M室清香,置生置生而且有驅(qū)趕蚊子的成器成器效用。不知各位看官在工作之中有沒有陷入過瘋狂CV代碼、京東看著密密麻
前言
風(fēng)油精的電y電妙用(1):在電風(fēng)扇的葉子上灑上幾滴風(fēng)油精,隨著風(fēng)葉的腦配腦配不停轉(zhuǎn)動(dòng),可使?jié)M室清香,置生置生而且有驅(qū)趕蚊子的成器成器效用。
不知各位看官在工作之中有沒有陷入過瘋狂CV代碼、京東看著密密麻麻的電y電類不想動(dòng)手,或者把大把的腦配腦配時(shí)間花費(fèi)在底層的情況。以筆者為例,置生置生會(huì)經(jīng)常遇到以下兩個(gè)問題:
- 隔一段時(shí)間就需要構(gòu)建一個(gè)新應(yīng)用,成器成器需要各種復(fù)制粘貼(缺乏定制化的京東腳手架)
- 新需求一堆的Entity、Bean、Request、Response、DTO、Dao、Service、Business需要寫,看著都不想動(dòng)手
很多時(shí)候甚至?xí)趶?fù)制粘貼代碼時(shí)漏掉一些關(guān)鍵的注解,比如:@Service,導(dǎo)致項(xiàng)目無法啟動(dòng),再花費(fèi)更多的精力去排查。因此本文將以實(shí)際工程代碼為例,來構(gòu)建一個(gè)可定制化,支持高度擴(kuò)展的代碼生成器。
項(xiàng)目目標(biāo)
本項(xiàng)目將基于生成器項(xiàng)目生成一個(gè)可直接運(yùn)行/可方便一鍵復(fù)制的SpringBoot成品項(xiàng)目,其中包括基本的數(shù)據(jù)庫操作、業(yè)務(wù)操作及Web接口、視圖層。
同時(shí)支持拔插式自定義實(shí)現(xiàn),具備較高的拓展性,以下是項(xiàng)目基本結(jié)構(gòu):
Code-Generate├── pom.xml├── src│ ├── main│ │ ├── java│ │ │ └── com│ │ │ └── mysql│ │ │ ├── App.java// 程序入口│ │ │ ├── bean│ │ │ │ ├── ClassInfo.java// 類實(shí)體(對(duì)應(yīng)表維度)│ │ │ │ ├── ConfigurationInfo.java// 配置中心│ │ │ │ ├── FieldInfo.java// 字段實(shí)體(對(duì)應(yīng)表字段維度)│ │ │ │ └── GlobleConfig.java// 全局配置│ │ │ ├── engine│ │ │ │ ├── AbstractEngine.java// 抽象引擎│ │ │ │ ├── GeneralEngine.java// 接口引擎│ │ │ │ └──impl│ │ │ │ ├── CustomEngineImpl.java// 自定義引擎(拔插式基類)│ │ │ │ └── DefaultEngine.java// 默認(rèn)引擎│ │ │ ├── factory│ │ │ │ ├── ClassInfoFactory.java// 類工廠(非必要,可融進(jìn)上述配置中心)│ │ │ │ └── PropertiesFactory.java// 配置文件工廠(非必要,可融進(jìn)上述配置中心)│ │ │ ├── intercept│ │ │ │ ├── CustomEngine.java│ │ │ │ └──impl│ │ │ │ ├── DataMdImpl.java// 自定義引擎案例一(數(shù)據(jù)庫文檔)│ │ │ │ └── LayUiHtmlImpl.java// 自定義引擎案例二(視圖界面)│ │ │ └── util│ │ │ ├── DataBaseUtil.java// 數(shù)據(jù)庫依賴│ │ │ ├── DBUtil.java// 數(shù)據(jù)庫操作類│ │ │ ├── IOTools.java// 工具類│ │ │ └── StringUtil.java// 工具類│ │ ├── resources│ │ │ ├── application.properties// 配置文件│ │ │ ├── log4j2.xml// 日志配置│ │ │ └── templates// 模板目錄│ │ └── test│ └── META-INF └── MANIFEST.MF// META-INF文件,為了打成Jar包使用(非必須)復(fù)制代碼
編碼
構(gòu)建配置中心
由于本項(xiàng)目涉及數(shù)據(jù)庫操作層等,因此除了目標(biāo)目錄,項(xiàng)目名,作者,根目錄等基本參數(shù)外,還需要數(shù)據(jù)庫相關(guān)配置等,配置文件如下:
數(shù)據(jù)庫IP, 數(shù)據(jù)庫Driver, 編碼, 用戶名, 密碼ip=127.0.0.1port=3306driver=com.mysql.jdbc.DriverdataBase=school-miaoencoding=UTF-8loginName=rootpassWord=需要構(gòu)建的表名 為* 默認(rèn)包含全部, 以;號(hào)隔離比如: a;b;c;d;include=*;項(xiàng)目名projectName=Demo包名packageName=com.demo作者authorName=Kerwin項(xiàng)目輸出根目錄rootPath=F:\\code自定義Handle包含項(xiàng), 現(xiàn)有自定義模塊:DataMdImpl,LayUiHtmlImpl, * 號(hào)默認(rèn)包含所有,以;號(hào)隔離比如: a;b;c;d;customHandleInclude=DataMdImpl;LayUiHtmlImpl;復(fù)制代碼
考慮好基礎(chǔ)的配置內(nèi)容后,通過讀取文件配置,將相關(guān)信息置入配置中心即可,具體代碼便不再展示,需要的直接觀看源碼即可(文末鏈接)。
本階段涉及的類:ConfigurationInfo.java、GlobleConfig.java、PropertiesFactory。
入口:com.mysql.engine.AbstractEngineinit
基于數(shù)據(jù)庫獲取表字段信息
上文已獲取到目標(biāo)數(shù)據(jù)庫的配置信息,本階段即可連接數(shù)據(jù)庫,通過通用SQL獲取目標(biāo)庫的表、字段信息。
以下面的SQL為例,只需獲取數(shù)據(jù)庫連接加上庫信息,即可獲取所有的表名
SELECTtable_nameFROMinformation_schema.TABLESWHEREtable_schema ="school-miao-demo"庫名ANDtable_type ="base table";響應(yīng)schools復(fù)制代碼
同理,通過通用SQL也可以獲取到指定數(shù)據(jù)表的所有字段及其類型:
SELECTcolumn_name, data_type, column_comment, numeric_precision, numeric_scale, character_maximum_length, is_nullable nullableFROMinformation_schema.COLUMNSWHEREtable_name =schools表明ANDtable_schema =school-miao-demo;庫名結(jié)果為column_name data_typesname varchar復(fù)制代碼
得到表字段的類型后存儲(chǔ)至配置中心即可,其中的關(guān)鍵一點(diǎn)在于需要注意數(shù)據(jù)類型的映射,比如varchar映射String,int映射Integer等等,同時(shí)把表字段通過字符串處理工具類轉(zhuǎn)化為駝峰類型(固定類型)的展示方式,例如:s_id => sid,這一點(diǎn)上需要注意數(shù)據(jù)庫字段的設(shè)計(jì)規(guī)范。
基于模板生成文件
本項(xiàng)目中最關(guān)鍵的一點(diǎn)即在于此,如果想要實(shí)現(xiàn)可配置化代碼生成器,一定有一個(gè)前提即:配置本身,回憶我們?cè)诙嗄昵俺鯇W(xué)JSP的時(shí)候,有沒有覺得JSTL表達(dá)式還蠻神奇的,它可以將Java語言和HTML語言完美的混合在一起,雖然現(xiàn)在我們已不再使用它,但是這種模板化的思想和工作方式,恰好可以用在此處。
通過調(diào)研發(fā)現(xiàn),在類似JSP的技術(shù)中,F(xiàn)reeMarker完美符合我們的預(yù)期,其中它的:
freemarker.template.Templateprocess(java.lang.Object, java.io.Writer)
方法,可以通過指定模板文件(FTL)、Java實(shí)體、目標(biāo)文件的方式,來幫助我們實(shí)現(xiàn)內(nèi)容的填充,使用方法類似JSTL,如下:
package ${packageName}.entity;importjava.io.Serializable;importlombok.Data;importjava.util.Date;importjava.util.List;/** * ${classInfo.classComment} * @author ${authorName} ${.now?string(yyyy-MM-dd)} */@Datapublicclass${classInfo.className} implementsSerializable{privatestaticfinallong serialVersionUID = 1L; <ifclassInfo.fieldList?exists && classInfo.fieldList?size gt0>