首页 > 代码库 > 如何设计一个易用的MVC框架

如何设计一个易用的MVC框架

<style></style>
导言

把一件简单的事情做复杂很容易,把一件复杂的事情做简单却不易。在计算机的世界里,

冯.诺依曼把复杂的电脑简化为:存储器,控制器,运算器和I/O设备;

丹尼斯·里奇把晦涩的汇编语言简化为258页的《C程序设计语言》;

詹姆斯高斯林把繁琐的跨平台编码简化为256条字节码指令;

对我们大部分人而言,把简单的事情做简单就足够了。

关于框架

框架是对某一类共通业务的封装,框架设计应该遵循几个基本的原则:1 易用性 2 稳定性3 扩展性,框架从来都是给别人用

的,框架的学习成本与他的复杂度成正比,如果你设计了一个功能强大但接口复杂的框架,我想,没有几人愿意使用;同样,

稳定性也是必须的,稳定的框架体现设计者所遵循的理念,稳定的框架也会越来越成熟;扩展性是框架的灵魂,没有扩展性

框架只会像明朝的八股文,禁锢才华横溢的书生,业务总是在变化,框架必须具备适应这种变化的能力。

java web框架发展,大致经历以下几个阶段:

刀耕火种时期

早期的java web技术采用serverlt实现,与CGI类似,只不过serverlet采用的是多线程而不是多进程,服务端除了处理业务逻辑之外

还负责页面的组织,通过Printwirter 打印html标签,这给当时的开发者带来了无尽的麻烦,为了修改一个CSS样式或者一段javacscript

不得不重新编译serverlet,为了输出简单的处理结果,你不得不用大量的篇幅打印html标签,下面是一个简单不过的例子:

import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class SimpleServlet extends HttpServlet {        private static final long serialVersionUID = 1L;    public void doGet(HttpServletRequest request, HttpServletResponse response)             throws ServletException, IOException {        response.setContentType("text/html");        PrintWriter out = response.getWriter();        String docType = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n";        out.println(docType + "<HTML>\n" +        "<HEAD><TITLE>Hello</TITLE></HEAD>\n"         + "<BODY BGCOLOR=\"#FDF5E6\">\n" +         "<H1>Hello world</H1>\n" + "</BODY></HTML>");        out.flush();        out.close();    }}

这段代码唯一的功能就是输出helloworld。很难想象,在复杂的业务下,程序员要在这种重复的功能上耗费多少时间,这个时期程

序员就像新石器时代的人民,用最原始的工具拓荒,当然这个时期的web还谈不上什么架构。当时的sun公司也注意到这点,随后

推出了JSP,java 进入下一个时代。

农业文明时期

为了解决用serverlet遇到的诸多问题,sun在1999年推出JSP技术,JSP允许在页面上插入java代码进行逻辑处理,同时也提供

了大量的tag处理html标签,大大提高了开发效率,开发人员从繁琐的out.prinlt中解脱出来,用更多的精力去关注业务逻辑,在此

基础上演化出jsp+Serverlet两层架构和 jsp+serverlet+javabean三层架构,在这种架构下,jsp侧重页面展现,serverlet负责业

务逻辑,javabean则负责二者的数据传递,这就是MVC的雏形,后续web框架也大多基于这种模式来发展,有的关注其中的某层,如

FreeMarker,有点关注整个流程,如struct。这个阶段的程序员开发逐渐迈入固定模式,像农业文明时代一样,使用着有限的工具,

推动着社会的发展。随着类似架构的不断使用和发展,java web迈入另一个跨域式发展的时期。

工业文明时期

 就像工业革命标志工业文明的发展一样,java web的工业文明时代同样有着标志性的事件,那就是2000年5月发布的structs框架,

这个框架标志着机械化开发的到来,在采用structs后,我们按照固定的模式写着类似的代码,配类似的配置,如果你接手过这个时期

的项目,你会发现他们都有大致的结构,这是一种跨越式的发展,20%的市场占有率也说明当时的程序员对这种框架有多么的渴求。

同时structs也降低了程序员的要求,找一个新手进行简单的培训后就可以进行项目开发,稳定的框架下开发也减少了出错的可能,

大大降低了项目的开发和维护成本。人们在使用中和思考中不断修复和革新这些框架,许多优秀的想法不断萌现,框架也不断智能化,

引领广大程序员迈入下一个新纪元。

现代文明时期

现在文明追求智能化让人类从繁重的体力中解放出来,享受生活,同样,跨入现代文明的web 开发也追求程序员的解放,大量

优秀的框架涌现:webwork  、Tapestry 、echo、spring等等不甚枚举,这些框架都和优秀,通过简单的配置就你实现强大

的功能,但要从中选择一个标志性,那么我将毫不犹豫选择spring,如果说java让程序员从操作系统的束缚中解脱出来,那么

spring就是让程序员从复杂的设计模式中解脱出来,IOC 、DI、 AOP、 RMI、Proxy,天才Rod Johnson一个不落的包含进来,

为java界带来新的春天,优秀的思想加上简单的API成就Spring无可替代的地位。

重复造车轮

讨论了这么多框架和设计,似乎跟我们的标题没有多大关系,而且有这么多优秀的框架,我们有必要自己设计吗?答案是肯定的,

社会发展的动力是革新,而革新的动力是对现有的事物不断思考,如果都安于现状,那么就无进步可言,也许大多数人都认为

web就应该按MVC设计,编码,当然,我也认同这点,但是随着对这些框架的应用,让我不禁想到许多问题,带着这些问题,

有了一个重复造车轮的想法。带着对structs的学习,我们也自己实现也该“MVC”框架。 struct很优秀,他主要实现了http

请求道用户逻辑的封装,事实上serverlet已经实现了这一点,不过serverlet只能做到将请求映射到一个具体的类,只提供了

service和doPost doGet之类的几个简单接口处理用户请求,而实际的开发中,在面临千差万别的业务时,我们不得不在

serverlet里加大量的if else判断:

public class SimpleServlet extends BaseServlet {    private static final long serialVersionUID = 1L;    @Override    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        super.service(request, response);        String command = request.getParameter("command");        if ("query".equals(command)) {            //doXX        } else if ("add".equals(command)) {            //doXX        } else if ("delete".equals(command)) {            //doXX        } else if ("update".equals(command)) {            //doXX        }else{            //doXX        }     }}

增加一个业务,你就得增加一个If-else,写类似的代码,后期的维护会让你头痛不已,当你要修改某个分支的功能时,你不得

不把所有的分支用例都跑一遍,确保对其他逻辑没有影响,实际上,这段代码有个共通的逻辑:字典查找功能,根据请求参数找到

对用的处理函数,这就是典型的表驱动模式,也叫表查找:事先注册相应的处理逻辑,在需要使用这些逻辑时,给出逻辑的关键字

找到对应的实现,当你的代码中充斥大量if-else或switch-case,你就应该考虑这种方式,让我们用表驱动来重构以上代码。

表驱动有三大元素:表 ,接口,管理类。

我们先提取一个简单的接口:

package com.czp.opensource.mvc.service;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** *  * @description <p> *              服务接口,每个实现类处理相应的请求 *              </p> * @author Czp * @version 1.0(2014-6-16) *  */public interface ServiceItf {    /**     *      * @desc: <p>     *        处理http请求     *        </p>     * @param request     * @param respose     */    public void process(HttpServletRequest request, HttpServletResponse respose);        /**     *      * @desc:     *     <p>处理的业务类型</p>      * @return     */    public String processType();}

可以看到,这个接口并没有什么特别的,只是HttpServerlet的简单包装,但是他为我们下一步的封装提供了可能。

接下来我们提供一个管理类,管理接口的实现类,包括注册和查找:

package com.czp.opensource.mvc.service;import java.util.concurrent.ConcurrentHashMap;/** *  * @description <p> *              服务工厂:负责注册 删除 查找服务,在整个应用的生命 *              周期类只需要一个工厂,所以实现为单例 *              </p> * @author Czp * @version 1.0(2014-6-16) * @since JDK1.5+ *  */public class ServiceFactory {    private volatile static ServiceFactory INSTANCE;        private ConcurrentHashMap<String, ServiceItf> services;        private ServiceFactory(){        services = new ConcurrentHashMap<String, ServiceItf>();    }        /**     *      * @desc:     *     <p>     *         Double check lock方式延迟初始化     *         只能在JDK1.5+调用     *     </p>      * @return     *    INSTANCE     */    public ServiceFactory getInstance(){        if(INSTANCE==null)        {            synchronized (ServiceFactory.class) {               if(INSTANCE==null)               {                   INSTANCE = new ServiceFactory();               }            }        }        return INSTANCE;    }        /**     *      * @desc:     *     <p>注册处理器</p>      * @param service     * @return     */    public boolean regist(ServiceItf service)    {        ServiceItf putIfAbsent = services.putIfAbsent(service.processType(), service);        return putIfAbsent!=null;    }        /**     *      * @desc:     *     <p>根据业务类型查找处理器</p>      * @param proccessType     * @return     */    public ServiceItf getService(String proccessType)    {        return services.get(proccessType);    }    /**     *      * @desc:     *     <p>清空表</p>     */    public void onClose(){        services.clear();        services = null;    }}

 接下来我们需要在合适的时间点注册我们的实现类,这个操作应该在所有请求到达前,最好的选择就是在web容器初始化的时,

因此,我们选择在listener里注册

package com.czp.opensource.mvc.service;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;/** *  * @description *       <p>上下文环境,负责初始化MVC框架的上下文</p> * @author  *        Czp * @version  *        1.0(2014-6-16) * */public class ContextListener implements ServletContextListener {        @Override    public void contextDestroyed(ServletContextEvent arg0) {        ServiceFactory.getInstance().onClose();    }    @Override    public void contextInitialized(ServletContextEvent arg0) {        ServiceFactory instance = ServiceFactory.getInstance();        instance.regist(new LoginService());        //注册其他服务,在此不赘述        //instance.regist(new LoginService());    }}

接下来我们就可以用这个简易的框架重构SimpleServlet

package com.czp.opensource.mvc.service;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class SimpleServlet extends HttpServlet {    private static final long serialVersionUID = 1L;    @Override    protected void service(HttpServletRequest request,            HttpServletResponse response) throws ServletException, IOException {        String command = request.getParameter("command");        ServiceItf service = ServiceFactory.getInstance().getService(command);        service.process(request, response);    }}

代码从我们的6个if-else简化为短短3行代码,最重要的是,增强了健将性,可维护性和扩展性,当你需要新加任何业务时,

无需要修改和测试原来的逻辑。以上就是spring-mvc struct等所有的MVC框架的本质所在,只不过他们的service是通过xml配置。

 

这个简单的框架存在许多问题,如果你也发现了,请留言指出,下篇我们将进一步完善这个框架。