对Spring MVC Framework的理解

Posted on 2005-05-27 11:29 李岚 阅读(3287) 评论(3)  编辑  收藏 所属分类: Java

前言

最近在看Spring MVC的源码,就把自己对MVC模式和对各种框架的实现的认识写出来给大家看看,算是一个总结.所以,恳请大家用怀疑的眼光来看待这篇文章,假如有认识不对的地方,麻烦指出.

MVCWEB应用

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的框架实现.比较流行的应该就是StrutsWebwork

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模式为基础.分为XworkWebwork,而且框架并不依赖于Servlet API.

 

Xwork提供了很多核心功能:拦截器(Interceptor,运行时表单验证,类型转换,IoC容器等.

 

WebWork建立在Xwork之上,用于处理基于HTTP的响应和请求.MapActionContext封装了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会在调用选定的ControllerhandlerRequest方法,并且在这个方法前后调用这个Controllerinterceptor(假如有配置的话),然后返回一个视图和模型的集合ModelAndView.框架通过ViewResolver来解析视图并且返回一个View对象,最后调用Viewrender方法返回到客户端

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() 这个方法.

 

特别的, FrameworkServletinitFrameworkServlet()这个方法是控制器的初始化方法,用来初始化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对象代表一个ControllerURL的映射(其实在运行的时候是一个HandlerExecutionChainURL的映射,HandlerExecutionChain对象其实就是对Controller和它interceptors的一个包装器,可以把HandlerMapping看成ControllerURL的映射).而这个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());

发现ControllerhandleRequest真正的操作又被代理给了HandlerAdapterhandle方法,并且返回一个ModelAndView,我想这里增加一层的意义应该是为了解除ControllerDispatchServlet的耦合吧.

 

接着就很简单了,调用render()方法,在这个方法里面由ViewResoler解析出视图名,再调用视图对象的render方法把合适的视图展现给用户

 

到此,控制器的流程就OVER

HandlerMapping

通过使用HandlerMapping,控制器可以用URL和某一个Controller进行标准的映射,而实现URL映射的具体子类的UrlHandlerMapping.

 

Spring还允许我们自定义映射,比如通过Session,cookie或者用户状态来映射.而这一切仅仅只需要实现HandlerMapping接口而已.不过URL映射已经能满足大部分的要求

Controller

Controller 类似StructsAction, 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

总结:

Springweb框架是一个很优秀的框架,在这里只是走马观花的分析了Spring的工作流程和一些关键的类,但是并没有去深入的去探讨它背后所体现的思想,还有它的优缺点等东西.这些都等下次再说吧

 

这里再次说明一下,上面说的只是我自己的想法,假如有不理解或者不正确的地方,请在下面留言指出或者查看官方的<Spring-Reference>,以后会逐渐推出自己对Spring其他的理解

Feedback

# re: 对Spring MVC Framework的理解  回复  更多评论   

2005-11-10 12:40 by pesome
写的很不错啊,我现在才开始学spring

# re: 对Spring MVC Framework的理解  回复  更多评论   

2006-04-03 16:35 by 朱吉赟
你依然那么蒸蒸日上。

我手机丢了,QQ密码忘了,差点与世隔绝了。

你最近怎么样?

我的新QQ:85604123

# re: 对Spring MVC Framework的理解  回复  更多评论   

2008-01-11 11:39 by bizairshop
看来spring mvc不是我的理想框架 航服推荐 http://www.bizairshop.com

# re: 对Spring MVC Framework的理解  回复  更多评论   

2013-05-28 14:03 by JAVA22222
看来spring mvc不是我的理想框架

只有注册用户登录后才能发表评论。


网站导航:
 

posts - 7, comments - 23, trackbacks - 0, articles - 0

Copyright © 李岚