代码生成器在线(手把手带你实现一个代码生成器)

前言

不知道各位读者是在工作中陷入疯狂的简历代码,还是看了密密麻麻的课不想干了,还是在底层耗了很多时间。以笔者为例,我们经常会遇到以下两个问题:

  • 每隔一段时间就需要构建一个新的应用,需要各种复制粘贴(缺少定制的脚手架)。
  • 有很多新的需求,比如实体、Bean、请求、响应、、道、服务、业务,这些都需要写,但是我一点都不想做。
  • 很多时候,一些关键的注释,比如@Service,在复制粘贴代码的时候甚至会漏掉,导致项目无法启动,然后花更多的精力去排查。因此,本文将以实际工程代码为例,构建一个可定制、高度可扩展的代码生成器


    项目目标

    本项目基于生成器项目,将生成一个可以直接运行/一键轻松复制的SpringBoot成品项目,包括基础数据库操作、业务操作、Web界面、视图层。

    同时支持插件自定义实现,扩展性高。以下是该项目的基本结构:

    代码-generate ┢-POM . XML ┢-src ┢-main └-Java ┖││╻─bean ┗╻╻╻─ClassInfo.java//class实体(对应表维度) ┗╻─ConfigurationInfo.java//Configuration中心[/hLayUiHtmlImpl.java//Custom引擎的Java ││└-impl │││││^│││└──case 2(查看界面) │└└──util │└├───DataBaseUtil.java//database依赖 │├h/]│├──IOTools.java//tools ┖┖──StringUtil.java//tools ┖┖┖┖──资源 ┖┗┗╻__╻__应用。│ │ │ ├── log4j2.xml //日志配置 ││└──模板//模板目录 │└──测试 └└─── meta-INF

    编码构建配置中心

    由于本项目涉及数据库操作层,除了目标目录、项目名称、作者、根目录等基本参数外。,还需要数据库相关的配置等。配置文件如下所示:

    #数据库IP,数据库驱动,代码,用户名,密码 IP = 127 . 0 . 0 . 1 port = 3306 Driver = com . MySQL . JDBC . Driver Database = school-Miao encoding = UTF-8[/]no .隔离如:A;b;c;d; include = *; #项目名 项目名= demo #包名 包名= com.demo #作者 作者路径= f:代码 #自定义句柄包含项,现有自定义模块:DataMdImpl,LayUiHtmlImpl,*默认包含全部,带;号隔离如:A;b;c;d; customHandleInclude = DataMdImpl;LayUiHtmlImpl 在考虑好基本的配置内容后,通过读取文件配置,复制代码,将相关信息放入配置中心,这样就不再显示具体的代码,可以直接查看需要的源代码(文末链接)。

    这一阶段涉及的班级:ConfigurationInfo.java、GlobleConfig.java和PropertiesFactory。

    入口:com . MySQL . engine . abstract engine # init


    基于数据库获取表字段信息

    获得了上面的目标数据库的配置信息后,我们就可以在这个阶段连接到数据库,并通过通用SQL获得目标数据库的表和字段信息。

    以下面的SQL为例,只要得到数据库连接加上库信息,就可以得到所有的表名

    SELECT table _ name FROM information _ schema。TABLES 其中 table _ schema = & # 34;学校-苗-demo & # 34;#库名 和table _ type = & # 34基础表& # 34;; # Response # schools 复制代码同样,指定数据表的所有字段及其类型也可以通过通用SQL获得:

    SELECT column_name, data_type, column_comment, numeric_precision, numeric_scale, character_maximum_length, is _ nullable nullable FROM information _ schema。列 ,其中 table _ name = & # 39;学校& # 39;#指示 和table _ schema = & # 39学校-苗-demo & # 39;;#库名 #结果是 # column _ name data _ type # sname varchar 复制代码获取表字段的类型,然后存储在配置中心。重点是要注意数据类型的映射,比如Varchar映射String,int映射Integer等等。Sid,我们需要注意数据库字段的设计规范。


    基于模板生成文件

    这是这个项目的重点。如果要实现可配置的代码生成器,必须有一个前提:配置本身。回想一下,很多年前我们第一次学习JSP的时候,是不是觉得JSTL的表情相当惊艳?可以完美的混合Java语言和HTML语言。虽然现在已经不用了,但是这种模板化的思路和工作方法正好可以用在这里。

    通过调查发现,在类似JSP的技术中,FreeMarker完全符合我们的预期,其中:

    freemarker . template . template # process(Java . lang . object,java.io.Writer)

    方法,可以帮助我们通过指定模板文件(FTL)、Java实体和目标文件来填充内容,类似于JSTL,如下:

    包$

    。实体; 导入Java . io . serializable; 导入龙目岛。数据; 导入Java . util . date; 导入Java . util . list; /* * * $ { class info . class comment } * @ author $ { author name } $ {。现在吗?字符串(& # 39;yyyy-MM-DD & # 39;)} */ @ Data public class $ { class info . class name }实现Serializable { private static final long serialVersionUID = 1L; & lt;#if classInfo.fieldList?存在&& classInfo.fieldList?size gt 0 & gt & lt;# list class info . field list as field item & gt; /* * * $ { field item . column name } $ { field item . field comment } */ private $ { field item . field class } $ { field item . field name }; & lt;/# list & gt; & lt;/# if & gt; } 复制代码。上面代码中的packageName、classInfo.classComment、classInfo.className等等都是我们之前获得的配置信息。

    & lt# list & gt也就是FreeMarker中的迭代标签,我们只需要根据自己的需要,省去改动的部分就可以了。


    实现拔插接口

    按照上面的进度,已经可以用模板实现项目的生成了(当然还需要配置模板目录),但是如何实现高可扩展性和插件接口呢?

    有以下想法:

  • 基于SPI的方式提供外部接口
  • 基于反射获取指定类的实现类。
  • 不知道SPI是什么的小伙伴可以看看这篇文章:《了解》Java SPI机制。

    因为这个项目不希望其他项目依赖它,所以采用第二种方法,借用著名的reflections包来实现类扫描。

    Maven官方地址:mvnrepository.com/artifact/or…



    核心代码如下:

    公共类CustomEngine { /* * * *扫描整个包以获取类 */ 私有静态集< Class & lt?扩展客户引擎& gt& gttoDos(){ Reflections Reflections = new Reflections(new configuration builder() 。setUrls(class path helper . for package(& # 34;")) 。filterInputsBy(input-& gt;{ 断言输入!= null return input . ends with(& # 34;。班& # 34;); })); return reflections . getsubtypesof(custom engine . class); } public static void handle custom(){ Set & lt;Class & lt?扩展客户引擎& gt& gtclasses = toDos(); for(Class & lt;?扩展客户引擎& gtAClass: classes) { //如果(& # 34;*;"。equals(globle config . getgloble config()。getCustomHandleInclude())| | globle config . getgloble config()。getCustomHandleIncludeMap()。contains key(a class . get simplename()){ try { //基于反射构建对象-调用句柄方法 custom Engine Engine = a class . new instance(); engine . handle(globle config . getgloble config()、class info factory . getclassinfolist()); } catch(instantiation exception | IllegalAccessException e){ e . printstacktrace(); } } } } } 复制代码。比如我们实现数据库的文档模板类,就可以完美的达到扩展的效果。这些模板如下:

    #基本介绍 |描述|内容| |: - | |项目名称| ${config.projectName} | |作者| $ #if classInfos?存在&& classInfos?size gt 0 & gt & lt;#将classInfo作为class info列出& gt ## ${classInfo.tableName}表结构描述 |代码字段名|字段名|数据类型(代码)|数据类型|长度|可空|注意| |:-# list class info . Field list as Field item & gt; | $ { field item . field name } | $ { field item . column name } | $ { field item . field class } | $ { field item . datatype } | $ { field item . maxlength } | $ { field item . nullable } | $ { field item . field comment } | & lt;/# list & gt; & lt;/# list & gt; & lt;/# if & gt; 显示复制代码效果:




    成果检验

    说了这么多,我们直接来看看效果吧~




    一点探索:FreeMarker 如何实现的模板解析

    这个项目的重点在于FreeMarker帮助我们实现了从模板内容生成文件这一最复杂的步骤,其特点与JSP的JSTL语法非常相似。现在,我们来研究一下它的底层实现原理,看看有没有值得借鉴的地方。

    从由易到难的角度来看,你会觉得实现通常只支持文本替换的JSTL语法很难吗?在我看来,并不难。我们只需要指定特殊的语法,触发我们的解析规则,然后用我们存储的实体对象映射内部字段,然后输出它们。例如:

    //我是测试#{demo.name} //逐行读取,触发# {}正则匹配时,用Map/其他数据结构的值替换demo.name即可。 复制代码简单解决。再想想麻烦。如何解决下面的需求?

    & lt# list class info . field list as field item & gt; /* * * $ { field item . column name } $ { field item . field comment } */ private $ { field item . field class } $ { field item . field name }; & lt;/# list & gt; 复制代码假设,< # list & gt如果标签不一致,就会出错(有正面标签,没有背面标签)。你会想到什么?

    是不是有点类似于LeeCode算法中生成括号的问题?在那道题中,要求()周围生成的括号必须一一匹配,所以我们在做这道题时,必然会想到使用stack的数据结构。让我们看看FreeMarker是如何实现的。代码如下:

    /* * * freemarker . core . environment # visit(freemarker . core . template element) * & # 34;访问& # 34;Templateelement。 */ void visit(template element元素)throwsioexception,template exception { //用于存储数据的临时数组 push element(element); try { //构建子元素集 templatelementstovisit = element . accept(this); //遍历子元素 if(templatelementstovisit!= null){ for(templatelementel:templatelementstovisit){ if(El = = null){ break;//跳过未使用的尾随缓冲区容量 } //递归遍历 visit(El); } } } catch(template exception te){ handleTemplateException(te); } finally { //移出数据 popElement(); } } 复制代码。它的设计思想很简单,就是将文本分成行,然后逐行递归遍历。调试原理图如下。下面的文本中有13行元素节点,开始逐行遍历。



    说到本文中迭代的父代,也就是,



    至此,迭代器处理模式已经完成,关键在于如何识别哪个代码属于迭代,是否类似于生成括号~

    代码生成器


    总结

    这个项目的主要核心是利用mysql内置的表字段查询和FreeMaker模板来构建规则和通用的代码内容。技术要点包括但不限于:

  • 原理分析
  • Mybatis原生XML,包括添加、批量添加、删除、批量删除、多条件分页查询、列表查询、单个查询、单个数据修改等。
  • 回溯日志
  • 跳羚
  • 插件拦截器(基于org.reflections实现)支持扫描指定接口。
  • 项目源代码(支持基础模块、HTML模块和数据库文档模块):GitHub地址


    作为一个程序员,合理的偷懒是很自然的!


    作者:Kerwin_
    链接:https://juejin.cn/post/7010560347824193543
    来源:掘金
    版权归作者所有。商业转载请联系作者授权,非商业转载请注明出处。

    您可以还会对下面的文章感兴趣

    使用微信扫描二维码后

    点击右上角发送给好友