前言
最近在看
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
其他的理解