自制代码生成器中的一些问题与思考

 

1. 引言

去年7月开始参加工作,刚开始被先后分配了两个制作基础页面的任务,也就是常规的增删改查,包括前端页面的 vue 文件以及后端实体类和各逻辑层的接口与实现类,总共需要创建 9 个文件,1个 vue、7个 java,1个 xml。

虽然可以使用 MybatisGenerator 根据数据库表自动生成实体类和 Mapper 层文件,但再往上的逻辑层就需要手动编写。Ctrl+C,Ctrl+V,再根据具体的实体类名称去修改,前前后后花了1个小时把一个基础页面调通。觉得实在太麻烦了,所以产生了写个代码生成器的念头,一键生成9个文件,从而告别复制粘贴。效果如下所示:

代码生成器演示图

2. 技术点

  1. 根据代码模板生成,使用了常用的 FreeMarker 作为模板引擎。
  2. 为了简化使用操作,做了个 GUI 把已有的 MybatisGenerator 和新编写的生成代码逻辑封装起来。使用了 JavaFX,可以拖控件,挺省事的。
  3. 为了保存生成文件的位置信息、连接数据库信息,以及在不同项目中切换,使用了 SQLite,方便。

3. 问题

以下记录所遇到的一些问题:

  1. 在 MybatisGenerator 生成实体类( java 文件)后,需要将实体类编译成 class 文件,再加载进 JVM 中,才能通过反射读取到实体类属性。实现步骤如下:

    • 读取 java 文件内容
    • JavaCompiler 动态编译生成class文件
    • 通过反射利用 ClassLoader 的 addUR L方法,将 class 文件的路径加入到 classpath 中(这里需要加入 classpath 的主要原因是最后的程序被打包成了 jar 包,而运行时生成的 class 文件是不会在 jar 包中,那么其路径也不在 classpath 中)
    • 加载类(Class.forName)
  2. 在用 IDE 开发时,可以通过 File 类成功找到资源文件:

    File configFile = new File("generatorConfig.xml");
    

    但是打包成 jar 包后,就连资源文件一起打包了,而这时是无法通过 file:/e:/codeGenerator.jar/generatorConfig.xml 这种形式的文件 URL 找到文件的。

    这是因为 jar 包是个单独文件,而不是文件夹,而需要通过 ClassLoader 提供的 getResourceAsStream 方法获取 jar 包中的内容。

    InputStream configFile = Thread.currentThread().getContextClassLoader().getResourceAsStream("generatorConfig.xml");
    

4. 思考

《阿里巴巴Java开发手册》中关于应用分层推荐使用4层,分别是 Web 层、Service 层、Manager 层和 DAO 层。而我们在开发的过程中经常能看到每一层都需要面向接口编程,例如 xxxManager。这么做是为了达到“对修改封闭,对扩展开放”的原则。

但实际情况是一个接口通常只有一个实现类,例如 xxxManagerImpl ,大多数的代码通常都并不会有扩展的情况。这样会导致的问题就有像前文所说一个简单基础数据的增删改查就需要 7 个 java 类,代码量增加了。难免有些是在为了使用接口而使用接口的味道。

那么是否需要面向接口编程?

下面让我们试想一下,在一个接口只有一个实现类的情况下,不使用面向接口编程的好处和坏处。

好处,很显然,我们只需要编写、维护一份代码(实现类),代码量减少了。

坏处,则是不可控的类信息暴露控制。例如,想通过 AOP 实现方法级别的缓存,并且使用注解标记方法,如果我们失误将注解标记在了 private 方法上,而由于基于 CGLIB 的 AOP 是通过生成子类来创建代理,所以该 AOP 将不会生效;但如果是基于接口动态代理,则不会因为失误出现这个问题。

最后,尝试给这个问题一个暂时的答案:

在大型团队进行面向企业应用开发中,为了避免意外的风险,尽量使用接口编程。

而当团队规模不大,了解不使用接口的风险,能够承受该风险,业务逻辑变更频繁时,不是面向企业应用开发,可考虑不使用接口,直接通过实现类开发。