前言
最近在看Spring MVC的源码,就把自己对MVC模式和对各种框架的实现的认识写出来给大家看看,算是一个总结.所以,恳请大家用怀疑的眼光来看待这篇文章,假如有认识不对的地方,麻烦指出.
MVC与WEB应用
MVC是什么就不用我多说了.对于现有较成熟的Model-View-Control(MVC)框架而言,其注意的主要问题无外乎下面这些:
Model:
模型应该包含由视图显示的数据.在J2EE Web应用中,数据通常应该由普通的javabean组成.一旦一个控制器选择了视图,模型就要包含视图相应的数据.模型本身不应该进一步的访问数据,也不应该和业务对象相联系.
模型要解决的问题包括:
l 封装要显示的数据
l 我不认为模型要依赖于特定的框架
l 不一定非得是javabean
View:
视图负责显示出模型包含的信息,视图不必了解控制器或是底层业务对象的具体实现
视图要解决的问题包括:
l 在显示给定数据模型的情况下显示内容
l 不应该包含有业务逻辑
l 可能需要执行显示逻辑,比如颜色交替的显示某个数组的各行
l 视图最好不处理验证的错误,数据的验证应该在由其他组件完成
l 视图不应该处理参数,参数应该交由控制器集中处理
Control:
控制器就好像MVC里的中枢神经,它也许会需要一些助手来帮助它比如解析视图,解析参数等.控制器可以访问到业务对象或者是它的代理是很重要的,比如Struts里的Action.
控制器要解决的问题包括:
l 检查和抽取请求参数
l 调用业务对象,传递从请求中获取的参数
l 创建模型,视图讲显示对应的模型
l 选择一个合适的视图发送给客户端
l 控制器有时不会只有一个
现有的框架
现在已经有很多的MVC的框架实现.比较流行的应该就是Struts和Webwork了
Struts
这是最流行的web框架,几乎成为了实际上的工业标准.除了上面讨论的MVC模式应该有的优点以外.它还有如下一些缺点:
l 每个Action只生成一次,然后就被缓存起来,再次请求这个Action的时候就不会生成新的对象,而是重复使用第一次生成的对象,这就意味着每个Action必须是线程安全的
l 采用ActionForm封装了表单数据,但是却只能对应String类型的数据, 虽然它可以使用工具Commons Beanutils进行类型转化,但是仅仅是提供了对象级别的支持
l 严重的依赖于Servlet API, 测试比较困难(不过下一版Struts里的Action.execute的方法签名讲会换成execute(ActionContext actionContext),依赖也许不会那么严重)
l 框架本身的验证规则比较简单,一般都是依赖于Commons Validation进行验证
l 想在Action前后做些处理很困难.有时甚至不得不自己去写专门的控制器
l 由于Struts都是具体的类继承,这样很容易打破封装?
l 提供各式各样的自定义的标签,但是数据绑定太原始了,这样就使页面代码依赖于Struts这个特定的框架,而它却不是规范,我觉得这是很致命的
l 它太面向JSP了,尽管使用其他视图技术是有可能的,但是使用的时候却不是很方便
Webwork
这个框架虽然我没使用过,但是却一直在关注它的发展
Webwork的设计思想采用了比Struts更为聪明的一种方式,就技术角度上说比Struts要高出不少.它以Command模式为基础.分为Xwork和Webwork,而且框架并不依赖于Servlet API.
Xwork提供了很多核心功能:拦截器(Interceptor),运行时表单验证,类型转换,IoC容器等.
WebWork建立在Xwork之上,用于处理基于HTTP的响应和请求.用Map和ActionContext封装了Session,Application等这些Servlet对象.从而解除了和Servlet API的耦合.
但是它仍然不是完美的:
l 为每一个请求都创建一个Action可能有些浪费.(但是Servlet引擎也是为每个请求创建多个对象,但是也没看出来对性能有多大的影响?)
l 当项目越来越大的时候,配置文件可能会很零乱.好像它不支持多个配置文件
l 异常处理是Command模式里值得注意的问题:我们不知道某一特定命令可能会抛出什么特定的异常,所以execute()被迫抛出异常,而不论异常是运行时异常,还是已检查异常
Spring MVC Framework的目标
上面说了一些MVC的原理,以及现在主流框架的一些问题,现在来看Spring是如何处理的. Spring MVC框架根据不同的角色定义了很多接口,但是它最大的问题也是依赖于Servlet API
Spring MVC Framework有这样一些特点:
l 它是基于组件技术的.全部的应用对象,无论控制器和视图,还是业务对象之类的都是java组件.并且和Spring提供的其他基础结构紧密集成.
l 不依赖于Servlet API(目标虽是如此,但是在实现的时候确实是依赖于Servlet的)
l 可以任意使用各种视图技术,而不仅仅局限于JSP
l 支持各种请求资源的映射策略
l 它应是易于扩展的
我认为评价一个框架,应该有几个原则
l 它应该是易于使用的,易于测试的
Spring 易于使用吗?我不这么觉得,尤其是它的配置文件.在最恐怖的情况下,各种业务逻辑,基础设施也许会拥挤在一个配置文件里.而如事务处理这些基础设施应该是由容器管理而不是开发人员,就算把这些分开到几个配置文件里,逻辑上虽然清晰了,但是基础设置却还是暴露在外边
Spring易于测试吗?对Spring进行单元测试很容易,测试起来很方便
l 应该在多个层次上提供接口
Spring提供了很多接口,而几乎每个接口都有默认的抽象实现,每个抽象实现都有一些具体实现,所以在可扩展性这点上Spring无疑是很优秀的
l 框架内部和框架外部应该被区别对待
框架内部可以很复杂,但是使用起来一定要简单,Spring的内部比较麻烦,但是它很好的隐藏了这种复杂性,使用起来很舒服,比如设置一个bean的属性.仅仅是setPropertyValue
(String propertyName, Object value)就完成,至于怎么去设置,Spring完全隐藏了这种复杂性
l 完善的文档和测试集
这个就不用说了,老外的东西,都很完善
Spring Web框架基本流程
知道了Spring MVC框架,现在来看看它的流程
Spring MVC Framework大至流程如下:
当web程序启动的时候,ContextLoaderServlet会把对应的配置文件信息读取出来,通过注射去初始化控制器DispatchServlet. 而当接受到一个HTTP请求的时候, DispatchServlet会让HandlerMapping去处理这个请求.HandlerMapping根据请求URL(不一定非要是URL,完全可以自定义,非常灵活)来选择一个Controller. 然后DispatchServlet会在调用选定的Controller的handlerRequest方法,并且在这个方法前后调用这个Controller的interceptor(假如有配置的话),然后返回一个视图和模型的集合ModelAndView.框架通过ViewResolver来解析视图并且返回一个View对象,最后调用View的render方法返回到客户端
DispatcherServlet
这是框架的控制器,是一个具体类,它通过运行时的上下文对象来初始化.控制器本身并不去控制流程,而只是是Controller的”控制器”,他只是把处理请求的责任委托给了对应的Controller.
控制器继承自抽象基类FrameworkServlet,它的属性webApplicationContext就代表着这个web程序上下文,而这个上下文对象默认实现就是从一个XML文件读取配置信息(当然也可以是其他文件格式). WebApplicationContext其实是beans包的东西,这个包提供了这个Spring整个框架的基础结构,以后我会分析这个包的内容.但是现在仅仅需要知道WebApplicationContext代表一个web应用的上下文对象.
现在来看看DispatchServlet是如何工作的:
DispatchServlet由于继承自抽象基类FrameworkServlet,而FrameworkServlet里的doGet(),doPost()方法里有调用serviceWrapper(),跳到serviceWrapper()里去看,结果发现它有把具体实现委托给了doService(request, response); 方法.所以现在已经很清楚了, DispatchServlet真正实现功能的是doService() 这个方法.
特别的, FrameworkServlet的initFrameworkServlet()这个方法是控制器的初始化方法,用来初始化HandlerMappings之类的对象,这也是延迟到子类实现的.其实就是一个Template模式的实现.don’t call us, we will call u.总的看来,Spring就是通过这样来实现它的控制反转的:用框架来控制流程,而不是用户
跳到doService()一看究竟,就会发现真正工作的又是另一个助手函数doDispatch(request, response),没办法,继续看下去,发现这样两行代码
HandlerExecutionChain mappedHandler = null;
mappedHandler = getHandler(processedRequest, false);
看HandlerExecutionChain源码就发现它其实就是对Controller和它的Interceptors的进行了包装;
getHandler()就是从HandlerMappings(这是一个List,存放的handlerMapping对象)中取出对应的handlerMapping对象, 每个HandlerMapping对象代表一个Controller和URL的映射(其实在运行的时候是一个HandlerExecutionChain和URL的映射,而HandlerExecutionChain对象其实就是对Controller和它interceptors的一个包装器,可以把HandlerMapping看成Controller和URL的映射).而这个HandlerMapping是通过配置文件在运行时注射进来的,一般是SimpleUrlHandlerMapping这个子类
取得了HandlerMapping对象,继续向下看,发现:
if (mappedHandler.getInterceptors() != null) {
for (int i = 0; i < mappedHandler.getInterceptors().length; i++) {
HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
这里就是在调用Controller的拦截器,原理就是这句了:
interceptor.preHandle(processedRequest, response, mappedHandler.getHandler(), mv);
preHandle方法传入了mappedHandler.getHandler()这个参数来实现递归调用!而 interceptor.postHandle方法如此一般.只不过这个方法是在handleRequest方法后调用
继续看下去:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
发现Controller的handleRequest真正的操作又被代理给了HandlerAdapter的handle方法,并且返回一个ModelAndView,我想这里增加一层的意义应该是为了解除Controller和DispatchServlet的耦合吧.
接着就很简单了,调用render()方法,在这个方法里面由ViewResoler解析出视图名,再调用视图对象的render方法把合适的视图展现给用户
到此,控制器的流程就OVER了
HandlerMapping
通过使用HandlerMapping,控制器可以用URL和某一个Controller进行标准的映射,而实现URL映射的具体子类的UrlHandlerMapping.
Spring还允许我们自定义映射,比如通过Session,cookie或者用户状态来映射.而这一切仅仅只需要实现HandlerMapping接口而已.不过URL映射已经能满足大部分的要求
Controller
Controller 类似Structs的Action, Controller接口只有一个方法handleRequest(),放回一个ModelAndView对象,如同设计目标所说的那样,每个Controller都是一个java组件,所以它可以在上下文环境中任意配置,组件属性都会在初始化的时候被配置.Spring自己提供了几个具体的实现.方便我们使用
ViewResolver
Controller通常返回包含视图名字而不是视图对象的ModelAndView对象.从而彻底的解除了控制器和视图之间的耦合关系,并且在这里还可以提供国际化的支持.
在你的配置文件中你可以:
welcomeView.class = org.springframework.web.servlet.view. InternalResourceView
welcomeView.url=/welcome.jsp
也可以
welcomeView.class = org.springframework.web.servlet.view.xslt. XsltView
welcomeView.url=/xslt/default.xslt
View
这也是一个java组件,它不做任何请求处理或是业务逻辑,它仅仅获取模型传递的数据,并把数据显示出来.它里面的 render方法按照如下流程工作:
l 设置模型的数据到request作用域
l 取得视图的URL
l 转发到对应的URL
总结:
Spring的web框架是一个很优秀的框架,在这里只是走马观花的分析了Spring的工作流程和一些关键的类,但是并没有去深入的去探讨它背后所体现的思想,还有它的优缺点等东西.这些都等下次再说吧
这里再次说明一下,上面说的只是我自己的想法,假如有不理解或者不正确的地方,请在下面留言指出或者查看官方的<Spring-Reference>,以后会逐渐推出自己对Spring其他的理解