當前位置:
首頁 > 知識 > 「快速入門」MyBatis Generator源碼分析修改和自定義插件

「快速入門」MyBatis Generator源碼分析修改和自定義插件

快速開始

環境配置

生成代碼

源碼修改

修改配置文件

加入解析器

修改DTD校驗文件

新建解析器

獲取表數據

生成文件基礎信息

生成資源生成器

保存資源文件信息

自定義插件

新建插件

應用插件

執行

MyBatis Generator 能快速代碼生成工具,儘管已經提供了大量的配置標籤,但是每個公司都有自己的代碼規範,這個時候,別人提供的不合適,那就只能自己上手了,畢竟適合自己的才是最順手的。

可能是CSDN問題,複製的xml粘貼到網頁里後,每個標籤自動閉合了,結尾自動帶了」/」,複製代碼時請注意。

快速開始

考慮到有的讀者沒有使用過,本章為了快速回顧MyBatis Generator的使用,已經會用請自行跳過。

環境配置

基礎JAVA開發環境請自行配置好,這裡不多做闡述。同時,從官網的GitHub上拉取源碼,點擊這裡 ;或者從下方直接copy倉庫地址拉取。

https://github.com/mybatis/generator.git

1

當拉取完畢的目錄監下圖,核心的目錄就是core,後續的修改都是基於它開始的。

生成代碼

再生產代碼前有以下幾個步驟要做:

創建一張表

這裡新建了一個test庫,同時加入了一張hello表。

CREATE TABLE `hello` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT "主鍵",

`code` smallint(6) DEFAULT NULL COMMENT "狀態碼",

`msg` mediumtext COMMENT "返回結果",

`created_time` datetime DEFAULT NULL COMMENT "創建時間",

`updated_time` datetime DEFAULT NULL COMMENT "更新時間",

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

1

2

3

4

5

6

7

8

新建配置文件

在項目中建立一個xml文件,按需修改為自己的路徑等信息。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE generatorConfiguration

PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

<classPathEntry location="D:IDEAgeneratorcoremybatis-generator-coresrc est
esourcesmysql-connector-java-5.1.36.jar" />

<context id="XHTables" targetRuntime="MyBatis3">

<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />

<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" />

<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />

<commentGenerator>

<property name="suppressDate" value="true" />

<!-- 是否[去除]自動生成的注釋 true:是 : false:否 -->

<property name="suppressAllComments" value="false" />

<property name="addRemarkComments" value="true" />

</commentGenerator>

<!--資料庫連接-->

<jdbcConnection driverClass="com.mysql.jdbc.Driver"

connectionURL="jdbc:mysql://106.14.216.248:3306/test?useUnicode=true&characterEncoding=UTF-8"

userId="root"

password="meiyoumima123">

</jdbcConnection>

<!--model 和 example位置-->

<javaModelGenerator targetPackage="dto" targetProject="D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esources est">

<property name="enableSubPackages" value="true" />

</javaModelGenerator>

<!--mapping文件位置-->

<sqlMapGenerator targetPackage="mapping" targetProject="D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esources est">

<property name="enableSubPackages" value="true" />

</sqlMapGenerator>

<!--XML位置-->

<javaClientGenerator type="XMLMAPPER" targetPackage="dao" targetProject="D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esources est">

<property name="enableSubPackages" value="true" />

</javaClientGenerator>

<!--要生成的表名-->

<table tableName="hello" domainObjectName="Hello"

enableCountByExample="true"

enableUpdateByExample="true"

enableDeleteByExample="true"

enableSelectByExample="true"

selectByExampleQueryId="false">

<generatedKey column="ID" sqlStatement="MYSQL" identity="true" />

</table>

</context>

</generatorConfiguration>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

運行

平時我們使用插件時,直接使用的是jar包,運行的時候是敲擊命令生成:

java -jar mybatis-generator-core-1.3.7.jar -configfile hello.xml -overwrite

1

因為,我們這裡是分析源碼,我們需要知道程序入口,那麼以DOS下敲java命令這個線索我們去尋找主入口。

解壓jar文件(jar插件通過maven依賴可以直接得到,或者自行下載),我們找到META-INF/MANIFEST.MF(此文件作用請自行百度)文件中找到這麼一句:Main-Class: org.mybatis.generator.api.ShellRunner,沒錯,這就是程序入口,見下圖:

那麼,回到項目,搜索這個類,見下圖:

同理,我們再啟動Main方法之前配置(不會配的話,測試時可以先寫死在main方法里)傳入參數:

-configfile D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esourcesHello.xml

-overwrite

-verbose

1

2

3

其中,-verbose 是列印執行過程,不加則沒有。在ShellRunner中對應的是下面這一行代碼:

ProgressCallback progressCallback = arguments.containsKey(VERBOSE) ? new VerboseProgressCallback()

: null;

1

2

如果,配置無誤並正確執行的話,將在控制台看到以下信息:

Connecting to the Database

Introspecting table hello

Generating Example class for table hello

Generating Record class for table hello

Generating Mapper Interface for table hello

Generating SQL Map for table hello

Saving file HelloMapper.xml

Saving file HelloExample.java

Saving file Hello.java

Saving file HelloMapper.java

MyBatis Generator finished successfully.

1

2

3

4

5

6

7

8

9

10

11

生成的目錄如下:

如此,所有操作便完成了。預想了解更多,請百度,下邊將進入分析源碼和插修改源碼環節。

源碼修改

現在用Maven管理項目是一件很普遍的事,公司對於代碼管理有嚴格的要求,對外提供服務的只能是API(微服務一般是這樣),與API(對外提供服務)無關的那麼是不允許對外提供訪問方式的。

比如,現在有(A、B)2個模塊,這裡對外提供的A模塊只能有Model(這裡對應Hello.java),其他的文件(HelloMapper.java、HelloExample.java、HelloMapper.xml)都只能在B的模塊中,那麼Gennerator可以修改dao和mapping文件目錄為B的目錄,重新生成即可。但是HelloExample卻不能通過配置去做,因為源碼是將Hello和HelloExample綁定在了一起了,這時就要靠我們自己來實現這個功能了。

註:有同學說,我手動將HelloExample從A複製到B不就可以了么,幹嘛要這麼麻煩,如果這樣想,也沒問題,這位同學你可以離開了(原因,自己悟)。

修改配置文件

<!--model 和 example位置-->

<javaModelGenerator >....省略...</javaModelGenerator>

<!--mapping文件位置-->

<sqlMapGenerator>....省略...</sqlMapGenerator>

<!--XML位置-->

<javaClientGenerator >....省略.../javaClientGenerator>

1

2

3

4

5

6

我們看配置文件發現既然少了一個配置標籤,那麼我們就先新增一個標籤,複製一個「javaModelGenerator 」標籤,並將名字改為「javaExampleGenerator 」:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE generatorConfiguration

PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

<classPathEntry location="D:IDEAgeneratorcoremybatis-generator-coresrc est
esourcesmysql-connector-java-5.1.36.jar" />

<context id="XHTables" targetRuntime="MyBatis3">

<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />

<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" />

<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />

<commentGenerator>

<property name="suppressDate" value="true" />

<!-- 是否[去除]自動生成的注釋 true:是 : false:否 -->

<property name="suppressAllComments" value="false" />

<property name="addRemarkComments" value="true" />

</commentGenerator>

<!--資料庫連接-->

<jdbcConnection driverClass="com.mysql.jdbc.Driver"

connectionURL="jdbc:mysql://106.14.216.248:3306/test?useUnicode=true&characterEncoding=UTF-8"

userId="root"

password="meiyoumima123">

</jdbcConnection>

<!--model 和 example位置-->

<javaModelGenerator targetPackage="dto" targetProject="D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esources est">

<property name="enableSubPackages" value="true" />

</javaModelGenerator>

<!-- 新加入標籤,將生成的 Example文件放到指定包下,官方是和 javaModelGenerator 在一個包下,不支持單獨配置-->

<javaExampleGenerator targetPackage="exampleModel" targetProject="D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esources est">

<property name="enableSubPackages" value="true" />

</javaExampleGenerator>

<!--mapping文件位置-->

<sqlMapGenerator targetPackage="mapping" targetProject="D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esources est">

<property name="enableSubPackages" value="true" />

</sqlMapGenerator>

<!--XML位置-->

<javaClientGenerator type="XMLMAPPER" targetPackage="dao" targetProject="D:IDEAgenerator1generator-mastercoremybatis-generator-coresrc est
esources est">

<property name="enableSubPackages" value="true" />

</javaClientGenerator>

<!--要生成的表名-->

<table tableName="hello" domainObjectName="Hello"

enableCountByExample="true"

enableUpdateByExample="true"

enableDeleteByExample="true"

enableSelectByExample="true"

selectByExampleQueryId="false">

<generatedKey column="ID" sqlStatement="MYSQL" identity="true" />

</table>

</context>

</generatorConfiguration>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

如此,配置文件修改完畢,如果IDE啟用驗證XML個是的話,這裡加入完畢後會顯示報錯,因為DTD驗證(自行百科)不通過,我們後續還有修改.dtd文件。不過,我們既然分析源碼,那就一步一步來,先不管這個報錯。

加入解析器

/**

* 初始化配置解析器

*/

ConfigurationParser cp = new ConfigurationParser(warnings);

/**

* 調用配置解析器創建配置對象

*

*/

Configuration config = cp.parseConfiguration(configurationFile);

/**

* shellcallback介面主要用來處理文件的創建和合併,傳入overwrite參數;默認的shellcallback是不支持文件合併的;

*/

DefaultShellCallback shellCallback = new DefaultShellCallback(

arguments.containsKey(OVERWRITE));

/**

* 創建一個MyBatisGenerator對象。MyBatisGenerator類是真正用來執行生成動作的類

*/

MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, shellCallback, warnings);

/**

* -verbose 用System.out列印執行過程

*

*/

ProgressCallback progressCallback = arguments.containsKey(VERBOSE) ? new VerboseProgressCallback(): null;

/**

* 執行生成操作,contexts和fullyqualifiedTables都是入口進來的參數,自行百度。

*/

myBatisGenerator.generate(progressCallback, contexts, fullyqualifiedTables);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

如上,在ShellRunner中這裡使我們關注的焦點,開始跟蹤代碼執行過程,從 Configuration config = cp.parseConfiguration(configurationFile) 一路跟蹤到parseConfiguration(InputSource inputSource)

throws IOException, XMLParserException這個方法:

Configuration config = cp.parseConfiguration(configurationFile);

1

2

為了代碼證件,這裡只顯示關鍵代碼:

private Configuration parseConfiguration(InputSource inputSource)

throws IOException, XMLParserException {

//1 設置校驗的dtd資源文件

builder.setEntityResolver(new ParserEntityResolver());

//2 解析配置

config = parseMyBatisGeneratorConfiguration(rootNode);

}

1

2

3

4

5

6

7

8

修改DTD校驗文件

先看標識1的代碼,ParserEntityResolver 是用來做dtd驗證,進入這個對象,並查看下面這個方法resolveEntity(String publicId, String systemId),會找到這一句:

InputStream is = getClass()getClassLoader().getResourceAsStream("org/mybatis/generator/config/xml/mybatis-generator-config_1_0.dtd");

InputSource ins = new InputSource(is);

1

2

因為我們配置的hello.xml文件頭是這個,和判斷條件一致:

所有,它讀的dtd文件路徑知道了:

文件位置以org/mybatis/generator/config/xml/mybatis-generator-config_1_0.dtd,剩下的就是修改這個文件,複製javaModelGenerator,將複製的這個改下節點名字為javaExampleGenerator,如圖操作:

還沒完,這個是回去看hello.xml,如果開了驗證的話,發現還是在報錯,因為還有一個地方:

同樣加入新增的javaExampleGenerator節點,這時再回去hello.xml看就不報錯了。

新建解析器

回到ConfigurationParser.parseConfiguration(InputSource inputSource)的這個方法。

private Configuration parseConfiguration(InputSource inputSource)

throws IOException, XMLParserException {

//1 設置校驗的dtd資源文件

builder.setEntityResolver(new ParserEntityResolver());

//2 解析配置

config = parseMyBatisGeneratorConfiguration(rootNode);

}

1

2

3

4

5

6

7

8

剛才,分析了1的方法,和dtd一樣,因為hello.xml的頭我們已經固定了,所以這裡用的是parseMyBatisGeneratorConfiguration(*)這個方法,現在找到2這一行繼續跟蹤:

private Configuration parseMyBatisGeneratorConfiguration(Element rootNode)

throws XMLParserException {

MyBatisGeneratorConfigurationParser parser = new MyBatisGeneratorConfigurationParser(

extraProperties);

return parser.parseConfiguration(rootNode);

}

1

2

3

4

5

6

發現到這裡換成了MyBatisGeneratorConfigurationParser的解析器,繼續:

public Configuration parseConfiguration(Element rootNode)

throws XMLParserException {

...

parseContext(configuration, childNode);

...

return configuration;

}

1

2

3

4

5

6

7

通過hello.xml發現,不論是javaModelGenerator標籤還是我們新加的javaExampleGenerator都是context的子標籤,由parseContext繼續往下:

private void parseContext(Configuration configuration, Node node) {

...

if ("property".equals(childNode.getNodeName())) { //$NON-NLS-1$

parseProperty(context, childNode);

} else if ("plugin".equals(childNode.getNodeName())) { //$NON-NLS-1$

parsePlugin(context, childNode);

} else if ("commentGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$

parseCommentGenerator(context, childNode);

} else if ("jdbcConnection".equals(childNode.getNodeName())) { //$NON-NLS-1$

parseJdbcConnection(context, childNode);

} else if ("connectionFactory".equals(childNode.getNodeName())) { //$NON-NLS-1$

parseConnectionFactory(context, childNode);

} else if ("javaModelGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$

parseJavaModelGenerator(context, childNode);

}

...

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

這裡發現,它根據不同標籤進行解析,以「parseJavaModelGenerator」為例,查看parseJavaModelGenerator(context, childNode)這個方法:

protected void parseJavaModelGenerator(Context context, Node node) {

//1 自定義解析器

JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration();

// 2 將解析器放入上下文

context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);

}

1

2

3

4

5

6

不難看出,核心方法有2個,那就照著複製修改唄:

先定位JavaModelGeneratorConfiguration類,複製一份修改類名為JavaExampleGeneratorConfiguration,並將toXmlElement里的節點名字換成我們新加入的標籤。

這裡只是加入了解析器,我們需要用上我們的解析器,並且把解析到的內容放入到上下文中,因此:

1、複製parseJavaModelGenerator方法並改名為parseJavaExampleGenerator,然後應用我們的解析器:

上邊是應用了解析器,但是我們的上下文還沒有放解析的內容,注意紫色框框。

2、紫色框框里的Context對象是存放上下文信息的,我們將解析器加入到Context中,並生成getter/setter:

private JavaExampleGeneratorConfiguration javaExampleGeneratorConfiguration;

public JavaExampleGeneratorConfiguration getJavaExampleGeneratorConfiguration() {

return javaExampleGeneratorConfiguration;

}

public void setJavaExampleGeneratorConfiguration(JavaExampleGeneratorConfiguration javaExampleGeneratorConfiguration) {

this.javaExampleGeneratorConfiguration = javaExampleGeneratorConfiguration;

}

1

2

3

4

5

6

7

8

3、讓DOM讀到新加標籤時,交給我們的解析器處理,因此還需要在parseContext(Configuration configuration, Node node)中加入我們的分支節點:

至此,我們新加入的標籤已經可以正確解析,數據準備階段已完成。下一步要做的是讓生成工具能讀到我們新加的解析器,並使用其中的信息。

獲取表數據

前期的準備工作已完成,現在開始從生成代碼開始往下跟,回到ShellRunner,以下面的generate(*)往下跟蹤代碼:

myBatisGenerator.generate(progressCallback, contexts, fullyqualifiedTables);

1

進入代碼塊:

public void generate(ProgressCallback callback, Set<String> contextIds,

Set<String> fullyQualifiedTableNames, boolean writeFiles) throws SQLException, IOException, InterruptedException {

...

//0、 連接資料庫,解析表欄位

for (Context context : contextsToRun) {

context.introspectTables(callback, warnings,fullyQualifiedTableNames);

}

//1、生成JAVA和xml對應資源文件

for (Context context : contextsToRun) {

context.generateFiles(callback, generatedJavaFiles,generatedXmlFiles, warnings);

}

//2、保存文件

if (writeFiles) {

...

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

先看連接資料庫:

public void introspectTables(ProgressCallback callback,

List<String> warnings, Set<String> fullyQualifiedTableNames)

throws SQLException, InterruptedException {

// 初始化表數組

introspectedTables = new ArrayList<IntrospectedTable>();

// 獲取表信息

for (TableConfiguration tc : tableConfigurations) {

...

List<IntrospectedTable> tables = databaseIntrospector.introspectTables(tc);

...

}

}

1

2

3

4

5

6

7

8

9

10

11

12

通過databaseIntrospector.introspectTables(tc);往下追蹤:

public List<IntrospectedTable> introspectTables(TableConfiguration tc)

throws SQLException {

List<IntrospectedTable> introspectedTables = calculateIntrospectedTables(

tc, columns)

}

private List<IntrospectedTable> calculateIntrospectedTables(

TableConfiguration tc,

Map<ActualTableName, List<IntrospectedColumn>> columns) {

IntrospectedTable introspectedTable = ObjectFactory

.createIntrospectedTable(tc, table, context);

}

public static IntrospectedTable createIntrospectedTable(

TableConfiguration tableConfiguration, FullyQualifiedTable table,

Context context) {

IntrospectedTable answer = createIntrospectedTableForValidation(context);

}

public static IntrospectedTable createIntrospectedTableForValidation(Context context) {

String type = context.getTargetRuntime();

if ("MyBatis3".equalsIgnoreCase(type)) {

type = IntrospectedTableMyBatis3Impl.class.getName();

}

// IntrospectedTable 其實就是 IntrospectedTableMyBatis3Impl對象

IntrospectedTable answer = (IntrospectedTable) createInternalObject(type);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

注意:IntrospectedTable 其實就是 IntrospectedTableMyBatis3Impl對象,這個後邊會用到。

以上代碼忽略了其他內容,主要是對查到的表數據進行過濾篩選,組建後續資源文件所需的對象,感興趣可以自行調試,

這裡主要是看到 IntrospectedTableMyBatis3Impl.class這個對象的生成。而這個Type 對應 的是helllo.xml中context標籤的「targetRuntime=」MyBatis3」。而每一個IntrospectedTable都封裝了對應一個表的全部構建信息。

生成文件基礎信息

看完連接資料庫,再看第一步「」生成JAVA和XML對應的資源文件「,往下走:

public void generateFiles(ProgressCallback callback,

List<GeneratedJavaFile> generatedJavaFiles,

List<GeneratedXmlFile> generatedXmlFiles, List<String> warnings)

throws InterruptedException {

//1、插件實例化

for (PluginConfiguration pluginConfiguration : pluginConfigurations) {

...

}

...

if (introspectedTables != null) {

for (IntrospectedTable introspectedTable : introspectedTables) {

callback.checkCancel();

//2、初始化生成規則、包和表名

introspectedTable.initialize();

introspectedTable.calculateGenerators(warnings, callback); // 3、添加解析器參數

generatedJavaFiles.addAll(introspectedTable.getGeneratedJavaFiles()); // 4、生成Java類 *Example

generatedXmlFiles.addAll(introspectedTable.getGeneratedXmlFiles()); // 5、生成xml

...

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

上圖,第一步插件實例化,這個我們先無需關心,後續我們手寫插件的時候,這裡才會是重點。看第二步:

public void initialize() {

calculateJavaClientAttributes(); // mapper

calculateModelAttributes();// dao、example資源位置這裡生成

calculateXmlAttributes(); // xml

...

}

1

2

3

4

5

6

這裡我們主要看 calculateModelAttributes()方法:

protected void calculateModelAttributes() {

// 1、讀取的是javaModelGenerator 標籤的包路徑

String pakkage = calculateJavaModelPackage();

// 2、拼裝全限定名,會影響生成Hello的位置

sb.setLength(0);

sb.append(pakkage);

sb.append(".");

sb.append(fullyQualifiedTable.getDomainObjectName());

setBaseRecordType(sb.toString());

// 2、拼裝全限定名,會影響生成HelloExample的位置

sb.setLength(0);

sb.append(pakkage);

sb.append(".");

sb.append(fullyQualifiedTable.getDomainObjectName());

sb.append("Example"); //$NON-NLS-1$

setExampleType(sb.toString());

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

上圖,我們看到這裡將Hello和HelloExample的全限定類名搞出來了,這個會影響最終文件的位置,然而String pakkage = calculateJavaModelPackage();方法跟蹤進去:

protected String calculateJavaModelPackage() {

// 獲取Context上下文中已經緩存了javaModelGenerator標籤屬性信息的JavaModelGeneratorConfiguration解析器

JavaModelGeneratorConfiguration config = context.getJavaModelGeneratorConfiguration();

StringBuilder sb = new StringBuilder();

sb.append(config.getTargetPackage());// javaModelGenerator 標籤中 targetPackage 屬性的值

sb.append(fullyQualifiedTable.getSubPackageForModel(isSubPackagesEnabled(config)));

return sb.toString();

}

1

2

3

4

5

6

7

8

我這裡注釋的很明白了,我們也應該明白為啥說Hello和HelloExample是綁定在一起了。

如此,找到病根,那就好解決了,既然綁在一起那我們就把它拆開:

1、複製calculateJavaModelPackage()並命名為calculateJavaExamplePackage(),然後重從Context中獲取我們自定義解析器的信息,下一步將會用到這個方法:

2、複製calculateModelAttributes()並改為 calculateExampleAttributes(),將 calculateExampleAttributes()中拼接HelloExample全限定類名剪貼到calculateExampleAttributes()方法中,注意新方法應用的是我們自己加的解析器即用的是上一步操作中複製的calculateJavaExamplePackage()方法名:

生成資源生成器

重新回到生成資源文件這裡,剛剛分析完了第二步,現在看第三步,在連接資料庫那一步,

我們已經知道IntrospectedTable 其實就是 IntrospectedTableMyBatis3Impl對象,查看calculateGenerators.calculateGenerators()方法:

@Override

public void calculateGenerators(List<String> warnings,

ProgressCallback progressCallback) {

// 添加Model、Example等生成器

calculateJavaModelGenerators(warnings, progressCallback);

// 添加Mapper等生成器

AbstractJavaClientGenerator javaClientGenerator =

calculateClientGenerators(warnings, progressCallback);

// 添加xml生成器

calculateXmlMapperGenerator(javaClientGenerator, warnings, progressCallback);

}

protected void calculateJavaModelGenerators(List<String> warnings,

ProgressCallback progressCallback) {

// 根據規則判斷是否需要Example生成器,即HelloExample

if (getRules().generateExampleClass()) {

AbstractJavaGenerator javaGenerator = new ExampleGenerator();

initializeAbstractGenerator(javaGenerator, warnings,

progressCallback);

javaModelGenerators.add(javaGenerator);

}

...

//根據規則判斷是否需要Model生成器,即生成Hello

if (getRules().generateBaseRecordClass()) {

AbstractJavaGenerator javaGenerator = new BaseRecordGenerator();

initializeAbstractGenerator(javaGenerator, warnings,

progressCallback);

javaModelGenerators.add(javaGenerator);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

在calculateJavaModelGenerators()方法中看到構造器都放在javaModelGenerators這個List中,這個會影響到後續獲取包路徑問題,因此我們要在此類中加入一個我們定義標籤的數組:

然後,修改calculateJavaModelGenerators() 方法,將getRules().generateExampleClass()的javaModelGenerators替換成新加的javaExampleGenerators數組中:

if (getRules().generateExampleClass()) {

AbstractJavaGenerator javaGenerator = new ExampleGenerator();

initializeAbstractGenerator(javaGenerator, warnings,

progressCallback);

javaExampleGenerators.add(javaGenerator);

}

1

2

3

4

5

6

保存資源文件信息

看完第三步,在看第四步:

public void generateFiles(ProgressCallback callback,

List<GeneratedJavaFile> generatedJavaFiles,

List<GeneratedXmlFile> generatedXmlFiles, List<String> warnings)

throws InterruptedException {

//1、插件實例化

for (PluginConfiguration pluginConfiguration : pluginConfigurations) {

...

}

...

if (introspectedTables != null) {

for (IntrospectedTable introspectedTable : introspectedTables) {

callback.checkCancel();

//2、初始化生成規則、包和表名

introspectedTable.initialize();

introspectedTable.calculateGenerators(warnings, callback); // 3、添加解析器參數

generatedJavaFiles.addAll(introspectedTable.getGeneratedJavaFiles()); // 4、生成Java類 *Example

generatedXmlFiles.addAll(introspectedTable.getGeneratedXmlFiles()); // 5、生成xml

...

}

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

第四步的introspectedTable.getGeneratedJavaFiles()方法,在連接資料庫那一步,

我們已經知道IntrospectedTable 其實就是 IntrospectedTableMyBatis3Impl對象,那麼我們看看IntrospectedTableMyBatis3Impl.getGeneratedJavaFiles()的實現:

在第三步分析的時候已經將HelloExample文件從javaModelGenerators中單獨抽離了,我們是放在javaExampleGenerators中,如果不抽離出來在這裡可以明確看出Hello和HelloExample都在javaModelGenerators中,獲取的都是javaModelGenerator解析器里存的包名,即對應的還是javaModelGenerator標籤的值,而不是我們自定義的。

改動如下:

最後,循環保存文件,這裡就不多闡述了。

至此,源碼修改完畢了,我們運行下ShellRunner來看下吧!

大功告成,快打包成jar試試看吧!

自定義插件

上圖,是我們生成的Hello文件,我們已經看到它已經實現了序列化、重新HashCode、toString,這是因為我們在配置文件有這麼幾個配置:

<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />

<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" />

<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />

1

2

3

他們分別是啟用了已有的序列化插件、HashCode插件、ToString插件。

現在回看Hello.java圖片,現在假設我們想給每個生成的Model加上註解,並且寫上我們自己的注釋,最終的效果:

/**

* Hello 設置Kryo的CompatibleFieldSerializer

* 程序自動生成,無需更改。

*/

@DefaultSerializer(CompatibleFieldSerializer.class)

public class Hello implements Serializable {

...

}

1

2

3

4

5

6

7

8

就是使用註解的方式使用Kryo的序列化技術,目標明確開始實現。

新建插件

我們先看下SerializablePlugin的寫法,我們發現在這包下除了我們配置的ToStringPlugin、EqualsHashCodePlugin、SerializablePlugin插件外,還有其他插件,但他們都有一個共同點都是繼承自PluginAdapter,並重寫了裡面的方法。

如此,先複製SerializablePlugin並重命名為SerializableAnnoPlugin,然後開始實現我們自己的方法:

public class SerializableAnnoPlugin extends PluginAdapter {

private FullyQualifiedJavaType defaultSerializer;

private FullyQualifiedJavaType compatibleFieldSerializer;

public SerializableAnnoPlugin() {

super();

defaultSerializer = new FullyQualifiedJavaType("com.esotericsoftware.kryo.DefaultSerializer");

compatibleFieldSerializer = new FullyQualifiedJavaType("com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer");

}

@Override

public boolean validate(List<String> warnings) {

return true;

}

@Override

public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass,

IntrospectedTable introspectedTable) {

makeSerializable(topLevelClass, introspectedTable);

return true;

}

@Override

public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass,

IntrospectedTable introspectedTable) {

makeSerializable(topLevelClass, introspectedTable);

return true;

}

@Override

public boolean modelRecordWithBLOBsClassGenerated(

TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

makeSerializable(topLevelClass, introspectedTable);

return true;

}

protected void makeSerializable(TopLevelClass topLevelClass,

IntrospectedTable introspectedTable) {

...

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

defaultSerializer 和 compatibleFieldSerializer 分別添加的是全限定類名,這個其實相當於導入jar的操作,會在頭部生成:

import com.esotericsoftware.kryo.DefaultSerializer;

import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer;

import java.io.Serializable;

1

2

3

makeSerializable()現在是空方法,因此,我們要怎麼去寫呢,慢慢分析。

1、我們從目錄去找到「生成資源生成器」那一部分,即IntrospectedTableMyBatis3Impld的calculateJavaModelGenerators方法,我們找到對應的生成器:

protected void calculateJavaModelGenerators(List<String> warnings,

ProgressCallback progressCallback) {

if (getRules().generateExampleClass()) {

AbstractJavaGenerator javaGenerator = new ExampleGenerator();

initializeAbstractGenerator(javaGenerator, warnings, progressCallback);

javaExampleGenerators.add(javaGenerator);

}

...

if (getRules().generateBaseRecordClass()) {

AbstractJavaGenerator javaGenerator = new BaseRecordGenerator();

initializeAbstractGenerator(javaGenerator, warnings, progressCallback);

javaModelGenerators.add(javaGenerator);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

2、new BaseRecordGenerator();這個就是生成Model的關鍵操作。在看這類的方法getGeneratedJavaFiles(),這個方法作用前邊已經分析過,但注意看javaGenerator.getCompilationUnits()這一句:

public List<GeneratedJavaFile> getGeneratedJavaFiles() {

List<GeneratedJavaFile> answer = new ArrayList<GeneratedJavaFile>();

for (AbstractJavaGenerator javaGenerator : javaModelGenerators) {

List<CompilationUnit> compilationUnits = javaGenerator.getCompilationUnits();

for (CompilationUnit compilationUnit : compilationUnits) {

GeneratedJavaFile gjf = new GeneratedJavaFile(compilationUnit, context.getJavaModelGeneratorConfiguration().getTargetProject(), context.getProperty(PropertyRegistry.CONTEXT_JAVA_FILE_ENCODING), context.getJavaFormatter());

answer.add(gjf);

}

}

}

1

2

3

4

5

6

7

8

9

10

這個生成器其實是調用BaseRecordGenerator的getCompilationUnits()方法,這裡就是生成資源的核心了:

public List<CompilationUnit> getCompilationUnits() {

CommentGenerator commentGenerator = context.getCommentGenerator();

...

// Model的注釋

commentGenerator.addModelClassComment(topLevelClass, introspectedTable);

...

return answer;

}

1

2

3

4

5

6

7

8

9

我們看到,先從上下文拿到構造器context.getCommentGenerator()。然後進入 commentGenerator.addModelClassComment(topLevelClass, introspectedTable)發現這個其實就是生成DOC的方法,因此,我們將 commentGenerator.addModelClassComment(topLevelClass, introspectedTable)注釋掉,然後將裡邊的方法複製到我們SerializableAnnoPlugin里的makeSerializable()方法里,然後稍作修改:

protected void makeSerializable(TopLevelClass topLevelClass,

IntrospectedTable introspectedTable) {

topLevelClass.addImportedType(this.defaultSerializer);

topLevelClass.addImportedType(this.compatibleFieldSerializer);

CommentGenerator commentGenerator = this.context.getCommentGenerator();

topLevelClass.addJavaDocLine("/**");

topLevelClass.addJavaDocLine(String.format("* %s設置Kryo的CompatibleFieldSerializer", introspectedTable.getRules().calculateAllFieldsClass().getShortName()));

topLevelClass.addJavaDocLine("* 程序自動生成,無需更改。");

topLevelClass.addJavaDocLine("*/");

commentGenerator.addJavaFileComment(topLevelClass);

StringBuilder sb = new StringBuilder();

topLevelClass.addAnnotation(sb.append("@DefaultSerializer(").append(this.compatibleFieldSerializer.getShortName()).append(".class)").toString());

}

1

2

3

4

5

6

7

8

9

10

11

12

13

應用插件

最後一步,在Hello.xml加入我們的插件:

<plugin type="org.mybatis.generator.plugins.SerializableAnnoPlugin" />

<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />

<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" />

<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />

1

2

3

4

執行

執行一下吧,大功告成:

《完》

「快速入門」MyBatis Generator源碼分析修改和自定義插件

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

簡單非同步處理

TAG:程序員小新人學習 |