第23章 Spring MVC初体验(一)

第23章 Spring MVC初体验

本章内容

  • 鸟瞰SpringMVC

  • 实践出真知

Spring MVC的优势如下:

Web层,或者确切点儿说,在框架的控制器的实现方面,SpringMVC对请求处理期间涉及的各种关注点进行了合理而完全的分离,并且明确设置了相应的角色用于建模并处理整个生命周期中的各个关注点,这包括:

  • 设置HandlerMapping用于处理Web请求与具体请求处理控制器之间的映射匹配;

  • 设置LocaleResolver用于国际化处理;

  • 设置ViewResolver用于灵活的视图选择等。

而这些角色所处理的关注点,在此前的大部分Web开发框架中都没有明确划分,或者只是部分分离出来,而现在它们都是可扩展,可替换的。

表现(presentation)层面来看,Spring运用逻辑命名视图(logical named view)策略,通过引入ViewResolverView,清晰地分离了视图类型的选择和渲染(Render)与具体控制器之间的耦合,使得各种视图技术可以很容易地集成到SpringMVC框架中。不管是使用JSP/JISTL作为视图技术,还是使用Velocity/FreeMarker,甚至PDF/Excel等二进制格式的视图形式,要启用它们,现在只是简单的配置工作而已。

Spring MVC独有的一个特色,得益于自己“出身名门”,它可以很容易并且很自然地得到自家“兄弟”的支持,从IoC容器提供的依赖注入支持,到SpringAOP的支持,以及数据访问层、事务管理层的支持等。Spring提供的良好的中间层支持,是之前许多Web开发框架都缺少的东西,而这个是Spring MVC与生俱来的。

鸟瞰Spring MVC

Spring MVC框架的处理控制器的实现策略,与其他的请求驱动的Web框架在总体思路上是相似的,就如我们之前所说的那样,通过引入FrontController和PageController的概念来分离流程控制逻辑与具体的Web请求处理逻辑。

org.springframework.web.servlet.DispatcherServlet就是Spring MVC框架中的FrontContoller,它负责接收并处理所有的Web请求,只不过针对具体的处理逻辑,它会委派给它的下一级控制器去实现,即org.springframework.web.servlet.mvc.Controller,而org.springframework.web.servlet.mvc.Controller则对应PageController的角色定义,具体关系如图23-1所示。

image-20220410172252872

当然,只有它们两个还远远称不上一个完整的Web开发框架,要使得整个的Web开发框架能够运作起来,我们还需要更多的角色相助。下面,让我带你走过DispatcherServlet这道长廊,并趁机一一介绍SpringMVC中的各位“掌权者”。

在我们开始DispatcherServlet的旅程之前,不妨先回顾一下控制器Servlet通常会做哪些工作。就以我们JSP Model 2架构中提到的MockMagicServlet为例(之前特意请你关注过它),它在处理Web请求的过程中所做的工作,我们可以简单归纳为如下三点。

  • 获取请求信息,比如请求的路径、各种参数值等,如下所示:

    String parameter1 = request.getParameter("paramName1");
    String parameter2 = request.getParameter("paramName2");
    
  • 根据请求信息,调用具体的服务对象处理具体的Web请求,如下所示

    List<InfoBean> infoList = MockServletService.query(parameter1, parameter2);
    
  • 处理完成后,将要在视图中显示的模型数据通过request进行传递,最后通过ReguestDispatcher选择具体的jsp视图并显示,如下所示:

    request.setAttribute("infoList", infoList);
    forward(request, response, "view.jsp");
    

有了以上MockMagicServlet处理流程的剖析作为基础,现在我们再来看DispatcherServlet在整个Web请求处理过程中所做的事情,就会发现,DispatcherServlet在实现上并没有太多不同或者说改变,唯一的差异或者改变是,DispatcherServlet将各项工作细化并分离给了较为独立的角色来完成。

DispatcherServlet的处理流程“可以简单概括如下。

1. HandlerMapping先生(Web请求的处理协调人)

既然DispatcherServlet是整个框架的FrontController,当将它注册到web.xml时,就注定了它要服务于规定的一组Web请求的命运,而不是单独的一个Web请求。下方代码是DispatcherServlet注册到web.xml配置文件的通常示例。

<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.Web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

不能够像“一个Web请求对应一个Servlet”那样获取Web容器对URL映射匹配的支持,DispatcherServlet现在只好自己来处理具体的Web请求和具体的处理类之间的映射关系匹配了。

对于Web请求与具体的处理类之间的映射匹配来说,具体的处理方式或者说策略可能多种多样,完全可能随着应用程序,甚至每个具体的Web请求的不同而发生变化,如下所述。

  • 最常见的就是,那种“掐头去尾”的处理方式,将Web请求的URL路径去除前面的上下文路径(context path)和最后的扩展名,取最终剩下的路径信息,作为匹配的结果。比如,如下代码将最终以resource作为匹配结果。http://www.nosuchname.com/yourapp/resource.html
  • 以Web请求的URL中存在的某个参数的值作为匹配的标准。比如,当发现请求的路径中存在controller这个参数的话,其值就会被作为匹配结果用来调用具体的处理类,对于如下所示的URL:http://www.nosuchname.com/yourapp/resource.html?controller=yourController匹配的结果就是yourController。
  • 以cookie或者session中的某些信息作为匹配标准。比如,针对某个客户的Web请求,全部转发给一个处理类进行处理。

甚至于,结合Ruby On Rails的理念,我们在开发中规定一些惯例或者说约定,然后以这些惯例或者约定来解析Web请求的URL路径信息,以获取具体的处理类匹配。可见,要将映射匹配的逻辑写死到DispatcherServlet,是无法有效扩展的,而且匹配的方式也可能随着需求而变化。

所以,SpringMVC为了能够灵活地处理映射的匹配,引入了org.springframework.web.servlet.HandlerMapping来**专门管理Web请求到具体的处理类之间的映射关系。**在Web请求到达DispatcherServlet之后,Dispatcher将寻求具体的HandlerMapping实例,以获取对应当前Web请求的具体处理类,即org.springframework.web.servlet.Controller

2. Controller(Web请求的具体处理者)

org.springframework.Web.servlet.Controller是对应DispatcherServlet的次级控制器,它本身实现了对应某个具体Web请求的处理逻辑。在我们所使用的HandlerMapping查找到当前Web请求对应哪个org.springframework.web.servlet.Controller的具体实例之后,DispatcherServlet即可获得HandlerMapping所返回的结果,并调用org.springframework.web.servlet.Controller的处理方法来处理当前的Web请求。

org.springframework.Web.servlet.Controller的处理方法执行完毕之后,将返回一个org.springframework.Web.servlet.ModelAndView实例,ModelAndView包含了如下两部分信息。

  • 视图的逻辑名称(或者具体的视图实例)。DispatcherServlet将根据该视图的逻辑名称,来决定为用户显示哪个视图。
  • 模型数据。视图渲染过程中需要将这些模型数据并入视图的显示中。有了ModelAndView所包含的视图与模型二者的信息之后,DispatcherServlet就可着手视图的渲染工作了。

3. ViewResolver和View(视图解析器和视图)

按照MockMagicServlet的处理流程,我们已经走到了最后一步,即选择并转到最终的JSP视图文件,即如下代码所展示的逻辑:

request.setAttribute("infoList", infoList);
forward(requst, response, "view.jsp");

但是,对于一个We框架来说,我们是不可以这么简单处理的。为什么呢?不要忘了,现在可用的视图技术可不只JSP一家,Velocity、Freemarker等通用的模板引擎,都可以帮助我们构建相应的视图,而它们是不依赖于request对象来传递模型数据的,甚至,我们也不只依赖JSP专用的RequestDispatcher来输出最终的视图。否则,我们也没有必要通过ModelAndView来返回视图以及模型数据了,直接在org.springframework.web.servlet.Controller`内部完成视图的渲染工作就可以了。不是吗?

鉴于此,Spring提出了一套基于ViewResolverView接口的Web视图处理抽象层,以屏蔽We框架在使用不同的Web视图技术时候的差异性。那么,SpringMVC是如何以统一的方式,将相同的模型数据纳入不同的视图形式并显示的呢?实际上,撇开JSP使用的RequestDispatcher不谈,Servlet自身就提供了两种最基本的视图输出方式。不知道你还是否记得最初的out.println?基本来说,我们要向客户端输出的视图类型,可以分为文本和二进制两种方式,比如JSP/JSTL、Velocity/Freemarker等最终的输出结果都是以(X)HTML等标记文本形式表现的,而PDF/Excel之类则属于二进制内容行列。对于这两种形式的视图内容的输出,Servlet自身公开给我们的HttpServletResponse已经足够可以应付了。

下方代码展示了HttpServletResponse是如何帮助我们处理这两种形式的视图输出的。

//使用Servlet输出标记文本视图
String markupText = ...;
PrintWriter writer = responge.getwriter();
writer.write(markupText);
writer.close();

//使用Servlet输出二进制格式视图
byte[] binaryContext = ...;
ServletOutputStream out = responge.getOutputStrean();
out.write(binaryContext);
out.flush();
out.close();

HttpServletResponse可以同时支持文本形式和二进制形式的视图输出的前提下,我们只要在最终将视图数据通过HttpServletResponse输出之前,借助于不同的视图技术API,并结合模型数据和相应的模板文件,就能生成最终的视图结果,如下伪代码所示:

1获取模型(Model)数据;
2获取视图模板文件(比如*.jsp、*.vm、*.fm、*.x1s等);
3结合视图模板和模型数据,使用相应的视图技术API生成最终视图结果;
4将视图结果通过HttpServletResponse输出到客户端;
5完成!

这样,不管最终生成的视图内容如何,我们都可以以几乎相同的方式输出它们。但唯一的问题在于,我们不可能将每个视图的生成代码都纳入DispatcherServlet的职权范围,毕竟,每种视图技术的生成代码是不同的,而且所使用的视图技术也可能随着具体环境而变化。SpringMVC通过引入org.springframework.Web.servlet.View接口定义,来统一地抽象视图的生成策略。之后,DispatcherServlet只需要根据SpringController处理完毕后通过ModelAndView返回的逻辑视图名称查找到具体的View实现,然后委派该具体的View实现类来根据模型数据,输出具体的视图内容即可。

Spring的View抽象策略示意图如图23-2所示。

image-20220410184743290

现在,视图模板与模型数据的合并逻辑,以及合并后的视图结果的输出逻辑,全部封装到了相应的View实现类中。DispatcherServlet只需要根据ModelAndView返回的信息,选择具体的View实现类做最终的具体工作即可。

不过,与HandlerMapping帮助DispatcherServlet查找具体的Spring Controller以处理Web请求类似,DispatcherServlet现在需要依赖于某一个org.springframework.web.servlet.ViewResolver来帮它处理逻辑视图名与具体的View实例之间的映射对应关系。ViewResolver将根据ModelAndView中的逻辑视图名查找相应的View实现类,然后将查找的结果返回DispatcherServletDispatcherServlet最终会将ModelAndView中的模型数据交给返回的View来处理最终的视图渲染工作。

至此,整个DispatcherServlet的处理流程即告结束。如果你还不足以从以上流程概略中分出哪个角色对应什么样的工作,图23-3所展示的Sequence图或许可以帮助大家加深一下印象。

image-20220410185310226

在了解了SpringMVC中的基本概念之后,让我们开始着手开发我们的第一个基于SpringMVC的Web应用程序。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125741645
今日推荐