NotOnlyJava

http://www.ibm.com/developerworks/cn/java/j-lo-serial/
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

Posted on 2014-06-17 20:56  NotOnlyJava  阅读(2389)  评论(1编辑  收藏  举报

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

冯.诺依曼把复杂的电脑简化为:存储器,控制器,运算器和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配置。

 

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