[转帖]SpringMVC深度探险(二) —— SpringMVC概览_Android, Python及开发编程讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Android, Python及开发编程讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3157 | 回复: 0   主题: [转帖]SpringMVC深度探险(二) —— SpringMVC概览        下一篇 
haili.yang
注册用户
等级:少校
经验:936
发帖:71
精华:1
注册:2012-12-24
状态:离线
发送短消息息给haili.yang 加好友    发送短消息息给haili.yang 发消息
发表于: IP:您无权察看 2012-12-25 15:53:08 | [全部帖] [楼主帖] 楼主

对于任何事物的研究,总是由表及里、由浅入深地进行。在本系列的第二篇文章中,我们将通过不同的观察视角,对SpringMVC做一些概要性的分析,帮助大家了解SpringMVC的基本构成要素、SpringMVC的发展历程以及SpringMVC的设计原则。 

SpringMVC的构成要素 

了解一个框架的首要任务就是搞清楚这个框架的基本构成要素。当然,这里所说的构成要素实际上还可以被挖掘为两个不同的层次: 

  • 基于框架所编写的应用程序的构成要素
  • 框架自身的运行主线以及微观构成要素
我们在这里首先来关注一下第一个层次,因为第一个层次是广大程序员直接能够接触得到的部分。而第二个层次的讨论,我们不得不在第一个��次的讨论基础之上通过不断分析和逻辑论证慢慢给出答案。 

在上一篇文章中,我们曾经列举了一段SpringMVC的代码示例,用于说明MVC框架的构成结构。我们在这里不妨将这个示例细化,总结归纳出构成SpringMVC应用程序的基本要素。 

1. 指定SpringMVC的入口程序(在web.xml中) 

Xml代码   北京联动北方科技有限公司


<!-- Processes application requests -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/**</url-pattern>
</servlet-mapping>


以一个Servlet作为入口程序是绝大多数MVC框架都遵循的基本设计方案。这里的DispatcherServlet被我们称之为核心分发器,是SpringMVC最重要的类之一,之后我们会对其单独展开进行分析。



2. 编写SpringMVC的核心配置文件(在[servlet-name]-servlet.xml中) 

Xml代码   北京联动北方科技有限公司


<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd"
default-autowire="byName">
<!-- Enables the Spring MVC @Controller programming model -->
<mvc:annotation-driven />
<context:component-scan base-package="com.demo2do" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>


SpringMVC自身由众多不同的组件共同构成,而每一个组件又有众多不同的实现模式。这里的SpringMVC核心配置文件是定义SpringMVC行为方式的一个窗口,用于指定每一个组件的实现模式。有关SpringMVC组件的概念,在之后的讨论中也会涉及。



3. 编写控制(Controller)层的代码 

Java代码   北京联动北方科技有限公司


@Controller
@RequestMapping
public class UserController {
      @RequestMapping("/login")
      public ModelAndView login(String name, String password) {
            // write your logic here
            return new ModelAndView("success");
      }
}


控制(Controller)层的代码编写在一个Java文件中。我们可以看到这个Java文件是一个普通的Java类并不依赖于任何接口。只是在响应类和响应方法上使用了Annotation的语法将它与Http请求对应起来。

从这个例子中,我们实际上已经归纳了构成基于SpringMVC应用程序的最基本要素。它们分别是: 

  • 入口程序 —— DispatcherServlet
  • 核心配置 —— [servlet-name]-servlet.xml
  • 控制逻辑 —— UserController
从应用程序自身的角度来看,入口程序和核心配置一旦确定之后将保持固定不变的,而控制逻辑则随着整个应用程序功能模块的扩展而不断增加。所以在这种编程模式下,应用程序的纵向扩展非常简单并且显得游刃有余。 

基于SpringMVC的应用程序能够表现为现在这个样子,经历了一个不断重构不断改造的过程。接下来的讨论,我们就来试图为大家揭秘这个过程。 

SpringMVC的发展历程 

在上一篇文章中,我们曾经讨论过MVC的发展轨迹。当时我们总结了一个MVC框架的发展轨迹图: 

北京联动北方科技有限公司

从图中我们可以发现,所有的MVC框架都是从基本的Servlet模型发展而来。因此,要了解SpringMVC的发展历程,我们还是从最基本的Servlet模型开始,探究SpringMVC对于Servlet模型的改造过程中究竟经历了哪些阶段、碰到了哪些问题、并看看SpringMVC是如何解决这些问题的。 

【核心Servlet的提炼】

在Servlet模型中,请求-响应的实现依赖于两大元素的共同配合: 

1. 配置Servlet及其映射关系(在web.xml中) 

Xml代码   北京联动北方科技有限公司


<servlet>
<servlet-name>registerServlet</servlet-name>
<servlet-class>com.demo2do.springmvc.web.RegisterServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>registerServlet</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>


在这里,<url-pattern>定义了整个请求-响应的映射载体:URL;而<servlet-name>则将<servlet>节点和<servlet-mapping>节点联系在一起形成请求-响应的映射关系;<servlet-class>则定义了具体进行响应的Servlet实现类。



2. 在Servlet实现类中完成响应逻辑 

Java代码   北京联动北方科技有限公司


public class RegisterServlet extends HttpServlet {
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 从request获取参数  
            String name = req.getParameter("name");
            String birthdayString = req.getParameter("birthday");
            // 做必要的类型转化  
            Date birthday = null;
            try {
                  birthday = new SmpleDateFormat("yyyy-MM-dd").parse(birthdayString);
            } catch (ParseException e) {
                  e.printStackTrace();
            }
            // 初始化User类,并设置字段到user对象中去  
            User user = new User();
            user.setName(name);
            user.setBirthday(birthday);
            // 调用业务逻辑代码完成注册  
            UserService userService = new UserService();
            userService.register(user);
            // 设置返回数据  
            request.setAttribute("user", user);
            // 返回成功页面  
            req.getRequestDispatcher("/success.jsp").forward(req, resp);
      }
}


Servlet实现类本质上是一个Java类。通过Servlet接口定义中的HttpServletRequest对象,我们可以处理整个请求生命周期中的数据;通过HttpServletResponse对象,我们可以处理Http响应行为。



整个过程并不复杂,因为作为一个底层规范,所规定的编程元素和实现方式应该尽可能直观和简单。在这一点上,Servlet规范似乎可以满足我们的要求。如果将上述过程中的主要过程加以抽象,我们可以发现有两个非常重要概念蕴含在了Servlet的规范之中: 

北京联动北方科技有限公司

控制流和数据流的问题几��贯穿了所有MVC框架的始末,因而我们不得不在这里率先提出来,希望对读者有一些警示作用。 

注:对于控制流和数据流的相关概念,请参考另外一篇博客:《Struts2技术内幕》 新书部分篇章连载(五)—— 请求响应哲学。这一对概念,几乎是所有MVC框架背后最为重要的支撑,读者应该尤其重视!



所有MVC框架的核心问题也由控制流和数据流这两大体系延伸开来。比如,在Servlet编程模型之下,“请求-响应映射关系的定义”这一问题就会随着项目规模的扩大而显得力不从心: 

问题1 写道

项目规模扩大之后,请求-响应的映射关系全部定义在web.xml中,将造成web.xml的不断膨胀而变得难以维护。



针对这个问题,SpringMVC提出的方案就是:提炼一个核心的Servlet覆盖对所有Http请求的处理。。 

这一被提炼出来的Servlet,通常被我们称之为:核心分发器。在SpringMVC中,核心分发器就是org.springframework.web.servlet.DispatcherServlet。 

注:核心分发器的概念并非SpringMVC独创。我们可以看到,核心分发器的提炼几乎是所有MVC框架设计中的必经之路。在Struts2中,也有核心分发器(Dispatcher)的概念,只是它并不以Servlet的形式出现。因此,读者应该把关注点放在核心分发器这个概念的提炼之上,而不是纠结于其形式。



有了DispatcherServlet,我们至少从表面上解决了上面的问题。至少在web.xml中,我们的配置代码就被固定了下来: 

Xml代码   北京联动北方科技有限公司


<!-- Processes application requests -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/**</url-pattern>
</servlet-mapping>


有了DispatcherServlet,我们只相当于迈出了坚实的第一步,因为对核心Servlet的提炼不仅仅是将所有的Servlet集中在一起那么简单,我们还将面临两大问题: 

问题2 写道

核心Servlet应该能够根据一定的规则对不同的Http请求分发到不同的Servlet对象上去进行处理。


问题3 写道

核心Servlet应该能够建立起一整套完整的对所有Http请求进行规范化处理的流程。



而这两大问题的解决,涉及到了DispatcherServlet的设计核心。我们也不得不引入另外一个重要的编程元素,那就是:组件。 

【组件的引入】

DispatcherServlet的引入是我们通过加入新的编程元素来对基本的Servlet规范进行抽象概括所迈出的第一步。不过接下来,有关DispatcherServlet的设计问题又一次摆到了我们的面前。 

如果仔细分析一下上一节末尾所提出的两个问题,我们可以发现这两个问题实际上都涉及到了DispatcherServlet的处理过程,这一处理过程首先必须是一剂万能药,能够处理所有的Http请求;同时,DispatcherServlet还需要完成不同协议之间的转化工作(从Http��议到Java世界的转化)。 

对此,SpringMVC所提出的方案是:将整个处理流程规范化,并把每一个处理步骤分派到不同的组件中进行处理。 

这个方案实际上涉及到两个方面: 

  • 处理流程规范化 —— 将处理流程划分为若干个步骤(任务),并使用一条明确的逻辑主线将所有的步骤串联起来
  • 处理流程组件化 —— 将处理流程中的每一个步骤(任务)都定义为接口,并为每个接口赋予不同的实现模式
在SpringMVC的设计中,这两个方面的内容总是在一个不断交叉、互为补充的过程中逐步完善的。 

处理流程规范化是目的,对于处理过程的步骤划分和流程定义则是手段。因而处理流程规范化的首要内容就是考虑一个通用的Servlet响应程序大致应该包含的逻辑步骤: 

  • 步骤1 —— 对Http请求进行初步处理,查找与之对应的Controller处理类(方法)
  • 步骤2 —— 调用相应的Controller处理类(方法)完成业务逻辑
  • 步骤3 —— 对Controller处理类(方法)调用时可能发生的异常进行处理
  • 步骤4 —— 根据Controller处理类(方法)的调用结果,进行Http响应处理
这些逻辑步骤虽然还在我们的脑海中,不过这些过程恰恰正是我们对整个处理过程的流程化概括,稍后我们就会把它们进行程序化处理。 

所谓的程序化,实���上也就是使用编程语言将这些逻辑语义表达出来。在Java语言中,最适合表达逻辑处理语义的语法结构是接口,因此上述的四个流程也就被定义为了四个不同接口,它们分别是: 

  • 步骤1 —— HandlerMapping
  • 步骤2 —— HandlerAdapter
  • 步骤3 —— HandlerExceptionResolver
  • 步骤4 —— ViewResolver
结合之前我们对流程组件化的解释,这些接口的定义不正是处理流程组件化的步骤嘛?这些接口,就是组件。 

除了上述组件之外,SpringMVC所定义的组件几乎涵盖了每一个处理过程中的重要节点。我们在这里引用Spring官方reference中对于最基本的组件的一些说明: 

北京联动北方科技有限公司

我们在之后篇文章中将重点对这里所提到的所有组件做深入的分析。大家在这里需要理解的是SpringMVC定义这些组件的目的和初衷。 

这些组件一旦被定义,自然而然也就引出了下一个问题:这些组件是如何串联在一起的?这个过程,是在DispatcherServlet中完成的。有关这一点,我们可以从两个不同的角度加以证明。 

1. 从DispatcherServlet自身数据结构的角度 

北京联动北方科技有限公司

如图中所示,DispatcherServlet中包含了众多SpringMVC的组件,这些组件是实现DispatcherServlet核心逻辑的基础。



2. 从DispatcherServlet的核心源码的角度 

Java代码   北京联动北方科技有限公司


try {
      // 这里省略了部分代码  
      // 获取HandlerMapping组件返回的执行链  
      mappedHandler = getHandler(processedRequest, false);
      if (mappedHandler == null || mappedHandler.getHandler() == null) {
            noHandlerFound(processedRequest, response);
            return;
      }
      // 获取HandlerAdapter组件  
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
      // 这里省略了部分源码  
      // 调用HandlerAdapter组件  
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
      // 这里省略了部分源码  
}catch (ModelAndViewDefiningException ex) {
      logger.debug("ModelAndViewDefiningException encountered", ex);
      mv = ex.getModelAndView();
}catch (Exception ex) {
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      // 调用HandlerExceptionResolver进行异常处理  
      mv = processHandlerException(processedRequest, response, handler, ex);
      errorView = (mv != null);
}


从上面的代码片段中,我们可以看到DispatcherServlet的核心逻辑不过是对组件的获取和调用。



除此之外,SpringMVC对处理流程的规范化和组件化所引出的另外一个问题就是如何针对所有的组件进行管理。 

先说说管理。其实管理这些组件对于SpringMVC来说完全不是问题,因为SpringMVC作为Spring Framework的一部分,其自身的运行环境就是Spring所定义的容器之中。我们知道,Spring Framework的核心作用之一就是对整个应用程序的组件进行管理。所以SpringMVC对于这些已定义组件的管理,只不过是借用了Spring自身已经提供的容器功能而已。 

注:SpringMVC在进行组件管理时,会单独为SpringMVC相关的组件构建一个容器环境,这一容器环境可以独立于应用程序自身所创建的Spring容器。有关这一点,我们在之后的讨论中将详细给出分析。



而SpringMVC对这些组件的管理载体,就是我们在上一节中所提到的核心配置文件。我们可以看到,核心配置文件在整个SpringMVC的构成要素中占有一席之地的重要原因就是在于:我们必须借助一个有效的手段对整个SpringMVC的组件进行定义,而这一点正是通过核心配置文件来完成的。 

如果我们把上面的整个过程重新梳理一下,整个逻辑看起来就像这样: 

北京联动北方科技有限公司

这四个方面的内容,我们是顺着设计思路的不断推进而总结归纳出来的。这也恰好证明之前所提到的一个重要观点,我们在这里强调一下: 

downpour 写道

处理流程的规范化和组件化,是在一个不断交叉、互为补充的过程中逐步完善的。



【行为模式的扩展】

有了组件,也有了DispatcherServlet对所有组件的串联,我们之前所提出的两个问题似乎已经可以迎刃而解。所以,我们可以说: 

downpour 写道

SpringMVC就是通过DispatcherServlet将一堆组件串联起来的Web框架。



在引入组件这个概念的时候,我们所强调的是处理流程的抽象化,因而所有组件的外在表现形式是接口。接口最重要意义是定义操作规范,所以接口用来表达每一个处理单元的逻辑语义是最合适不过的。但光有接口,并不能完整地构成一个框架的行为模式。从操作规范到行为模式的变化,是由接口所对应的实现类来完成的。 

在Java语言中,一个接口可以有多个不同的实现类,从而构成一个树形的实现体系。而每一个不同的实现分支,实际上代表的是对于相同的逻辑语义的不同解读方式。结合上面我们的描述,也可以说:一个接口的每一个不同的实现分支,代表了相同操作规范的不同行为模式。 

我们可以通过之前曾经提到过的一个SpringMVC组件HandlerMapping为例进行说明。 

北京联动北方科技有限公司

上图就是HandlerMapping接口的树形实现体系。在这个实现体系结构中,每一个树形结构的末端实现都是SpringMVC中比较具有典型意义的行为模式。我们可以截取其中的几个实现来加以说明: 

  • BeanNameUrlHandlerMapping —— 根据Spring容器中的bean的定义来指定请求映射关系
  • SimpleUrlHandlerMapping —— 直接指定URL与Controller的映射关系,其中的URL支持Ant风格
  • DefaultAnnotationHandlerMapping —— 支持通过直接扫描Controller类中的Annotation来确定请求映射关系
  • RequestMappingHandlerMapping —— 通过扫描Controller类中的Annotation来确定请求映射关系的另外一个实现类
有关这几个实现类的具体示例和使用说明,读者可以参考不同版本的Spring官方文档来获取具体的细节。 

注:我们在这里之所以要强调不同版本的Spring官方文档的原因在于这些不同的实现类,正代表了不同版本SpringMVC在默认行为模式上选择的不同。在下图中,我们列出了不同重大版本的SpringMVC的实现体系结构,并用红色框圈出了每个版本默认的实现类。

北京联动北方科技有限公司

我们可以看到,上述这些不同的HandlerMapping的实现类,其运行机制和行为模式完全不同。这也就意味着对于HandlerMapping这个组件而言,可以进行选择的余地就很大。我们既可以选择其中的一种实现模式作为默认的行为模式,也可以将这些实现类依次串联起来成为一个执行链。不过这已经是实现层面和设计模式上的小技巧了。 

单就HandlerMapping一个组件,我们就能看到各种不同的行为模式。如果我们将逻辑主线中所有的组件全部考虑进来,那么整个实现机制就会随着这些组件实现体系的不同而表现出截然不同的行为方式了。因此,我们的结论是: 

downpour 写道

SpringMVC各种不同的组件实现体系成为了SpringMVC行为模式扩展的有效途径。



有关SpringMVC的各种组件和实现体系,我们将在之后的讨论中详细展开。 




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论