点击上面  免费订阅本账号!

本公众号主要推送javaweb开发相关技术,基础知识点,同时会深入剖析复杂的问题,分享一些优秀的框架,大型项目经验,当今最流行的Javaweb技术,热点科技新闻,招聘信息,生活乐趣等等。点击上方的蓝字,这样您每天可以看到更多的java知识和资讯!完全是免费订阅,请放心关注。

作者也没写过什么框架,只是分享一些自己的理解,抛砖引玉罢了。如果你写过一些框架可能会产生一些共鸣欢迎讨论,如果你正在写或正打算写一个框架可能会给你一些启发。本文以为较长可能会分多个篇博客来写,现在能想到的是主要分为步骤、模式两部分。如果你觉得好,按一个推荐举手之劳让更多的人可以看到。写本文的时候作者完全是把脑子里的东西写了出来,没有参考任何的资料,所以对于每一项内容可能都是不完整的,不能作为一个完整的参考。有一些方法学的东西每个人都有自己的喜好,没有觉得的对和错。

定位

所谓定位就是回答几个问题,我出于什么目的要写一个框架,我的这个框架是干什么的,有什么特性适用于什么场景,我的这个框架的用户对象是谁,他们会怎么使用,框架由谁维护将来怎么发展等等。

  1. 如果你打算写框架,那么肯定心里已经有一个初步的定位,比如它是一个缓存框架、Web MVC框架、IOC框架、ORM/数据访问框架、RPC框架或是一个用于Web开发的全栈式框架。

  2. 是 否要重复造轮子?除非是练手项目,一般我们是有了解决不了问题的时候才会考虑不使用既有的成熟的框架而重复造轮子的,这个时候需要列出新框架主要希望解决 什么问题。有关是否应该重复造轮子的话题讨论了很多,我的建议是在把问题列清后进行简单的研究看看是否可以通过扩展现有的框架来解决这个问题。一般而言大 部分成熟的框架都有一定的扩展和内部组件的替换能力,可以解决大部分技术问题,但在如下情况下我们可能不得不自己去写一个框架,比如即使通过扩展也无法满 足技术需求、安全原因、需要更高的生产力、需要让框架和公司内部的流程更好地进行适配、开源的普适框架无法满足性能需求、二次开发的成本高于重新开发的成 本等等。

  3. 主打轻量级?轻量级是很多人打算自己写一个新框架的原因,但我们要明白,大部分项目在一开始的时候其实都是轻量级的,随着框架 的用户越来越多,它必定需要满足各种奇怪的需求,在经过了无数次迭代之后,框架的主线流程就会多很多扩展点、检测点,这样框架势必变得越来越重(从框架的 入口到框架的工作结束的方法调用层次越来越多,势必框架也就越来越慢),如果你打算把框架定位于一个轻量级的框架的话,那么在今后的迭代过程中需要进行一 些权衡,在心中有坚定的轻量级的理念的同时不断做性能测试来确保框架的轻量,否则随着时间的发展框架可能会越来越重进而偏离了开始的定位。

  4. 特性?如果你打算写一个框架,并且只有轻量级这一个理由的话,你或许应该再为自己的框架想一些新特性,就像做一个产品一样,如果找不出两个以上的亮点,那么这个产品不太可能成功,比如你的新框架可以是一个零配置的框架,可以是一个前端开发也能用的后端框架。

  5. 其它?一般来说框架是给程序员使用的,我们要考虑框架使用的频度是怎么样的,这可能决定的框架的性能需求和稳定性需求。还有,需要考虑框架将来怎么发展,是希望走开源路线还是商业路线。当然,这些问题也可以留到框架有一个大致的结构后再去考虑。

我们来为本文模拟一个场景,假设我们觉得现有的Spring MVC等框架开发起来效率有点低,打算重复造轮子,对于新框架的定位是一个给Java程序员使用的轻量级的、零配置的、易用的、易扩展的Web MVC框架。


调研

虽然到这里你已经决定去写一个框架了,但是在着手写之前还是至少建议评估一下市面上的类似(成熟)框架。需要做的是通读这些框架的文档以及阅读一些源码,这么做有几个目的:

  1. 通过分析现有框架的功能,可以制定出一个新框架要实现的功能列表。

  2. 通过分析现有框架的问题,总结出新框架需要避免的东西和改善的地方。

  3. 通过阅读现有框架的源码,帮助自己理清框架的主线流程为总体设计做铺垫(后面总体设计部分会更多谈到)。

  4. 如果能充分理解现有的框架,那么你就是站在巨人的肩膀上写框架,否则很可能就是在井底造轮子。

新 开发一个框架的好处是没有兼容历史版本的包袱,但是责任也同样重大,因为如果对于一开始的定位或设计工作没有做好的话,将来如果要对格局进行改变就会有巨 大的向前兼容的包袱(除非你的框架没有在任何正式项目中使用),兼容意味着框架可能会越来越重,可能会越来越难看,阅读至少一到两个开源实现,做好充分的 调研工作可以使你避免犯大错。

假设我们评估了一些主流框架后已经很明确,我们的MVC框架是一个Java平台的、基于Servlet的轻量级的Web MVC框架,主要的理念是约定优于配置,高内聚大于低耦合,提供主流Web MVC框架的大部分功能,并且易用方面有所创新,新特性体包括:

  1. 起手零配置,总体上约定由于配置,即使需要扩展配置也支持通过代码和配置文件两种方式进行配置。

  2. 除了Servlet之外不依赖其它类库,支持通过插件方式和诸如Spring等框架进行整合。

  3. 更优化的项目结构,不需要按照传统的Java Web项目结构那样来分离代码和WEB-INF,视图可以和代码在一起,阅读代码更便利。

  4. 拦截器和框架本身更紧密,提供Action、Controller和Global三个级别的"拦截器"(或者说过滤器)。

  5. 丰富的Action的返回值,返回的可以是视图、可以是重定向、可以是文件、可以是字符串、可以是Json数据,可以是Javascript代码等等。

  6. 支持针对测试环境自动生成测试的视图模型数据,以便前端和后端可以同时开发项目。

  7. 支持在开发的时候自动生成路由信息、模型绑定、异常处理等配置的信息页面和调试页面,方便开发和调试。

提供一套通用的控件模版,使得,并且支持多种模版引擎,比如Jsp、Velocity、Freemarker、Mustache等等。

嗯,看上去挺诱人的,这是一个不错的开端,如果你要写的框架自己都不觉得想用的话,那么别人就更不会有兴趣来尝试使用你的框架了。


解决难点

之 所以把解决难点放在开搞之前是因为,如果实现这个框架的某些特性,甚至说实现这个框架的主流程有一些核心问题难以解决,那么就要考虑对框架的特性进行调 整,甚至取消框架的开发计划了。有的时候我们在用A平台的时候发现一个很好用的框架,希望把这个框架移植到B平台,这个想法是好的,但之所以在这以前这么 多年没有人这么干过是因为这个平台的限制压根不可能实现这样的东西。比如我们要实现一个MVC框架,势必需要依赖平台提供的反射特性,如果你的语言平台压 根就没有运行时反射这个功能,那么这就是一个非常难以解决的难点。又比如我们在某个平台实现一个类似于.NET平台Linq2Sql的数据访问框架,但如 果这个目标平台的开发语言并不像C#那样提供了类型推断、匿名类型、Lambda表达式、扩展方法的话那么由于语法的限制你写出来的框架在使用的时候是无 法像.NET平台Linq2Sql那样优雅的,这就违背了实现框架的主要目的,实现新的框架也就变得意义不大了。

对于我们要实现的MVC框 架貌似不存在什么根本性的无法解决的问题,毕竟在Java平台已经有很多可以参考的例子了。如果框架的实现总体上没什么问题的话,就需要逐一评估框架的这 些新特性是否可以解决。建议对于每一个难点特性做一个原型项目来证明可行,以免在框架实现到一半的时候发现有无法解决的问题就比较尴尬了。

分析一下,貌似我们要实现的这8大特性只有第1点要研究一下,看看如何免配置通过让代码方式让我们的Web MVC框架可以和Servlet进行整合,如果无法实现的话,我们可能就需要把第1点特性从零配置改为一分钟快速配置了。

开搞

首先需要给自己框架取一个名字,取名要考虑到易读、易写、易记,也需要尽量避免和市面上其它产品的名字重复,还有就是最好不要起一个侮辱其它同类框架的名字以免引起公愤。
如果将来打算把项目搞大的话,可以提前注册一下项目的相关域名,毕竟现在域名也便宜,避免到时候项目名和域名差距很大,或项目的.com或.org域名对应了一个什么不太和谐的网站这就尴尬了。
然后就是找一个地方来托管自己的代码,如果一开始不希望公开代码的话,最好除了本地源代码仓库还有一个异地的仓库以免磁盘损坏导致抱憾终身,当然如果不怕出丑的话也可以在起步的时候就使用Github等网站来托管自己的代码。


总体设计

对 于总体设计我的建议是一开始不一定需要写什么设计文档画什么类图,因为可能一开始的时候无法形成这么具体的概念,我们可以直接从代码开始做第一步。框架的 使用者一般而言还是开发人员,抛开框架的内在的实现不说,框架的API设计的好坏取决于两个方面。对于普通开发人员而言就是使用层面的API是否易于使 用,拿我们的MVC框架举例来说:

最基本的,搭建一个HelloWorld项目,声明一个Controller和Action,配置一个路由规则让Get方法的请求可以解析到这个Action,可以输出HelloWorld文字,怎么实现?
如果要实现从Cookie以及表单中获取相关数据绑定到Action的参数里面,怎么实现?
如果要配置一个Action在调用前需要判断权限,在调用后需要记录日志,怎么实现?

我们这里说的API,它不一定全都是方法调用的API,广义上来说我们认为框架提供的接入层的使用都可以认为是API,所以上面的一些功能都可以认为是MVC框架的API。

框架除了提供基本的功能,还要提供一定程度的扩展功能,使得一些复杂的项目能够在某些方面对框架进行增强以适应各种需求,比如:

  1. 我的Action是否可以返回图片验证码?

  2. 我的Action的参数绑定是否可以从Memcached中获取数据?

  3. 如果出现异常,能否在开发的时候显示具体的错误信息,在正式环境显示友好的错误页面并且记录错误信息到数据库?

一 般而言如果要实现这样的功能就需要自己实现框架公开的一些类或接口,然后把自己的实现"注册"到框架中,让框架可以在某个时候去使用这些新的实现。这就需 要框架的设计者来考虑应该以怎么样的友好形式公开出去哪些内容,使得以后的扩展实现在自由度以及最少实现上的平衡,同时要兼顾外来的实现不破坏框架已有的 结构。

要想清楚这些不是一件容易的事情,所以在框架的设计阶段完全可以使用从上到下的方式进行设计。也就是不去考虑框架怎么实现,而是以一 个使用者的身份来写一个框架的示例网站,API怎么简单怎么舒服就怎么设计,只从使用者的角度来考虑问题。对于相关用到的类,直接写一个空的类(能用接口 的尽量用接口,你的目的只是通过编译而不是能运行起来),让程序可以通过编译就可以了。你可以从框架的普通使用开始写这样一个示例网站,然后再写各种扩展 应用,在此期间你可能会用到框架内部的20个类,这些类就是框架的接入类,在你的示例网站通过编译的那刹那,其实你已经实现了框架的接入层的设计。

这里值得一说的是API的设计蕴含了非常多的学问以及经验,要在目标平台设计一套合理易用的API首先需要对目标平台足够了解,每一个平台都有一些约定俗成的规范,如果设计的API能符合这些规范那么开发人员会更容易接受这个框架,此外还有一些建议:

  1. 之 所以我们把API的设计先行,而不是让框架的设计先行是因为这样我们更容易设计出好用的API,作为框架的实现者,我们往往会进行一些妥协,我们可能会为 了在框架内部DRY而设计出一套丑陋的API让框架的使用者去做一些重复的工作;我们也可能会因为想让框架变得更松耦合强迫框架的使用者去使用到框架的一 些内部API去初始化框架的组件。如果框架不是易用的,那么框架的内部设计的再合理又有什么意义?

  2. 尽量少暴露一些框架内部的类名吧,对 于框架的使用者来说,你的框架对他一点都不熟悉,如果要上手你的框架需要学习一到两个类尚可接受,如果要使用到十几个类会头晕脑胀的,即使你的框架有非常 多的功能以及配置,可以考虑提供一个入口类,比如创建一个ConfigCenter类作为入口,让使用者可以仅仅探索这个类便可对框架进行所有的配置。

  3. 一 个好的框架是可以让使用者少犯错误的,框架的设计者务必要考虑到,框架的使用者没有这个业务来按照框架的最佳实践来做,所以在设计API的时候,如果你希 望API的使用者一定要按照某个方式来做的话,可以考虑设置一个简便的重载来加载默认的最合理的使用方式而不是要求使用者来为你的方法初始一些什么依赖, 同时也可以在API内部做一些检测,如果发现开发人员可能会犯错进行一些提示或抛出异常。好的框架无需过多的文档,它可以在开发人员用的时候告知它哪里错 了,最佳实践是什么,即便他们真的错了也能以默认的更合理的方式来弥补这个错误。

  4. 建议所有的API都有一套统一的规范,比如入口都叫XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和 ZZZService。API往往需要进行迭代和改良的,在首个版本中把好名字用掉也不一定是一个好办法,最好还是给自己的框架各种API的名字留一点余 地,这样以后万一需要升级换代不至于太牵强。

下一步工作就是把项目中那些空的类按照功能进行划分。目的很简单,就是让你的框架 的100个类或接口能够按照功能进行拆分和归类,这样别人一打开你的框架就可以马上知道你的框架分为哪几个主要部分,而不是在100个类中晕眩;还有因为 一旦在你的框架有使用者后你再要为API相关的那些类调整包就比困难了,即使你在创建框架的时候觉得我的框架就那么十几个类无需进行过多的分类,但是在将 来框架变大又发现当初设计的不合理,无法进行结构调整就会变得很痛苦。因此这个工作还是相当重要的,对于大多数框架来说,可以有几种切蛋糕的方式:

  1. 分 层。我觉得框架和应用程序一样,也需要进行分层。传统的应用程序我们分为表现层、逻辑层和数据访问层,类似的对于很多框架也可以进行横向的层次划分。要分 层的原因是我们的框架要处理的问题是基于多层抽象的,就像如果没有OSI七层模型,要让一个HTTP应用去直接处理网络信号是不合理的也是不利于重用的。 举一个例子,如果我们要写一个基于Socket的RPC的框架,我们需要处理方法的代理以及序列化,以及序列化数据的传输,这完全是两个层面的问题,前者 偏向于应用层,后者偏向于网络层,我们完全有理由把我们的框架分为两个层面的项目(至少是两个包),rpc.core和rpc.socket,前者不关心 网络实现来处理所有RPC的功能,后者不关心RPC来处理所有的Socket功能,在将来即使我们要淘汰我们的RPC的协议了,我们也可以重用 rpc.socket项目,因为它和RPC的实现没有任何关系,它关注的只是socket层面的东西。

  2. 横切。刚才说的分层是横向的分 割,横切是纵向的分割(横切是跨多个模块的意思,不是横向来切的意思)。其实横切关注点就是诸如日志、配置、缓存、AOP、IOC等通用的功能,对于这部 分功能,我们不应该把他们和真正的业务逻辑混淆在一起。对于应用类项目是这样,对于框架类项目也是这样,如果某一部分的代码量非常大,完全有理由为它分出 一个单独的包。对于RPC项目,我们可能就会把客户端和服务端通讯的消息放在common包内,把配置的处理单独放在config包内。

  3. 功能。也就是要实现一个框架主要解决的问题点,比如对于上面提到的RPC框架的core部分,可以想到的是我们主要解决是客户端如何找到服务端,如何把进 行方法调用以及把方法的调用信息传给目标服务端,服务端如何接受到这样的信息根据配置在本地实例化对象调用方法后把结果返回客户端三大问题,那么我们可能 会把项目分为routing、client、server等几个包。

如果是一个RPC框架,大概是这样的结构:

对于我们的Web MVC框架,举例如下:

  1. 我们可以有一个mvc.core项目,细分如下的包:

  2. common:公共的一组件,下面的各模块都会用到

  3. config:配置模块,解决框架的配置问题

  4. startup:启动模块,解决框架和Servlet如何进行整合的问题

  5. plugin:插件模块,插件机制的实现,提供IPlugin的抽象实现

  6. routing:路由模块,解决请求路径的解析问题,提供了IRoute的抽象实现和基本实现

  7. controller:控制器模块,解决的是如何产生控制器

  8. model:视图模型模块,解决的是如何绑定方法的参数

  9. action:action模块,解决的是如何调用方法以及方法返回的结果,提供了IActionResult的抽象实现和基本实现

  10. view:视图模块,解决的是各种视图引擎和框架的适配

  11. filter:过滤器模块,解决是执行Action,返回IActionResult前后的AOP功能,提供了IFilter的抽象实现以及基本实现

  12. 我们可以再创建一个mvc.extension项目,细分如下的包:

  13. filters:一些IFilter的实现

  14. results:一些IActionResult的实现

  15. routes:一些IRoute的实现

  16. plugins:一些IPlugin的实现

这里我们以IXXX来描述一个抽象,可以是接口也可以是抽象类,在具体实现的时候根据需求再来确定。

这 种结构的划分方式完全吻合上面说的切蛋糕方式,可以看到除了横切部分和分层部分,作为一个Web MVC框架,它核心的组件就是routing、model、view、controller、action(当然,对于有些MVC框架它没有route部 分,route部分是交由Web框架实现的)。

如果我们在这个时候还无法确定框架的模块划分的话,问题也不大,我们可以在后续的搭建龙骨的步骤中随着更多的类的建立,继续理清和确定模块的划分。

经过了设计的步骤,我们应该心里对下面的问题有一个初步的规划了:

  • 我们的框架以什么形式来提供如何优雅的API?

  • 我们的框架包含哪些模块,模块大概的作用是什么?

搭建龙骨

在 经过了初步的设计之后,我们可以考虑为框架搭建一套龙骨,一套抽象的层次关系。也就是用抽象类、接口或空的类实现框架,可以通过编译,让框架撑起来,就像 造房子搭建房子的钢筋混凝土结构(添砖加瓦是后面的事情,我们先要有一个结构)。对于开发应用程序来说,其实没有什么撑起来一说,因为应用程序中很多模块 都是并行的,它可能并没有一个主结构,主流程,而对于框架来说,它往往是一个高度面向对象的,高度抽象的一套程序,搭建龙骨也就是搭建一套抽象层。这么说 可能有点抽象,我们还是来想一下如果要做一个Web MVC框架,需要怎么为上面说的几个核心模块进行抽象(我们也来体会一下框架中一些类的命名,这里我们为了更清晰,为所有接口都命名为IXXX,这点不太 符合Java的命名规范):

  1. routing MVC的入口是路由

  2. 每一个路由都是IRoute代表了不同的路由实现,它也提供一个getRouteResult()方法来返回RouteResult对象

  3. 我们实现一个框架自带的DefaultRoute,使得路由支持配置,支持默认值,支持正则表达式,支持约束等等

  4. 我们需要有一个Routes类来管理所有的路由IRoute,提供一个findRoute()方法来返回RouteResult对象,自然我们这边调用的就是IRoute的getRouteResult()方法,返回能匹配到的结果

  5. RouteResult对象就是匹配的路由信息,包含了路由解析后的所有数据

  6. controller 路由下来是控制器

  7. 我们有IControllerFactory来创建Controller,提供createController()方法来返回IController

  8. IController代表控制器,提供一个execute()方法来执行控制器

  9. 我们实现一个框架自带的DefaultControllerFactory来以约定由于配置的方式根据约定规则以及路由数据RouteResult来找到IController并创建它

  10. 我 们为IController提供一个抽象实现,AbstractController,要求所有MVC框架的使用者创建的控制器需要继承 AbstractController,在这个抽象实现中我们可以编写一些便捷的API以便开发人员使用,比如view()方法、file()方法、 redirect()方法、json()方法、js()方法等等

  11. action 找到了控制器后就是来找要执行的方法了

  12. 我们有IActionResult来代表Action返回的结果,提供一个execute()方法来执行这个结果

  13. 我们的框架需要实现一些自带的IActionResult,比如ContentResult、ViewResult、FileResult、JsonResult、RedirectResult来对应AbstractController的一些便捷方法

  14. 再来定义一个IActionInvoker来执行Action,提供一个invokeAction()方法

  15. 我们需要实现一个DefaultActionInvoker以默认的方式进行方法的调用,也就是找到方法的一些IFilter按照一定的顺序执行他们,最后使用反射进行方法的调用得到上面说的IActionResult并执行它的execute()方法

  16. filter 我们的框架很重要的一点就是便捷的过滤器

  17. 刚才提到了IFilter,代表的是一个过滤器,我们提供IActionFilter对方法的执行前后进行过滤,提供IResultFilter对IActionResult执行前后进行过滤

  18. 我们的IActionInvoker怎么找到需要执行的IFilter呢,我们需要定义一个IFilterProvider来提供过滤器,它提供一个getFilters()方法来提供所有的IFilter的实例

  19. 我 们的框架可以实现一些自带的IFilterProvider,比如AnnotationFilterProvider通过扫描Action或 Controller上的注解来获取需要执行的过滤器信息;比如我们还可以实现GlobalFilterProvider,开发人员可以直接通过配置或代 码方式告知框架应用于全局的IFilter

  20. 既然我们实现了多个IFilterProvider,我们自然需要有一个类来管理这些IFilterProvider,我们实现一个FilterProviders类并提供getFilters()方法(这和我们的Routes类来管理IRoute是类似的,命名统一)

  21. view 各种IActionResult中最特殊最复杂的就是ViewResult,我们需要有一个单独的包来处理ViewResult的逻辑

  22. 我们需要有IViewEngine来代表一个模版引擎,提供一个getViewEngineResult()方法返回ViewEngineResult

  23. ViewEngineResult包含视图引擎寻找视图的结果信息,里面包含IView和寻找的一些路径等

  24. IView自然代表的是一个视图,提供render()方法(或者为了统一也可以叫做execute)来渲染视图

  25. 我 们的框架可以实现常见的一些模版引擎,比如FreemarkerViewEngine、VelocityViewEngine 等,VelocityViewEngine返回的ViewEngineResult自然包含的是一个实现IView的VelocityView,不会返回 其它引擎的IView

  26. 同样的,我们是不是需要一个ViewEngines来管理所有的IViewEngine呢,同样也是实现findViewEngine()方法

  27. common 这里可以放一些项目中各个模块都要用到的一些东西

  28. 比 如各种context,context代表的是执行某个任务需要的环境信息,这里我们可以定义HttpContext、 ControllerContext、ActionContext和ViewContext,后者继承前者,随着MVC处理流程的进行,View执行时的 上下文相比Action执行时的上下文信息肯定是多了视图的信息,其它同理,之所以把这个信息放在common里面而不是放在各个模块自己的包内是因为这 样更清晰,可以一目了然各种对象的执行上下文有一个立体的概念

  29. 比如各种helper或utility

接下去就不再详细阐述model、plugin等模块的内容了。

看到这里,我们来总结一下,我们的MVC框架在组织结构上有着高度的统一:

  • 如果xxx本身并无选择策略,但xxx的创建过程也不是一个new这么简单的,可以由xxxFactory类来提供一个xxx

  • 如果我们需要用到很多个yyy,那么我们会有各种yyyProvider(通过getyyy()方法)来提供这些yyy,并且我们需要有一个yyyProviders来管理这些yyyProvider

  • 如果zzz的选择是有策略性的,会按照需要选择zzz1或zzzN,那么我们可能会有一个zzzs来管理这些zzz并且(通过findzzz()方法)来提供合适的zzz

同 时我们框架的相关类的命名也是非常统一的,可以一眼看出这是实现、还是抽象类还是接口;是提供程序,是执行结果还是上下文。当然,在将来的代码实现过程中 很可能会把很多接口变为抽象类提供一些默认的实现,这并不会影响项目的主结构。我们会在模式篇对框架常用的一些高层设计模式做更多的介绍。

到了这里,我们的项目里已经有几十个空的(抽象)类、接口了,其中也定义了各种方法可以把各个模块串起来(各种find()方法和execute()方法),可以说整个项目的龙骨已经建立起来了,这种感觉很好,因为我们心里很有底,我们只需要在接下去的工作中做两个事情:

  • 实现各种DefaultXXX来走通主流程

  • 实现各种IyyyProvider和Izzz接口来完善支线流程


走通主线流程

所谓走通主线流程,就是让这个框架可以以一个HelloWorld形式跑起来,这就需要把几个核心类的核心方法使用最简单的方式进行实现,还是拿我们的MVC框架来举例子:

  • 从startup开始,可能需要实现ServletContextListener来动态注册我们框架的入口Servlet,暂且起名为DispatcherServlet吧,在这个类中我们需要走一下主线流程

  • 调用Routes.findRoute()获得IRoute

  • 调用IRoute.getRouteResult()来获得RouteResult

  • 使用拿到的RouteResult作为参数调用DefaultControllerFactory.createController()获得IController(其实也是AbstractController)

  • 调用IController.execute()

  • 在 config中创建一个IConfig作为一种配置方式,我们实现一个DefaultConfig,把各种默认实现注册到框架中去,也就是 DefaultRoute、DefaultControllerFactory、DefaultActionInvoker,然后把各种 IViewEngine加入ViewEngines

  • 然后需要完成相关默认类的实现:

  • 实现Routes.findRoute()

  • 实现DefaultRoute.getRouteResult()

  • 实现DefaultControllerFactory.createController()

  • 实现AbstractController.execute()

  • 实现DefaultActionInvoker.invokeAction()

  • 实现ViewResult.execute()

  • 实现ViewEngines.findViewEngine()

  • 实现VelocityViewEngine.getViewEngineResult()

  • 实现VelocityView.render()

在这一步,我们并不一定要去触碰filter和model这部分的内容,我们的主线流程只是解析路由,获得控制器,执行方法,找到视图然后渲染视图。过滤器和视图模型的绑定属于增强型的功能,属于支线流程,不属于主线流程。

虽 然在这里我们说了一些MVC的实现,但本文的目的不在于教你实现一个MVC框架,所以不用深究每一个类的实现细节,这里想说的是,在前面的龙骨搭建完后, 你会发现按照这个龙骨为它加一点肉上去实现主要的流程是顺理成章的事情,毫无痛苦。在整个实现的过程中,你可以不断完善common下的一些 context,把方法的调用参数封装到上下文对象中去,不但看起来清楚且符合开闭原则。到这里,我们应该可以跑起来在设计阶段做的那个示例网站的 HelloWorld功能了。

在这里还想说一点,有些人在实现框架的时候并没有搭建龙骨的一步骤,直接以非OOP的方式实现了主线流程,这种方式有以下几个缺点:

不容易做到SRP单一指责原则,你很容易把各种逻辑都集中写在一起,比如大量的逻辑直接写到了DispatcherServlet中,辅助一些Service或Helper,整个框架就肥瘦不匀,有些类特别庞大有些类特别小。
不容易做到OCP开闭原则,扩展起来不方便需要修改老的代码,我们期望的扩展是实现新的类然后让框架感知,而不是直接修改框架的某些代码来增强功能。
很难实现DIP依赖倒置原则,即使你依赖的确实是IService但其实就没意义,因为它只有一个实现,只是把他当作帮助类来用罢了。

实现各种支线流程

我们想一下,对于这个MVC框架有哪些没有实现的支线流程?其实无需多思考,因为我们在搭建龙骨阶段的设计已经给了我们明确的方向了,我们只需要把除了主线之外的那些龙骨上也填充一些实体即可,比如:

  1. 实现更多的IRoute,并注册到Routes

  2. 实现更多的IViewEngine,并注册到ViewEngines

  3. 实现必要的IFilterProvider以及FilterProviders,把IFilterProvider注册到FilterProviders

  4. 增强DefaultActionInvoker.invokeAction()方法,在合适的时候调用这些IFilter

  5. 实现更多的IActionResult,并且为AbstractController实现更多的便捷方法来返回这些IActionResult

  6. ……实现更多model模块的内容和plugin模块的内容

实现了这一步后,你会发现整个框架饱满起来了,每一个包中不再是仅有的那些接口和默认实现,而且会有一种OOP的爽快感,爽快感来源于几个方面:

  1. 面对接口编程抽象和多态的放心安心的爽快感

  2. 为抽象类实现具体类享受到父类大量实现的满足的爽快感

  3. 实现了大量的接口和抽象类后充实的爽快感

我们再来总结一下之前说的那些内容,实现一个框架的第一大步就是:

  1. 设计一套合理的接口

  2. 为框架进行模块划分

  3. 为框架搭建由抽象结构构成的骨架

  4. 在这个骨架的基础上实现一个HelloWorld程序

  5. 为这个骨架的其它部分填充更多实现

经 过这样的一些步骤后可以发现这个框架是很稳固的,很平衡的,很易于扩展的。其实到这里很多人觉得框架已经完成了,有血有肉,其实个人觉得只能说开发工作实 现了差不多30%,后文会继续说,毕竟直接把这样一个血肉之躯拿出去对外有点吓人,我们需要为它进行很多包装和完善。

单元测试

在这之前我们写的框架只能说是一个在最基本的情况下可以使用的框架,作为一个框架我们无法预测开发人员将来会怎么使用它,所以我们需要做大量的工作来确保框架不但各种功能都是正确的,而且还是健壮的。写应用系统的代码,大多数项目是不会去写单元测试的,原因很多:

  • 项目赶时间,连做一些输入验证都没时间搞,哪里有时间写测试代码。

  • 项目对各项功能的质量要求不高,只要能在标准的操作流程下功能可用即可。

  • 项目基本不会去改或是临时项目,一旦测试通过之后就始终是这样子了,没有迭代。

  • ……

对于框架,恰恰相反,没有配套的单元测试的框架(也就是仅仅使用人工的方式进行测试,比如在main中调用一些方法观察日志或输出,或者运行一下示例项目查看各种功能是否正常,是非常可怕的)原因如下:

  1. 自动化程度高,回归需要的时间短,甚至可以整合到构建过程中进行,这是人工测试无法实现的。

  2. 框架一定是有非常多的迭代和重构的, 每一次修改虽然只改了A功能,但是可能会影响到B和C功能,人工测试的话你可能只会验证A是否正常,容易忽略B和C,使用单元测试的话只要所有功能都有覆盖,那么几乎不可能遗漏因为修改导致的潜在问题,而且还能反馈出来因为修改导致的兼容性问题。

  3. 之前说过,一旦框架开放出去,框架的使用者可能会以各种方式在各种环境来使用你的框架,环境不同会造成很多怪异的边界输入或非法输入,需要使用单元测试对代码进行严格的边界测试,以确保框架可以在严酷的环境下生存。

  4. 单元测试还能帮助我们改善设计,在写单元测试的时候如果发现目标代码非常难以进行模拟难以构建有效的单元测试,那么说明目标代码可能有强依赖或职责过于复杂,一个被单元测试高度覆盖的框架往往是设计精良的,符合高内聚低耦合的框架。

如果框架的时间需求不是特别紧的话,单元测试的引入可以是走通主线流程的阶段就引入,越早引入框架的成熟度可能就会越高,以后重构返工的机会会越小,框架的可靠性也肯定会大幅提高。之前我有写过一个类库项目,并没有写单元测试,在项目中使用了这个类库一段时间也没有出现任何问题,后来花了一点时间为类库写了单元测试,出乎我意料之外的是,我的类库提供的所有API中有超过一半是无法通过单元测试的(原以为这是一个成熟的类库,其实包含了数十个BUG),甚至其中有一个API是在我的项目中使用的。你可能会问,为什么在使用这个API的时候没有发生问题而在单元测试的时候发生问题了呢?原因之前提到过,我是框架的设计者,我在使用类库提供的API的时候是知道使用的最佳实践的,因此我在使用的时候为类库进行了一个特别的设置,这个问题如果不是通过单元测试暴露的话,那么其它人在使用这个类库的时候基本都会遇到一个潜在的BUG。

示范项目

写一个示例项目不仅仅是为了给别人参考,而且还能够帮助自己去完善框架,对于示例项目,最好兼顾下面几点:

  1. 是一个具有一定意义的网站或系统,而不是纯粹为了演示特性而演示。这是因为,很多时候只有那些真正的业务逻辑才会暴露出问题,演示特性的时候我们总是有一些定势思维会规避很多问题。或者可以提供两个项目,一个纯粹演示特性,一个是示例项目。

  2. 覆盖尽可能多的特性或使用难点,在项目的代码中提供一些注释,很多开发人员不喜欢阅读文档,反而喜欢看一下示例项目直接上手(模仿示例项目,或直接拿示例项目中的代码来修改)。

  3. 项目中的代码,特别是涉及到框架使用的代码一定要规范,原因上面也说了,作为框架的设计者你不会希望大家复制的代码粘帖的代码一团糟吧。

  4. 如果你的项目针对的不仅仅是Web项目,那么示例项目最好提供Web和桌面两个版本,一来你自己容易发现因为环境不同带来的使用差异,二来可以给予不同类型项目不同的最佳实践。

原文链接:http://www.cnblogs.com/lovecindywang/p/4444915.html

点击阅读全文阅读"程序员的春联,各种感悟各种嗨”

有人用微信聊天,有人却在微信中学习,成长。下面是2016最HOT IT公众号,赶快试试新的关注方法吧!关注方式
★长按二维码,选择“识别图中二维码”进行关注。

 没看够?更多好文在阅读原文

你知道如何写一个框架吗?详细步骤放送(上)相关推荐

  1. 写一个框架的详细步骤

    定位 所谓定位就是回答几个问题,我出于什么目的要写一个框架,我的这个框架是干什么的,有什么特性适用于什么场景,我的这个框架的用户对象是谁,他们会怎么使用,框架由谁维护将来怎么发展等等. 如果你打算写框 ...

  2. 从零开始写一个框架的详细步骤

    定位 所谓定位就是回答几个问题,我出于什么目的要写一个框架,我的这个框架是干什么的,有什么特性适用于什么场景,我的这个框架的用户对象是谁,他们会怎么使用,框架由谁维护将来怎么发展等等. 如果你打算写框 ...

  3. 从零开始写一个RPC框架的详细步骤

    http://blog.csdn.net/liu88010988/article/details/51547592 定位 所谓定位就是回答几个问题,我出于什么目的要写一个框架,我的这个框架是干什么的, ...

  4. 【原创】如何写一个框架:步骤(下)

    [原创]如何写一个框架:步骤(上) 说明:写本文的时候作者完全是把脑子里的东西写了出来,没有参考任何的资料,所以对于每一项内容可能都是不完整的,不能作为一个完整的参考.有一些方法学的东西每个人都有自己 ...

  5. 控制台编写JAVA程序教程_写一个java程序的步骤是什么?写java程序技巧

    写Java程序是要按照步骤来的,这样才能写好一个java程序,那么接下来,我们就来给大家讲解一下写一个java程序的步骤是什么? (1)创建Java项目:"FileàNewàProjectà ...

  6. django框架搭建详细步骤

    创建djiango项目的详细步骤 1:进入项目目录执行 django-admin startproject mysite 2: 进入mysite目录,在下面创建app应用bookstore djang ...

  7. 等压线上怎么画风向_圣诞贺卡怎么写?向你爱的人送上最有温度的祝福吧!

    在现在的社会,好像什么都很方便,要写字的话,各种字体在电脑上随便敲一敲就出来了,但是英国人民对于写贺卡这件事,还抱有一种执着. 挑选一张精美的贺卡,再一笔一划写上发自内心的祝福,哪怕字体没有那么好看, ...

  8. idea ssm框架搭建详细步骤_搭建一套纯净版的SSM框架,随时CV使用它不香吗?

    之前的时候写过一篇文章,因为各种原因,需要搭建一套ssm框架,上次的时候就是搭建了一套框架,但是其中相应的代码实现并没有添加进去,今天咱就完整起来,搭建一个测试代码,当然大家不需要非要用我的,再网上有 ...

  9. 做一个项目的详细步骤

    第一步:需求分析阶段:(在拿到一个项目之后首先要知道客户需要的是什么,在这个阶段要完成需求分析设计书和基础的框架) 第二步:系统设计阶段:(要对项目整体进行设计,在这个阶段要设计出数据库,完成类图和序 ...

最新文章

  1. 如何使用Salt 的各种状态值
  2. 网页中英文语言切换解决方案
  3. 蓝桥分酒java_[蓝桥杯][java]海盗分酒
  4. MongoDB · 引擎特性 · MongoDB索引原理
  5. 成为更优秀的程序员:退后一步看问题
  6. 前端自动化打包工具--webpack
  7. 【CSP201312-2】ISBN号码,字符串,简单模拟
  8. Java EE Servlet 几个path
  9. Python 学习---------Day4
  10. 如何在软件UI设计中运用格式塔心理学5项法则?
  11. 数字化转型提出新要求 新华三IT基础架构如何随需而变?
  12. Java设计模式:简单工厂模式(附实例代码)每天一学设计模式
  13. 阿里云ACP大数据专业认证,值得报名吗?
  14. 黄哥python培训骗局
  15. 数据库的内外链接和左右链接
  16. 闽江学院计算机与控制工程学院在哪个校区,计算机与控制工程学院举行优秀校友分享会...
  17. HuaWei ❀ BGP GR与NSR
  18. html5 i标签什么意思,快速了解HTML5 b和i标签
  19. 模拟低通原型滤波器的MATLAB设计
  20. 初中七年级计算机课教学总结怎么写,初中计算机教学工作总结范文

热门文章

  1. 神经网络学习小记录40——春节到了,用LSTM写古诗不?
  2. 【大牛系列教学】Android热修复原理,满满干货指导
  3. zynq Linux软件中断,zynq linux驱动之PL-PS中断【转】
  4. 用比较wu的话解释程序专业术语
  5. 国产高性能32位M0内核MCU单片机DP32G003
  6. MPAndroidChart饼图PieChart的使用
  7. MPAndroidChart—— pieChart 属性详解
  8. 基于Java、JSP的网上招聘系统的设计和实现
  9. c++指针数组(入门)
  10. 男生宿舍“卧谈”经典