欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

(快速入门)MyBatis Generator源码分析修改和自定义插件

程序员文章站 2022-05-08 11:06:14
...

(快速入门)MyBatis Generator源码分析修改和自定义插件

MyBatis Generator 能快速代码生成工具,尽管已经提供了大量的配置标签,但是每个公司都有自己的代码规范,这个时候,别人提供的不合适,那就只能自己上手了,毕竟适合自己的才是最顺手的。

可能是CSDN问题,复制的xml粘贴到网页里后,每个标签自动闭合了,结尾自动带了”/”,复制代码时请注意。


快速开始

考虑到有的读者没有使用过,本章为了快速回顾MyBatis Generator的使用,已经会用请自行跳过。

环境配置

基础JAVA开发环境请自行配置好,这里不多做阐述。同时,从官网的GitHub上拉取源码,点击这里 ;或者从下方直接copy仓库地址拉取。

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

当拉取完毕的目录监下图,核心的目录就是core,后续的修改都是基于它开始的。
(快速入门)MyBatis Generator源码分析修改和自定义插件

生成代码

再生产代码前有以下几个步骤要做:
创建一张表
这里新建了一个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;

新建配置文件
在项目中建立一个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:\IDEA\generator\core\mybatis-generator-core\src\test\resources\mysql-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&amp;characterEncoding=UTF-8"
        userId="root"
        password="meiyoumima123">
    </jdbcConnection>
    <!--model 和 example位置-->
    <javaModelGenerator targetPackage="dto" targetProject="D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\test">
      <property name="enableSubPackages" value="true" />
    </javaModelGenerator>
    <!--mapping文件位置-->
    <sqlMapGenerator targetPackage="mapping"  targetProject="D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\test">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>
    <!--XML位置-->
    <javaClientGenerator type="XMLMAPPER" targetPackage="dao" targetProject="D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\test">
      <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>

运行
平时我们使用插件时,直接使用的是jar包,运行的时候是敲击命令生成:

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

因为,我们这里是分析源码,我们需要知道程序入口,那么以DOS下敲java命令这个线索我们去寻找主入口。
解压jar文件(jar插件通过maven依赖可以直接得到,或者自行下载),我们找到META-INF/MANIFEST.MF(此文件作用请自行百度)文件中找到这么一句:Main-Class: org.mybatis.generator.api.ShellRunner,没错,这就是程序入口,见下图:
(快速入门)MyBatis Generator源码分析修改和自定义插件
那么,回到项目,搜索这个类,见下图:
(快速入门)MyBatis Generator源码分析修改和自定义插件
同理,我们再启动Main方法之前配置(不会配的话,测试时可以先写死在main方法里)传入参数:

-configfile D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\Hello.xml 
-overwrite 
-verbose

其中,-verbose 是打印执行过程,不加则没有。在ShellRunner中对应的是下面这一行代码:

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

如果,配置无误并正确执行的话,将在控制台看到以下信息:

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.

生成的目录如下:
(快速入门)MyBatis Generator源码分析修改和自定义插件
如此,所有操作便完成了。预想了解更多,请百度,下边将进入分析源码和插修改源码环节。

源码修改

现在用Maven管理项目是一件很普遍的事,公司对于代码管理有严格的要求,对外提供服务的只能是API(微服务一般是这样),与API(对外提供服务)无关的那么是不允许对外提供访问方式的。
(快速入门)MyBatis Generator源码分析修改和自定义插件
比如,现在有(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>

我们看配置文件发现既然少了一个配置标签,那么我们就先新增一个标签,复制一个“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:\IDEA\generator\core\mybatis-generator-core\src\test\resources\mysql-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&amp;characterEncoding=UTF-8"
        userId="root"
        password="meiyoumima123">
    </jdbcConnection>
    <!--model 和 example位置-->
    <javaModelGenerator targetPackage="dto" targetProject="D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\test">
      <property name="enableSubPackages" value="true" />
    </javaModelGenerator>
    <!-- 新加入标签,将生成的 Example文件放到指定包下,官方是和 javaModelGenerator 在一个包下,不支持单独配置-->
    <javaExampleGenerator targetPackage="exampleModel" targetProject="D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\test">
      <property name="enableSubPackages" value="true" />
    </javaExampleGenerator>
    <!--mapping文件位置-->
    <sqlMapGenerator targetPackage="mapping"  targetProject="D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\test">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>
    <!--XML位置-->
    <javaClientGenerator type="XMLMAPPER" targetPackage="dao" targetProject="D:\IDEA\generator1\generator-master\core\mybatis-generator-core\src\test\resources\test">
      <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>

如此,配置文件修改完毕,如果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);

如上,在ShellRunner中这里使我们关注的焦点,开始跟踪代码执行过程,从 Configuration config = cp.parseConfiguration(configurationFile) 一路跟踪到parseConfiguration(InputSource inputSource)
throws IOException, XMLParserException这个方法:

 Configuration config = cp.parseConfiguration(configurationFile);

为了代码证件,这里只显示关键代码:

 private Configuration parseConfiguration(InputSource inputSource)
            throws IOException, XMLParserException {
             //1 设置校验的dtd资源文件
             builder.setEntityResolver(new ParserEntityResolver());
             //2 解析配置
             config = parseMyBatisGeneratorConfiguration(rootNode);

    }

修改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);

因为我们配置的hello.xml文件头是这个,和判断条件一致:

(快速入门)MyBatis Generator源码分析修改和自定义插件

所有,它读的dtd文件路径知道了:
文件位置以org/mybatis/generator/config/xml/mybatis-generator-config_1_0.dtd,剩下的就是修改这个文件,复制javaModelGenerator,将复制的这个改下节点名字为javaExampleGenerator,如图操作:
(快速入门)MyBatis Generator源码分析修改和自定义插件
还没完,这个是回去看hello.xml,如果开了验证的话,发现还是在报错,因为还有一个地方:
(快速入门)MyBatis Generator源码分析修改和自定义插件
同样加入新增的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的方法,和dtd一样,因为hello.xml的头我们已经固定了,所以这里用的是parseMyBatisGeneratorConfiguration(*)这个方法,现在找到2这一行继续跟踪:

private Configuration parseMyBatisGeneratorConfiguration(Element rootNode)
            throws XMLParserException {
        MyBatisGeneratorConfigurationParser parser = new MyBatisGeneratorConfigurationParser(
                extraProperties);
        return parser.parseConfiguration(rootNode);
    }

发现到这里换成了MyBatisGeneratorConfigurationParser的解析器,继续:

public Configuration parseConfiguration(Element rootNode)
            throws XMLParserException {
      ...
      parseContext(configuration, childNode);
      ...
      return configuration;
    }

通过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);
}

        ...

这里发现,它根据不同标签进行解析,以“parseJavaModelGenerator”为例,查看parseJavaModelGenerator(context, childNode)这个方法:

 protected void parseJavaModelGenerator(Context context, Node node) {
        //1 自定义解析器
        JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration(); 
        // 2 将解析器放入上下文
        context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);
        }

不难看出,核心方法有2个,那就照着复制修改呗:
(快速入门)MyBatis Generator源码分析修改和自定义插件
先定位JavaModelGeneratorConfiguration类,复制一份修改类名为JavaExampleGeneratorConfiguration,并将toXmlElement里的节点名字换成我们新加入的标签。
这里只是加入了解析器,我们需要用上我们的解析器,并且把解析到的内容放入到上下文中,因此:
1、复制parseJavaModelGenerator方法并改名为parseJavaExampleGenerator,然后应用我们的解析器:
(快速入门)MyBatis Generator源码分析修改和自定义插件

上边是应用了解析器,但是我们的上下文还没有放解析的内容,注意紫色框框。

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

private JavaExampleGeneratorConfiguration javaExampleGeneratorConfiguration;
 public JavaExampleGeneratorConfiguration getJavaExampleGeneratorConfiguration() {
        return javaExampleGeneratorConfiguration;
    }

    public void setJavaExampleGeneratorConfiguration(JavaExampleGeneratorConfiguration javaExampleGeneratorConfiguration) {
        this.javaExampleGeneratorConfiguration = javaExampleGeneratorConfiguration;
    }

3、让DOM读到新加标签时,交给我们的解析器处理,因此还需要在parseContext(Configuration configuration, Node node)中加入我们的分支节点:
(快速入门)MyBatis Generator源码分析修改和自定义插件
至此,我们新加入的标签已经可以正确解析,数据准备阶段已完成。下一步要做的是让生成工具能读到我们新加的解析器,并使用其中的信息。

获取表数据

前期的准备工作已完成,现在开始从生成代码开始往下跟,回到ShellRunner,以下面的generate(*)往下跟踪代码:

 myBatisGenerator.generate(progressCallback, contexts, fullyqualifiedTables);

进入代码块:

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) {
                    ...
           }

先看连接数据库:

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);
             ...
            }
      }

通过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);
 }

注意: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
                ...
            }
        }

    }

上图,第一步插件实例化,这个我们先无需关心,后续我们手写插件的时候,这里才会是重点。看第二步:

public void initialize() {
        calculateJavaClientAttributes(); // mapper
        calculateModelAttributes();// dao、example资源位置这里生成
        calculateXmlAttributes(); // xml
        ...
    }

这里我们主要看 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());
    }

上图,我们看到这里将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();
    }

我这里注释的很明白了,我们也应该明白为啥说Hello和HelloExample是绑定在一起了。
如此,找到病根,那就好解决了,既然绑在一起那我们就把它拆开:
1、复制calculateJavaModelPackage()并命名为calculateJavaExamplePackage(),然后重从Context中获取我们自定义解析器的信息,下一步将会用到这个方法:
(快速入门)MyBatis Generator源码分析修改和自定义插件

2、复制calculateModelAttributes()并改为 calculateExampleAttributes(),将 calculateExampleAttributes()中拼接HelloExample全限定类名剪贴到calculateExampleAttributes()方法中,注意新方法应用的是我们自己加的解析器即用的是上一步操作中复制的calculateJavaExamplePackage()方法名:
(快速入门)MyBatis Generator源码分析修改和自定义插件

生成资源生成器

重新回到生成资源文件这里,刚刚分析完了第二步,现在看第三步,在连接数据库那一步,
我们已经知道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);
        }
  }

在calculateJavaModelGenerators()方法中看到构造器都放在javaModelGenerators这个List中,这个会影响到后续获取包路径问题,因此我们要在此类中加入一个我们定义标签的数组:
(快速入门)MyBatis Generator源码分析修改和自定义插件
然后,修改calculateJavaModelGenerators() 方法,将getRules().generateExampleClass()的javaModelGenerators替换成新加的javaExampleGenerators数组中:

if (getRules().generateExampleClass()) {
            AbstractJavaGenerator javaGenerator = new ExampleGenerator();
            initializeAbstractGenerator(javaGenerator, warnings,
                    progressCallback);
            javaExampleGenerators.add(javaGenerator);
        }

保存资源文件信息

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

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
                ...
            }
        }

    }

第四步的introspectedTable.getGeneratedJavaFiles()方法,在连接数据库那一步,
我们已经知道IntrospectedTable 其实就是 IntrospectedTableMyBatis3Impl对象,那么我们看看IntrospectedTableMyBatis3Impl.getGeneratedJavaFiles()的实现:

(快速入门)MyBatis Generator源码分析修改和自定义插件
在第三步分析的时候已经将HelloExample文件从javaModelGenerators中单独抽离了,我们是放在javaExampleGenerators中,如果不抽离出来在这里可以明确看出Hello和HelloExample都在javaModelGenerators中,获取的都是javaModelGenerator解析器里存的包名,即对应的还是javaModelGenerator标签的值,而不是我们自定义的。
改动如下:
(快速入门)MyBatis Generator源码分析修改和自定义插件

最后,循环保存文件,这里就不多阐述了。
至此,源码修改完毕了,我们运行下ShellRunner来看下吧!
(快速入门)MyBatis Generator源码分析修改和自定义插件

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

自定义插件

(快速入门)MyBatis Generator源码分析修改和自定义插件

上图,是我们生成的Hello文件,我们已经看到它已经实现了序列化、重新HashCode、toString,这是因为我们在配置文件有这么几个配置:

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

他们分别是启用了已有的序列化插件、HashCode插件、ToString插件。
现在回看Hello.java图片,现在假设我们想给每个生成的Model加上注解,并且写上我们自己的注释,最终的效果:

/**
* Hello 设置Kryo的CompatibleFieldSerializer
* 程序自动生成,无需更改。
*/
@DefaultSerializer(CompatibleFieldSerializer.class)
public class Hello implements Serializable {
    ...
}

就是使用注解的方式使用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) {
        ...
    }
}

defaultSerializer 和 compatibleFieldSerializer 分别添加的是全限定类名,这个其实相当于导入jar的操作,会在头部生成:

import com.esotericsoftware.kryo.DefaultSerializer;
import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer;
import java.io.Serializable;

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);
        }
    }

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);
            }
        }
 }

这个生成器其实是调用BaseRecordGenerator的getCompilationUnits()方法,这里就是生成资源的核心了:

public List<CompilationUnit> getCompilationUnits() {

        CommentGenerator commentGenerator = context.getCommentGenerator();
        ...
        // Model的注释
        commentGenerator.addModelClassComment(topLevelClass, introspectedTable);
        ...
        return answer;
    }

我们看到,先从上下文拿到构造器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());
    }

应用插件

最后一步,在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" />

执行

执行一下吧,大功告成:
(快速入门)MyBatis Generator源码分析修改和自定义插件


《完》

相关标签: Mybatis Generator