如何搭建合适的Web框架?

以前在Web开发框架推导一文中咱们一步步的搭建了一个开发框架。前端

如何搭建合适的Web框架?

在当时的状况下,还算知足需求。可是随着项目的逐渐完善,需求变动的频度逐渐变得比新增需求的频度高,原来框架的弊端愈来愈明显,因此须要对框架进行升级改进。面试

咱们先来看原来框架的问题,而后基于这些问题,来对框架进行改进。sql

原框架的问题

  • 代码生成问题
  • 参数传递问题
  • Service层问题
  • 测试依赖问题
  • Mapper.xml的问题

代码生成问题数据库

在原框架中,咱们基于各类约束,编写了一个代码生成组件,经过这个组件,咱们能够针对选中的表来生成Controller,Service,Model,Mapper等一系列的类,也就是说,只要建完表,就能够直接生成一套CRUD,直接就能够启动并测试。这在项目初期看起来很美,可是在需求变更时,仍是有不少的局限性。markdown

首先,生成的代码逻辑是固化的。若是稍微有些调整,就须要调整生成代码的组件,而后从新打包,上传到jar仓库,项目修改组件版本,再进行代码生成,整个流程过于繁琐。app

其次,为了方便代码的生成,实际上是作了很多妥协的:框架

  • 为了方便在修改表字段之后,可以从新生成,不少类都抽象了一个基类用于操做Model字段。这些基类不可以手动修改,由于每次生成都会覆盖。这实际致使了类的数量的增多。
  • 生成的CRUD固化了,不能手动调整。若是生成的CRUD不知足需求,不能直接在代码上修改。只能拷贝一份进行修改,由于再次生成时会覆盖。这致使了代码的冗余。
  • Param和Result委托了Model,这在Model发生改变时,能在编译期就能知道对应字段的调整。可是也引入了很多问题,咱们在「参数传递问题」一节单独讨论。

参数传递问题模块化

当初为了便于代码的生成,决定Param和Result都继承Model,这致使了以下的一些问题:工具

  • 使得Param和Result都依赖了Model。可是Param和Result是视图层模型,而Model是持久层模型,二者的进化度并非一致的。可是如今的继承关系致使了在默认状况下视图层模型的进化须要和持久层同步,固然你也能够手动调整Param和Result,可是这又致使了代码生成的优点没有了。
  • Param和Result经过委托的方式来设置字段,也就是说,它们实际是没有字段的,经过getter和setter将值设置到了Model中。这就无法使用lombok来简化getter和setter,使得Param和Result代码行数较多
  • 同时,对于swagger来讲,有些注解须要基于字段,致使某些功能没法实现(例如:ModelAttribute),只能基于额外手段来处理(例如:须要经过ApiImplicitParams来实现字段文档)。
  • CRUD都是基于同一个Param和Result,致使前端的接口会显示不少无用的字段,加大前端理解接口的难度

Service层问题oop

Service层有以下问题:

  • Service层的职责太重,包括了事务处理、参数设置、业务逻辑

  • 致使Service中的代码是面条代码,不利于业务逻辑的理解

  • 同时事务注解是直接加在类上的,Spring的默认事务机制会致使相似以下代码的逻辑调用不会抛出指望的异常

    // PostService public String savePost(Post post) { postRepository.save(post); for(PostDiscuss discuss : post.getDiscuss()) { // 这里是抓不到RuntimeException异常的,会是一个TransactionRollBack的异常 discussService.save(discuss); } } // discussService public String savePost(PostDiscuss discuss) { throw new RuntimeException("保存失败"); }

测试依赖问题

核心的业务逻辑在Service中,测试仍是须要依赖于Spring,当项目愈来愈大时,启动项目的时间愈来愈长,可能要1分钟甚至更长。这就致使单元测试效率愈来愈低。

Mapper.xml的问题

在面试的时候,我常常会问下面的一些问题:

  • Java里面接口的做用是什么?
  • Service、DAO为何要编写接口,再去实现这个接口?
  • 接口和实如今相同的模块下,反正都要从新打包的。多写个接口不是多写了好几行代码吗?
  • 和上面相似的问题,Mybatis里面,声称将sql独立到了Mapper.xml文件中,使得能够不须要编译直接修改sql。但Mapper.xml都是和Class放在一块儿的,改了仍是须要从新打包,并且Mybatis是不能动态加载Mapper.xml的,那把sql独立到XML里,到底有什么优点?

对于最后一个问题,个人答案是,对于大部分项目来讲,没什么优点项目易不易于部署、扩展,不在于你使用的框架,而在于你的设计

就以Mapper.xml来讲,Mybatis将sql与代码分离了,可是你在项目里仍是将Mapper.xml和代码放在同一个模块下,那这个优点就没有了。既然没有这个优点,咱们还有必要单独写Mapper.xml文件吗?个人选择是,那就不写了,直接使用Mybatis提供的注解。

同时为了解决Service层对DAO层(这里也就是对Mybatis)的强依赖,对框架进行了一些改进,解耦Service和DAO层。具体见下面的改进方案。

框架改进方案

为了解决上面这些问题,对框架进行了以下调整:

  • 分离Param、Result和Model
  • 替换代码生成
  • 独立业务逻辑
  • Model层优化

分离Param、Result和Model

上面已经提到了Param、Result和Model强耦合会有不少问题,因此这里就将Param、Result和Model分离开。每一个都是独立的Bean,这就解决了上面几个问题。可是引入了两个新问题:

  • 首先,很明显的,增长了手动编码的量。当一个表修改了字段,须要修改三个类甚至更多的类
  • 其次,增长了数据传递之间的代码。即Param传递到Model,须要对字段赋值。若是一个字段一个字段的设值,会增长不少无聊的代码。而使用反射的话会对性能有一些影响

那如何解决这两个问题呢?首先,纯手撸确定是不可能的。须要提供一些自动化手段。

对于赋值来讲,Spring提供了BeanUtils来简化处理,虽然是基于反射来设值的,可是对于现阶段来讲,这点性能损耗仍是没什么影响的。可是,BeanUtils对于不一样类型的属性不能进行拷贝,假设我有一个Domain对象Book,里面有个字段Author,如今我要赋值给BookResult,其中有个字段AuthorResult,此时BeanUtils是没法赋值的。因此我编写了一个基于Gson的工具类来处理,性能测试10000次的属性拷贝BeanUtils须要500多毫秒,基于Gson的工具类只须要300毫秒左右。

对于表字段的生成,若是使用的是IDEA的话,IDE默认提供了一个脚本,能够从表来生成POJO!咱们可使用这个脚原本生成Model,而后将字段拷贝到Param和Result中,来简化字段的编写。我对这个脚本进行了修改,以符合项目需求。主要增长了lombok的支持,新增了类注释和字段注释。

替换代码生成

对于上面代码生成组件的问题,我调整了代码生成的方式。再也不基于组件来生成,而是基于IDEA自己的FileTemplate、LiveTemplate以及Scripted Extensions来进行生成。虽然这样的方式,不可以一次性生成多个文件,可是因为生成逻辑基本是一次性的,因此影响不是很大。在初次生成代码时,代码生成组件的效率是高于FileTemplate、LiveTemplate以及Scripted Extensions的组合,可是后期调整的灵活性,明显是FileTemplate、LiveTemplate以及Scripted Extensions的组合要高于代码生成组件的:

  • 首先,当文件结构调整时,只须要修改FileTemplate,并将配置文件导出给项目组成员便可。
  • 一样的,当LiveTemplate调整时,也只须要修改对应的LiveTemplate,并将配置文件导出给项目组成员便可。
  • 其次,想生成哪一个文件,只要针对这个文件生成便可
  • 第三,经过FileTemplate生成完整的文件后,能够经过LiveTemplate快速的进行模块化的编码
  • 最后,FileTemplate能够设置为项目级别,即每一个项目能够有独立的FileTemplate

具体的操做流程,在下面演示。

独立业务逻辑

针对Service和测试的问题,将原来的Controller、Service和Model三层,拆分为四层:

  • Controller负责前端数据的接收和返回,以及统一异常处理
  • Service负责事务以及Domain层逻辑的组装。这里就不会出现事务嵌套问题,也就不会致使抓不到指望的异常的问题
  • Domain负责业务逻辑
  • Model负责数据持久

这样Service的职责减轻了,同时再也不有事务嵌套的问题。

Model层优化

上面提到,框架中最终放弃了Mapper.xml,转而使用Mybatis的注解来实现持久化操做。改用注解,规避了XML代码的编写,可是并无解决框架对Mybatis的强依赖。因此这里在Domain中新增了Repository接口层,此层用于定义Domain的持久化操做,而Model层中对Repository进行实现,这里的实现就是Mybatis实现。这样作有两个好处:

  • 依赖倒置:原来是Domain依赖Model层,而如今是Model层依赖Domain层,这样当我要把Mybatis替换掉时,Domain彻底无感知。
  • 独立测试:由于如今Domain不依赖于其它任何层,因此能够脱离数据库和容器来进行测试。使得测试的效率不会随着项目的开发而愈来愈低

如何搭建合适的Web框架?

框架改进细节

如今已经知道了,如何对框架进行改进,咱们如今就开始着手进行改造。其实主要的改造是对代码生成方式的改造,也就是编写FileTemplate、LiveTemplate和ScriptedExtensions。下面对这三个功能进行简单的说明,先说ScriptedExtensions。

Scripted Extensions

先来解释一下,什么是Scripted Extensions。咱们都知道,如今的IDE都是插件式的,也就是说,咱们能够经过开发商提供的插件开发包来开发插件,扩展示有的IDE功能。可是编写插件须要特定的开发环境,若是是一个很简单的功能,还要费劲去搭开发环境,挺麻烦的。因此IDEA提供了Scripted Extensions,能够理解为一个简化版的插件,就是能够经过脚原本扩展IDE功能。

IDEA提供了Database功能,能够链接数据库进行相关操做。当你链接了数据库,在表上右击时,能够看到Scripted Extensions这个选项,里面有一个功能是能够基于表来生成POJO的groovy脚本。

可是功能比较low:

  • 包名是写死的:com.sample
  • 没有生成table注释
  • 没有基于lombok来简化getter和setter

不过好在,咱们能基于这个脚原本自行修改,在刚才的Scripted Extensions菜单里,有个Go to Scripts Directory选项,点击后,能够进入脚本目录。

如何搭建合适的Web框架?

直接对这个groovy文件Ctrl+c,Ctrl-v,复制一份,重命名一下,基于这个脚本进行修改便可。具体怎么修改,按照本身的需求来,里面主要就是根据表信息对String的拼接而已。

FileTemplate

FileTemplate是IDEA提供的生成文件的模板,你在点击菜单的File->New...之后,出现的各类文件,都是基于FileTemplate来实现的。咱们自定义的Controller、Service、Domain等类,均可以经过FileTemplate来简化建立。

具体使用方式为,按下Ctrl-Alt-S呼出设置菜单,点击Editor->File And Code Template,在里面新增Template便可。

如何搭建合适的Web框架?

几点说明:

  • 下面的描述中列出了默认的一些参数以及做用
  • 你也能够自定义变量,自定义的变量若是没有赋值,在建立时会有输入框提示输入内容
  • 模板是基于Velocity的,因此若是你熟悉 Velocity,那就能够直接上手
  • Enable Live Template选项是在FileTemplate激活LiveTemplate变量,不过须要使用#[[]]#包裹。可是对于建立Java,这个功能有bug,并不能定位到须要的位置,因此暂时没使用

建立完成后,就能够在New菜单中看到这个模板了。

LiveTemplate

LiveTemplate实际就是CodeSnippet。建立方式和FileTemplate相似。按下Ctrl-Alt-S呼出设置菜单,点击Editor->Live Template,在里面新增Template便可。

如何搭建合适的Web框架?

几点说明:

  • 这里的变量是使用$$包裹
  • 每一个变量就是一个占位符,在使用tab展开后,能够手动输入值
  • 右下角的Edit variables,用于对变量赋值,IDEA提供了一些方法、也能够设置默认值
  • 下面的change连接,能够选择LiveTemplate生效的位置,好比只在Java类声明处生效

编码流程

建立了上面的几个模板后,编码流程以下:

  • 在表上右击,经过Scripted Extensions来生成Model
  • 经过FileTemplate来快速生成Controller、Service、Domain等类
  • 经过LiveTemplate来快速编写代码

总结

本文经过对原框架问题的梳理及解决,来对框架进行升级改造,以适应项目的发展和推动。

相关文章
相关标签/搜索