基于模板的代码生成器设计思路

本文探讨了基于模板的代码生成器设计,包括代码生成工具的五大要素:模板参数、生成控制器、模板引擎、模板。文章分析了模板参数与模板的关系,并详细解释了模板引擎的三种工作模式。此外,还讨论了工具的可扩展性,如对象的挖掘、对象映射、代码文件的生成方式,以及未来需要解决的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  在讨论代码生成器之前,首先,让我们抛开这个主题,来看看一家工厂。在这家工厂里,有一个很大的厂房,厂房里有好几条生成线在运作,人来人往,川流不息又井井有然。一些工人在搬运各种各样的原材料;一些工人在调试和安装模具;一些工人在流水线上完成各自的工序;一些工人在将成品入库。当然,这可能不是一家现实的工厂,现实的工厂会比这个复杂多,这只是工厂的抽象与简化。

         那么这个抽象的工厂对于我们的代码生成器有什么启发意义吗?

         首先,让我们先引进几个术语:代码生成工具、生成控制器、模板参数、模板引擎、模板。

         我们暂且不给出这些术语的定义。参考工厂的生产模式,我们可以将代码生成描述为,在代码生成工具(对应为工厂)内有好几个生成控制器(对应为流水线),每一个生成控制器利用模板引擎(对应为模具)读取并分析模板(对应为原材料),然后结合模板参数(对应为原材料),最终生成代码文件(对应为产品)。

         是不是有些相似性?而上面引入的5个术语就是基于模板的代码生成器的5大要素。全文都围绕着这5大要素展开。

        

         知道了5要素后,我们一个一个来分析他们。

         首先是模板参数。我们首先问自己,什么是模板参数?它有哪些形式?

         模板参数,顾名思义,它是针对模板而言的,它依赖于模板,它和模板之间有机的结合,会产生丰富多彩的代码文件。可以说,模板和模板参数之间是相互依存的,谁离开了对方都会成为死物,只有当二者结合,才能产生生命力。但是,除开这些空洞的描述,我们能具体的描述什么是模板参数吗?我们能列举并归纳它有那些形式吗?我无法给它一个准确的定义,就像这个世界的代码一样,它似乎有无数种。现在好像进入了死胡同,哪里是突破口呢?

         我们为什么要直接去分析它呢,可不可以从另外一个角度来分析、归纳呢?我们都知道,我们工具要实现的目标是生成代码,也就是说,我们的关注焦点是代码,是程序。而以OO的观点而言,程序是什么?它是由对象组成的。所以,可以说,我们工具要实现的目标是生成’对象’。我们都知道,对象包含了属性和行为,我个人觉得还可以扩展一点,对象的展现。既然我们的整个工具都是围绕对象而运作,那么对于模板参数而言,它也离不开这个范围。

         最后,我们可以给模板参数下一个定义,模板参数是对象的属性、行为、展现的描述或抽象。接着,我们可以给模板下一个定义,模板是对象实现的描述。

         其次是模板引擎,它的作用是什么?有哪些方式?

         模板参数、模板引擎和模板3者相互作用的方式,我们可以划分为3种。一种,我称之为push模式,在这种模式下,生成器将获得的模板参数推入(push)到模板中。这种模式的特点是,对象实现的逻辑由模板来描述,生成器仅仅作为一个推动的工具,利用模板引擎将模板参数推入到模板中,最终生成所需要的代码文件。另外一种,我称之为pull模式,在这种模式下,生成器首先将读取(pull)模板的内容,用模板引擎分析它,然后将分析后的模板与模板参数结合,最终生成所需要的代码文件。这种模式的特点时,生成器本身需要包含一定的代码生成业务逻辑。最后一种我称为混合模式(mix),在这种模式下,生成器首先将模板参数推入模板中获取代码片段,然后根据生成器根据本身的业务逻辑生成代码并输出,在这种模式下,生成器同样也要有一定的代码生成业务逻辑。

         所以,我们可以把生成器看成是模板参数、模板引擎和模板之间的联系纽带。通过生成器,我们可以分隔不同的代码生成方式,从而降低耦合度。

         最后是工具。对于工具而言,最重要的是可扩展性。它主要表现为:

         1.   能够不断的添加、删除、更新生成器。

         2.   能够支持以不同的方式获取业务对象。可以是从数据库中、从文本文件中、从Xml文件中。等等方式。

         3.   能够支持多种生成方式,可以是按层生成,可以是按对象生成。等等。

        对象

       也可以称为业务对象。它主要包含属性、行为和展现。对于代码生成工具而言,主要的问题是挖掘对象、获取属性、定义行为、定义展现、对象映射。

         挖掘对象

         是工具工作的第一步。对于工具而言,对象的挖掘需要高扩展性。初步列一下,有以下几种方式:

         1.通过数据库来挖掘对象。(初期支持SqlServer2000和SqlServer2005)

         2.通过Xml文件来挖掘对象。

         3.自定义对象。

        对象映射

         举个例子来说,我们现在有一个学籍系统,其中包含了学生对象,它的属性有姓名、性别、年龄等等。学生所拥有的行为有上课、写作业,同时,为了持久化学生对象,我们还会有新建学生、修改学生、删除学生、查询学生的行为。那么这些属性和行为是否都集中放在一个类中呢?

         为了使整个应用程序结构良好,我们通常都会划分几个层。假设,我们现在将应用程序划分为实体层、业务逻辑层、数据访问层、展示层。这样一来,学生对象将在各个层上映射为学生实体对象,学生业务逻辑对象,学生数据访问对象。这,我称之为对象映射。

         对象属性

         对象的属性可以划分为持久化属性、业务属性与展现属性。一个对象的属性可以同时为持久化属性、业务属性和展现属性。

         持久化属性:顾名思义,就是最终将会持久化到数据库(或者其他文件)中的属性。这些属性一般通过数据库来获取。

         业务属性:为了满足业务逻辑而定义的属性,一般由用户自己定义。

         展现属性:用于对象展现时的属性。

         对象行为

         对象的行为可以划分为持久化行为、业务行为与展现行为。与对象属性划分有区别的是,一个行为只能属于一种类型。

        模板参数

       模板参数其实是一个由层次对象构成的对象。对象模型如下图所示:


       模板参数由工具创建、初始化、填充对象并传递给生成器。然后由生成器自己决定是将模板参数传递给模板引擎,还是在生成器内部进行解析。

       模板

       是模板文件及内容在应用程序中的映射。由于在框架内允许用户自定义模板引擎。因此,对模板的内容不做任何限制。但是因为系统需要通过模板来找到对应的模板引擎,顾每个模板都唯一指向一个能解析它的模板引擎。这一点通过对模板文件的强制性命名规则来实现:

          模板文件的后缀名一定要为.template

          模板的文件名的格式为:*.LanguageCode.TemplateEngineeCode

         其中LanguageCode表示模板所对应的语言编码。TemplateEngineeCode表示模板引擎编码。这2项中LanguageCode属于可选项而TemplateEngineeCode是必选项。系统解析时,首先将文件名按句号(.)切割。然后从后往前,首先确认截取的TemplateEngineeCode部分是否为已知的模板引擎编码。如果不是,那么匹配过程结束,系统认为整个模板文件名中没有定义LanguageCode和TemplateEngineeCode。如果是,那么系统继续往上搜索匹配。

         所有的模板类都必须继承自AbstractTemplate,接口签名如下:

/// <summary>
/// 模板接口
/// </summary>

public   abstract   class  AbstractTemplate
{
    
/// <summary>
    
/// 模板所对应的语言编码
    
/// </summary>

    public abstract string LanguageCode{ get;}
    
/// <summary>
    
/// 模板引擎编码
    
/// </summary>

    public abstract string TemplateEngineeCode{ get;}
    
欢迎访问我的博客关注本代码生成器的最新动态: http://blog.youkuaiyun.com/lk_blog 代码生成器使用帮助:http://pan.baidu.com/s/1bnxOHGF 代码生成工具使用前置条件:系统安装JDK并配置JAVA_HOME环境变量。 功能: 一、数据库生成实体类. 1.配置 (1)本工具提供了3中连接数据库的方式,分别是:MySql、Oracle、SqlServer,在界面上给出连接示例,方便进行复制和修改(减少因错误配置连接方式而带来了不能链接数据库问题)。配置正确后即可正确读取出数据库中的相关表和表中字段。 (2)生成的代码路径可根据需要进行灵活配置,如果没有配置默认为:d:\.LKGenerator\dest-beans (3)提供了根据数据表生成代码的相关配置,可以配置去掉表前缀(如t_),去掉表后缀,去掉表名内容下划线(业界习惯表命名中用下划线区分单词),大写表名下划线后的第一个字母,去掉属性中下划线并大写其后第一个字母,这样可以使生成的代码更符合业界代码命名习惯,如,用户和角色的关联关系表常被命名为:t_user_role,表中字段有user_id和role_id,按上面的配置后生成的代码为UserRole.java,类中的属性为userId和roleId. 2.选择表 前置条件:本功能要求第一步配置数据库连接方式能正确连接到数据库。 本功能可列出所配置数据库的所有表,请选择所需生成代码的表后点击确认按钮(注:如果重新配置步骤1的数据库连接后需刷新列表)。 3.生成实体类代码 前置条件:步骤2中选择了需要生成代码的表。 本功能可根据步骤2中选择表进行实体Beans代码的生成,生成的代码默认包含AutoBean和AutoField的注解配置(AutoBean和AutoField注解非生成代码的必须项,如果配置了可以在生成代码时时为模版注入该配置,增强模版的扩展性)。 4.编译实体类 前置条件:系统安装JDK并配置JAVA_HOME环境变量 本功能可以对Java代码进行编译(注:本功能的使用不必要依赖步骤3中的代码,可对任何正确的java代码进行编译,如果代码中有引用第三方的jar,请将jar放置到d:\.LKGenerator\.lib文件夹下。) 5.导出数据到Excel 前置条件:执行第三步后已生成带注解的JavaBean. 本功能只要使用地三步的配置注解的JavaBean就能将数据表中的数据导出。 6.从Excel导入到DB 前置条件:执行第三步后已生成带注解的JavaBean. 本功能只要使用地三步的配置注解的JavaBean就能将Excel中的数据导入到数据表中。 7.生成数据表文档 前置条件:步骤2中选择了需要生成文档的表。 本功能可以根据步骤2中选择的表进行excel文档的生成。 8.清空Beans代码目录。 本功能可以清空生成代码的文件夹,主要为了增强用户体验,让使用者方便打开Beans文件夹并对代码进行修改。 9.打开Beans代码目录 本功能可以打开生成代码的文件夹,主要为了增强用户体验,让使用者方便打开Beans文件夹并对代码进行修改。 二、实体类根据模版生成最终代码. 功能简介:根据实体类代码结合模版生成最终代码,代码模版规范参考模版框架velocity的规范和样例。 实现思路:加载实体类的class文件到classLoader中,读取实体类的属性和注解,将类名、属性字段名、注解三者与模版进行结合生成最终的代码。 1.配置 (1)配置模版路径,这个配置可以配置用于生成代码的velocity模版路径,生成的代码将模版中的特定符号见“模版中可使用的变量”中的描述。 (2)配置生成代码的固定文件路径,这个配置中的文件将原样拷贝到最终生成的代码中,主要适用于非文本文件如*.jpg *.jar等文件。 (3)配置引用jar路径,这个jar路径的配置是需要加载的class需要引用的jar. (4)注入模版的Bean(*.class)路径,这个路径下需放置实体Bean的class文件。 (5)配置生成代码的根路径,这个路径即最终生成代码的路径。 (6)配置注入模板的键值,这个功能可以配置除默认注入到模版的变量之外的特殊变量,例:author=李坤,模板可用变量${author},base-package=com.tgb.lk,则模版中可使用${base-package} (7)读取数据库中的数据用于生成代码。 本配置主要控制是否开启读取配置数据库中数据后作为模板的输入功能。开启本功能将自动将数据库中的数据转换成JavaBean并将此JavaBean作为模板变量的输入用于生成代码模板中可使用的通用变量: ${base-package}包名的默认变量,可在步骤(5)中进行属性值的设置,可以放到路径中,路径中使用时将把配置的点(.)转为路径分隔符(/)使用. ${beans}实体类的类名集合. ${bean}实体类类名首字母变为小写. ${bean.Lname}同${bean},实体类类名首字母变为小写. ${bean.Uname}实体类类名首字母变为大写. ${bean.ALname}实体类类名全部变为小写字母. ${bean.AUname}实体类类名全部变为大写字母. 注:${bean},${bean.Lname},${bean.Uname},${bean.ALname},${bean.AUname},${base-package}可以用于模版中也可以用于路径和文件的命名. 注解增强Bean属性,需配置@AutoBean(alias="xxx",table="xxx",args={"xxx","yyy"}): ${bean.alias}实体类注解中的alias值,如果实体类上没有配置,则默认为类名.可配置中文注释,生成代码时显示该注释. ${bean.table}实体类注解中的table值,对应数据库中的表名. ${bean.argsLength},${bean.argsSize}bean注解中的args参数数组配置的参数个数. ${bean.args[0]}bean注解中的args参数数组配置中的第一个值. ${bean.fields}实体类的字段集合,可对其进行循环. ${field}实体类属性名. ${field}实体属性首字母变为小写. ${field.Lname}同${bean},实体类属性首字母变为小写. ${field.Uname}实体类属性首字母变为大写. ${field.ALname}实体类属性全部变为小写字母. ${field.AUname}实体类属性全部变为大写字母. 注解增强Field属性,需配置 @AutoField(alias = "序号", isKey = true , isRequired = true , type="String", combo = {"男","女"},args={"xxx","yyy"}) ${field.alias}实体属性注解中的alias值,默认为属性名.可配置中文注释,生成代码时显示该注释. ${field.isKey}实体属性注解中的isKey值,boolean类型,配置后可以使用${bean.key}变量. ${field.isRequired}实体属性中的isRequired属性,该属性可以配置该字段是否为必填项. ${field.type}实体类属注解中的类型,例如String,Integer... ${field.comboLength},${field.comboSize}实体属性注解中combo参数数组配置的参数个数. ${field.combo[0]}实体属性注解中combo参数数组配置中的第一个值. ${field.argsLength}}实体属性注解中的args参数数组配置的参数个数. ${field.args[0]}实体属性注解中args参数数组配置中的第一个值. ${bean.datasLength} = ${bean.datasSize}bean中读取数据表中的内容并转为类的对象,模板中可以使用. #foreach($data in ${bean.datas}) ${data.uuid},${data.name}... #end 2.从实体类生成代码 前置条件:步骤1中的(1)中配置了可用的velocity模版,步骤1中(4)中有可用的*.class文件。 本功能用于生成代码。 3.打开生成代码目录 本功能可以打开生成代码的文件夹,主要为了增强用户体验,让使用者方便打开Beans文件夹并对代码进行修改。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值