首页 > 代码库 > 如何设计一个易用的MVC框架
如何设计一个易用的MVC框架
把一件简单的事情做复杂很容易,把一件复杂的事情做简单却不易。在计算机的世界里,
冯.诺依曼把复杂的电脑简化为:存储器,控制器,运算器和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配置。
这个简单的框架存在许多问题,如果你也发现了,请留言指出,下篇我们将进一步完善这个框架。