Foxnic-Web 代码生成 (1) —— 开始生成代码

Foxnic-Web 代码生成 (1) —— 开始生成代码

基本原理

使用 Foxnic-Web 以及 Foxnic-SQL  进行应用开发时,都可以支持代码生成。他们的区别是,基于 Foxnic-SQL 的快速 main 函数启动的应用,只需要生成 Model 和 Service 即可。基于 Foxnic-Web 开发 Web 应用时,除了生成 Model 和 Service 以外,还要生成 Proxy、Controller、UI界面等。

Foxnic 的代码生成是基于数据表的,所以当表结构变更,甚至只是注释的调整,我们也是建议重新生成必要的代码。在 Foxnic 的体系中,我们认为最初的表结构设计、ER图设计,就是这个系统设计的起点。后续的程序设计或数据结构设计都是表结构设计的延续。

Foxnic 的代码生成体系希望开发者可以有一个较高的开发起点,可以基于生成的代码直接开发应用,甚至是代码生成后无需修改就可以直接使用了。另一方面,我们又不关闭二次开发的开放性,毕竟自由的修改代码才是软件系统可以按需定制的终极路径。这也是 Foxnic 体系没有走无代码或低代码平台的原因。

本文中的示例代码均可在 https://gitee.com/LeeFJ/foxnic-samples 项目中找到。

生成表结构元数据

代码生成的第一步是生成表结构元数据。我们知道,数据表、列、索引等信息都存储在数据库内,我们在 java 程序内无法直接引用。但有些时候,我们又需要用到这些东西,特别是表名、字段等。Foxnic-Web 在代码生成、以及其它业务逻辑编写时都有可能用到这些元数据。

所以,我们要通过一种方式将数据库元数据转换成 java 结构,这种结构是通过代码生成的。Java 生成的元数据它是静态的,不会随着变结构的改变而改变,Foxnic-Web 要求表结构变动后,需要重新生成元数据类。

示例项目的 webfull 项目下的 WebFullDBMetaGenerator 类用于生成数据库元数据类:

package org.github.foxnic.web.generator.constants;

import com.github.foxnic.dao.spec.DAO;

import com.github.foxnic.generator.builder.constants.DBMetaClassFile;

import org.github.foxnic.web.generator.config.WebFullConfigs;

public class WebFullDBMetaGenerator {

    /**

    * 运行main函数生成代码

    * */

    public static void main(String[] args) throws Exception {

        WebFullDBMetaGenerator g = new WebFullDBMetaGenerator();

        g.buildDBMeta();

    }

    /**

    * 生成DBMeta数据

    * */

    private void buildDBMeta() {

        WebFullConfigs configs=new WebFullConfigs("webfull-service-example");

        DAO dao=configs.getDAO();

        DBMetaClassFile dbMetaBuilder=new DBMetaClassFile(dao,configs.getDomainProject(),configs.getProjectConfigs().getDomainConstantsPackage(),"WebFullTables");

        dbMetaBuilder.setTableFilter(table->{

            table=table.toLowerCase();

            // 仅生成以 example_ 开头的表

            if(table.startsWith("webfull_")) return true;

            return false;

        });

        dbMetaBuilder.save(true);

    }

}

执行 main 函数后,在 domain 模块内生成 WebFullTables 类,后面生成其它代码,我们会用到这个类中数据库表结构定义的常量。

非 Web 环境的代码生成

事实上,非 Web 环境的开发是很少的,但是我们在讲解 Foxnic-SQL 部分的时候,也用到了非 Web 环境的代码生成,大家参考 Foxnic-SQL 相关的文档即可。

https://gitee.com/LeeFJ/foxnic-samples 项目的 com/leefj/foxnic/sql/demo/generator 目录的

ExampleCodeGenerator.java 中有示例,大家看代码结合我们的文档,很容易理解。后面的篇幅我们着重介绍 Web 环境下的代码生成,所有 Web 环境下代码生成的原理一样适用于非 Web 环境。

为了方便理解,我们还是贴一下代码:

package com.leefj.foxnic.sql.demo.generator;

import com.github.foxnic.commons.project.maven.MavenProject;

import com.github.foxnic.dao.spec.DAO;

import com.github.foxnic.generator.builder.model.PoClassFile;

import com.github.foxnic.generator.config.ModuleContext;

import com.github.foxnic.sql.meta.DBTable;

import com.leefj.foxnic.sql.demo.app.domain.example.Address;

import com.leefj.foxnic.sql.demo.app.domain.example.Goods;

import com.leefj.foxnic.sql.demo.app.domain.example.Order;

import com.leefj.foxnic.sql.demo.app.domain.example.OrderItem;

import com.leefj.foxnic.sql.demo.config.DBInstance;

import com.leefj.foxnic.sql.demo.config.db.ExampleTables;

/**

* 代码生成器

* */

public class ExampleCodeGenerator {

    public static interface  Config  {

        void config(PoClassFile poType);

    }

    private static final String BASE_PACKAGE = "com.leefj.foxnic.sql.demo.app";

    /**

    * 需要首先运行 ExampleDBMetaGenerator 生成 ExampleTables 类

    * */

    public static void main(String[] args) {

        ExampleCodeGenerator generator = new ExampleCodeGenerator();

        // 生成商品实体类

        generator.generate(ExampleTables.EXAMPLE_GOODS.$TABLE, poType -> {

            // Goods 对象 通过 orderList 属性持有 Order

            poType.addListProperty(Goods.class,"orderList","订单明细商品","订单明细商品");

            // Goods 对象 通过 addressList 属性持有 Address

            poType.addListProperty(Address.class,"addressList","收件地址","收件地址,包括收件人以及手机号码");

            // Goods 对象 通过 itemList 属性持有 OrderItem

            poType.addListProperty(OrderItem.class,"itemList","订单明细","订单明细");

        });

        // 生成订单实体类

        generator.generate(ExampleTables.EXAMPLE_ORDER.$TABLE , poType -> {

            // Order 对象 通过 goodsList 属性持有 Goods

            poType.addListProperty(Goods.class,"goodsList","订单明细商品","订单明细商品");

            // Order 对象 通过 address 属性持有 Address

            poType.addSimpleProperty(Address.class,"address","收件地址","收件地址,包括收件人以及手机号码");

            // Order 对象 通过 itemList 属性持有 OrderItem

            poType.addListProperty(OrderItem.class,"itemList","订单明细","订单明细");

        });

        // 生成订单明细实体类

        generator.generate(ExampleTables.EXAMPLE_ORDER_ITEM.$TABLE, poType -> {

            // OrderItem 对象 通过 goodsList 属性持有 Goods

            poType.addSimpleProperty(Goods.class,"goods","订单明细商品","订单明细商品");

            // OrderItem 对象 通过 address 属性持有 Address

            poType.addSimpleProperty(Address.class,"address","收件地址","收件地址,包括收件人以及手机号码");

            // OrderItem 对象 通过 order 属性持有 Order

            poType.addListProperty(Order.class,"order","订单","订单");

        });

        // 生成地址实体类

        generator.generate(ExampleTables.EXAMPLE_ADDRESS.$TABLE, poType -> {

            // Address 对象 通过 goodsList属性 持有 Goods

            poType.addListProperty(Goods.class,"goodsList","订单明细商品","订单明细商品");

            // Address 对象 通过 orderList 持有 Order

            poType.addListProperty(Address.class,"orderList","收件地址","收件地址,包括收件人以及手机号码");

            // Address 对象 通过 itemList 持有 OrderItem

            poType.addListProperty(OrderItem.class,"itemList","订单明细","订单明细");

});

}

    /**

    * 按表生成

    * */

    public void generate(DBTable table) {

    generate(table,null);

    }

    /**

    * 按表生成

    * */

    public void generate(DBTable table,Config config) {

        DAO dao = DBInstance.DEFAULT.dao();

        MavenProject project = GeneratorUtil.getProject();

        String pkg = table.name().split("_")[0];

        String prefix = pkg + "_";

        ModuleContext context = new ModuleContext(GeneratorUtil.initGlobalSettings(),table,prefix,BASE_PACKAGE + "." + pkg);

        context.setDomainProject(project);

        context.setServiceProject(project);

        context.setDAO(dao);

        if(config!=null) {

        config.config(context.getPoClassFile());

        }

        context.buildPo();

        context.buildVo();

        context.buildService();

    }

}

Web 环境的代码生成

Web 环境的代码生成本文以 webfull 项目为示例,  它以 WebFullCodeStarter 开始,初始化代码生成器,各模块配置,最后按生成用户指定的模块代码。代码生成的模块配置是按表配置的,一个表对应一套代码。我们先来看一下 WebFullCodeStarter 的代码:

package org.github.foxnic.web.generator.module;

import com.github.foxnic.generator.util.ModuleCodeGenerator;

import org.github.foxnic.web.generator.module.bpm.ExampleReimbursementConfig;

import org.github.foxnic.web.generator.module.mall.ExampleAddressConfig;

import org.github.foxnic.web.generator.module.mall.ExampleGoodsConfig;

import org.github.foxnic.web.generator.module.mall.ExampleOrderConfig;

import org.github.foxnic.web.generator.module.mall.ExampleOrderItemConfig;

/**

* 代码生成启动类

* */

public class WebFullCodeStarter extends ModuleCodeGenerator {


    public static void main(String[] args) {

        // 新建启动类对象

        WebFullCodeStarter g=new WebFullCodeStarter();

        // 初始化本次需要生成代码的模块

        g.initModules();

        // 启动

        g.start();

    }

    /**

    * 初始化本次需要生成代码的模块

    * */

    public void initModules() {

        initExampleModules();

        initBPMModules();

    }

    /**

    * 初始化 BPM 示例模块

    * */

    private void initBPMModules() {

        this.addConfig(new ExampleReimbursementConfig());

    }

    /**

    * 初始化订单示例模块

    * */

    private void initExampleModules() {

        this.addConfig(new ExampleGoodsConfig());

        this.addConfig(new ExampleAddressConfig());

        this.addConfig(new ExampleOrderConfig());

        this.addConfig(new ExampleOrderItemConfig());

    }

}

启动 WebFullCodeStarter 的 main 函数,输出如下:

输入 ALL 生成全部

或输入需模块序号: (1) webfull_example_goods (2) webfull_example_address (3) webfull_example_order (4) webfull_example_order_item (5) webfull_example_reimbursement

|

此时,输入 all 或 a 生成列表中全部模块的代码,如果输入指定模块的序号,则社能成指定模块的代码。

从这个动图中,我们不难发现,代码生成器不必重启可以反复生成。针对一些界面的生成,可以边修改代码生成的配置,边生成边测试,我们把它叫做迭代式的代码生成。

为了能够反复进行代码生成,我们针对模块代码的结构做了一些特殊的设计,尽量把业务逻辑的代码剥离到某个单独的文件,其它文件则可以反复生成,这种代码生成的方式极大的提高了开发效率。

代码生成的结果

某个模块的代码生成后,在项目的各个 Maven 子模块下生成相应的 Java 类、Html 文件、js 文件。下面对各个文件说明如下:

文件类型示例位置说明

POOrder.javadomain与数据表对应的实体类。

POMetaOrderMeta.javadomain针对 PO 类的元数据描述,包含一个 PO Proxy 内部类。

VOOrderVO.javadomain用于 API 接口传参、返回等。

VOMetaOrderVOMeta.javadomain针对 VO 类的元数据描述,包含一个 VO Proxy 内部类。

自定义模型CustomModeldomain代码生成时自定义的模型类。

ProxyOrderServiceProxy.javaproxy接口代理、Feign代理接口、接口路径定义。

服务接口IOrderService.javaservice服务接口

服务实现OrderServiceImpl.javaservice服务接口实现

流程支持OrderBpmEventAdaptor.javaservice可选;开启流程审批时会生成,处理流程逻辑。

API控制器OrderControllerserviceRest API 接口控制器

页面控制器OrderPageControllerview页面控制器

列表页order_list.htmlview列表页,表格展示数据。

列表页JSorder_list.jsview列表页对应的JS文件

表单页order_form.htmlview表单页,表单方式展示、编辑数据。

表单页JSorder_form.jsview表单页JS

逻辑扩展JSorder_ext.jsview剥离表单、表格业务逻辑,使页面代码可反复生成。

从上面的表格我们可以看到,一个数据表对用的简单模块,就包含可诸多文件。其中,黑色加粗的几项不建议手动修改,如要修改可以通过代码生成的方式重新生成。

页面如果有业务逻辑,尽量到 xxx_ext.js 文件修改,因为一旦修改也列表页或表单也的代码就无法再次重新生成代码。

项目依赖

Foxnic-Web 代码生成的方式是迭代式的,所以被生成的代码需要依赖到代码生成的项目中。以 webfull 项目的 generator 项目为例,在它的 pom 文件中需要加入 domain、proxy、service  和 view 的依赖。

<dependencies>

  <!-- 通用基础模块 -->

  <dependency>

    <groupId>com.github.foxnic.web</groupId>

    <artifactId>framework-boot</artifactId>

    <version>${foxnic.web.version}</version>

  </dependency>

  <dependency>

    <groupId>com.github.foxnic.web</groupId>

    <artifactId>framework-cloud</artifactId>

    <version>${foxnic.web.version}</version>

  </dependency>

  <dependency>

    <groupId>com.github.foxnic</groupId>

    <artifactId>foxnic-generator</artifactId>

    <version>${foxnic.version}</version>

  </dependency>

  <!-- 当前项目基础模块 -->

  <dependency>

    <groupId>com.github.foxnic.example.webfull</groupId>

    <artifactId>webfull-proxy</artifactId>

    <version>${project.version}</version>

  </dependency>

  <dependency>

    <groupId>com.github.foxnic.example.webfull</groupId>

    <artifactId>webfull-domain</artifactId>

    <version>${project.version}</version>

  </dependency>

  <dependency>

    <groupId>com.github.foxnic.example.webfull</groupId>

    <artifactId>webfull-framework</artifactId>

    <version>${project.version}</version>

  </dependency>

  <!-- 当前项目业务模块 -->

  <dependency>

    <groupId>com.github.foxnic.example.webfull</groupId>

    <artifactId>webfull-service-example</artifactId>

    <version>${project.version}</version>

  </dependency>

  <dependency>

    <groupId>com.github.foxnic.example.webfull</groupId>

    <artifactId>webfull-view-example</artifactId>

    <version>${project.version}</version>

  </dependency>

</dependencies>

小结

本节主要介绍了在 Foxnic-SQL 和 oxnic-Web 代码生成的基本步骤,以及代码生成的文件分布,具体作用等。尤其要注意是代码生成项目的依赖问题,可能导致异常而无法正确生成代码。

后面的章节中,我们将进一步介绍代码生成的细节。

相关项目

https://gitee.com/LeeFJ/foxnic

https://gitee.com/LeeFJ/foxnic-web

https://gitee.com/lank/eam

https://gitee.com/LeeFJ/foxnic-samples

官方文档

http://foxnicweb.com/docs/doc.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容