Spring基础知识(12)- Spring MVC (二) | 执行流程、视图解析器、@Controller和@RequestMapping注解


1. Spring MVC执行流程

    1)  HTTP request 请求的执行流程

        (1) 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
        (2) 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain);
        (3) DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
        (4) HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
        (5) Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
        (6) HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
        (7) DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
        (8) ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
        (9) DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
        (10) 视图负责将结果显示到浏览器(客户端)。

    2)  组件或接口

        Spring MVC 涉及到的组件或接口如下。

        (1) DispatcherServlet(前端控制器)

            DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

        (2) HandlerMapping(处理器映射器)

            HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。

        (3) HandlerAdapter(处理器适配器)

            HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。

        (4) Handler(处理器)

            Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。

        (5) ViewResolver(视图解析器)

            View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。

        (6) View(视图)

            View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。

        以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。


2. 视图解析器(ViewResolver)

    在 “Spring基础知识(11)- Spring MVC (一)” 里创建的 SpringmvcBasic 项目中,TestController 的 handleRequest() 方法里 ModelAndView 加载了 demo.jsp, 代码如下:

 1         package com.example.controller;
 2 
 3         import javax.servlet.http.HttpServletRequest;
 4         import javax.servlet.http.HttpServletResponse;
 5         import org.springframework.ui.ModelMap;
 6         import org.springframework.web.servlet.ModelAndView;
 7         import org.springframework.web.servlet.mvc.Controller;
 8 
 9         public class TestController implements Controller {
10             public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
11                 ModelMap modelMap = new ModelMap();
12                 modelMap.addAttribute("message", "Spring MVC Demo Page");
13                 return new ModelAndView("/WEB-INF/jsp/demo.jsp", modelMap);
14             }
15         }


    这里的 demo.jsp 作为 View 实体文件,使用全路径 "/WEB-INF/jsp/demo.jsp", "/" 指 webapp 的根目录,文件类型是 JSP。

    在 controller 下的方法里,如果不想关心 View 文件的路径和文件类型,只通过 View 的名字(比如,"demo")就加载实体 View 文件,就需要用到视图解析器(ViewResolver)。Spring MVC 提供了多个视图解析器,下面介绍一些常用的视图解析器。

    1) URLBasedViewResolver

        UrlBasedViewResolver 是对 ViewResolver 的一种简单实现,主要提供了一种拼接 URL 的方式来解析视图。

        UrlBasedViewResolver 通过 prefix 属性指定前缀,suffix 属性指定后缀。当 ModelAndView 对象返回具体的 View 名称时,它会将前缀 prefix 和后缀 suffix 与具体的视图名称拼接,得到一个视图资源文件的具体加载路径,从而加载真正的视图文件并反馈给用户。

        使用 UrlBasedViewResolver 除了要配置前缀和后缀属性之外,还需要配置“viewClass”,表示解析成哪种视图。示例代码如下。

1             <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">            
2                 <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--不能省略-->
3                 <!--前缀-->
4                 <property name="prefix" value="/WEB-INF/jsp/"/>
5                 <!--后缀-->
6                 <property name="suffix" value=".jsp"/>  
7             </bean>


        上述 viewClass 值为 InternalResourceViewResolver,它用来展示 JSP 页面。如果需要使用 jstl 标签展示数据,将 viewClass 属性值指定为 JstlView 即可。

        另外,存放在 /WEB-INF/ 目录下的内容不能直接通过 request 请求得到,所以为了安全性考虑,通常把 jsp 文件放在 WEB-INF 目录下。

    2) InternalResourceViewResolver

        InternalResourceViewResolver 为“内部资源视图解析器”,是日常开发中最常用的视图解析器类型。它是 URLBasedViewResolver 的子类,拥有 URLBasedViewResolver 的一切特性。

        InternalResourceViewResolver 能自动将返回的视图名称解析为 InternalResourceView 类型的对象。InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。
        
        也就是说,使用 InternalResourceViewResolver 视图解析时,无需再单独指定 viewClass 属性。示例代码如下。

1             <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
2                 <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceViewResolver"/> <!--可以省略-->
3                 <!--前缀-->
4                 <property name="prefix" value="/WEB-INF/jsp/"/>
5                 <!--后缀-->
6                 <property name="suffix" value=".jsp"/>  
7             </bean>


    3) FreeMarkerViewResolver

        FreeMarkerViewResolver 是 UrlBasedViewResolver 的子类,可以通过 prefix 属性指定前缀,通过 suffix 属性指定后缀。

        FreeMarkerViewResolver 最终会解析逻辑视图配置,返回 freemarker 模板。不需要指定 viewClass,配置如下。

1             <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
2                 <property name="prefix" value="fm_"/>
3                 <property name="suffix" value=".ftl"/>
4             </bean>


        下面指定 FreeMarkerView 类型最终生成的实体视图(模板文件)的路径以及其他配置。需要给 FreeMarkerViewResolver 设置一个 FreeMarkerConfig 的 bean 对象来定义 FreeMarker 的配置信息,代码如下。

1             <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
2                 <property name="templateLoaderPath" value="/WEB-INF/ftl" />
3             </bean>


        定义了 templateLoaderPath 属性后,Spring 可以通过该属性找到 FreeMarker 模板文件的具体位置。当有模板位于不同的路径时,可以配置 templateLoaderPath 属性,来指定多个资源路径。

        然后定义一个 Controller,让其返回 ModelAndView,同时定义一些返回参数和视图信息。

1             public class TestController implements Controller {
2                 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
3                     ModelAndView mv = new ModelAndView();
4                     mv.addObject("username", "Tester");
5                     mv.setViewName("freemarker");
6                     return mv;
7                 }
8             }


        当 FreeMarkerViewResolver 解析逻辑视图信息时,会生成一个 URL 为“前缀+视图名+后缀”(这里即“fm_freemarker.ftl”)的 FreeMarkerView 对象,然后通过 FreeMarkerConfigurer 的配置找到 templateLoaderPath 对应文本文件的路径,在该路径下找到该文本文件,从而 FreeMarkerView 就可以利用该模板文件进行视图的渲染,并将 model 数据封装到即将要显示的页面上,最终展示给用户。

        在 /WEB-INF/ftl 文件夹下创建 fm_freemarker.ftl,代码如下。

1             <html>
2             <head>
3             <title>FreeMarker</title>
4             </head>
5             <body>
6                 <b>Welcome!</b>
7                 <i>${username }</i>
8             </body>
9             </html>

 

3. @Controller 和 @RequestMapping 注解

    上文中使用了 org.springframework.web.servlet.mvc.Controller,这个 Controller 接口仅仅定义了一个方法 handleRequest() 用于负责处理客户请求,并返回对应的模型和视图,即一个处理方法(action)对应于一个 Controller。

    SpringMVC 提供一个多处理方法(action)控制器  org.springframework.web.servlet.mvc.multiaction.MultiActionController,在 Spring 4.3.9.RELEASE 版本下,MultiActionController 处于 Deprecated 状态,所以不建议使用这个控制器。

    SpringMVC 提供的另外一种多处理方法(action)的解决方案是 @Controller 和 @RequestMapping 注解。

    1) @Controller 注解

        @Controller 注解用于声明某类的实例是一个控制器。例如,在 com.example.controller 包中创建控制器类 IndexController,示例代码如下。

1             package com.example.controller;
2             
3             import org.springframework.stereotype.Controller;
4 
5             @Controller
6             public class IndexController {
7 
8             }


        Spring MVC 使用扫描机制,需要在配置文件中使用 <context:component-scan/> 元素指定要扫描的包路径。

        在配置文件 springmvc-beans.xml 中添加以下代码:

            <!-- 扫描 com.example 包 -->
            <context:component-scan base-package="com.example" />

    2) @RequestMapping 注解

        一个控制器内有多个处理请求的方法,每个方法负责不同的请求操作,而 @RequestMapping 就负责将请求映射到对应的控制器方法上。

        在基于注解的控制器类中可以为每个请求编写对应的处理方法。使用 @RequestMapping 注解将请求与处理方法一 一对应即可。

        @RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。

        @RequestMapping 注解常用属性如下。

属性 描述
value 是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称,支持通配符匹配,如 "test/*"。
path 和 value 属性都是用来作为映射使用,支持通配符匹配,如 "test/*"。
name 相当于方法的注释,使方法更易理解。
method 用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。method = RequestMethod.GET 表示该方法只支持 GET 请求。也可指定多个 HTTP 请求,可以同时支持 GET 和 POST 请求。
params 用于指定请求中规定的参数。
header 表示请求中必须包含某些指定的 header 值。
consumers 用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。
produces 用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。也可以指定返回值的编码。如:produces = "application/json,charset=utf-8"

 

    3) 通过请求 URL 进行映射

        (1) 方法级别注解

            方法级别注解的示例代码如下。

 1             package com.example.controller;
 2             
 3             import org.springframework.stereotype.Controller;
 4             import org.springframework.web.bind.annotation.RequestMapping;
 5 
 6             @Controller
 7             public class IndexController {
 8                 
 9                 @RequestMapping(value = "/index/register")
10                 public String register() {
11                     return "register";
12                 }
13             }


            上述示例中有两个 RequestMapping 注解语句,它们都作用在处理方法上。在整个 Web 项目中,@RequestMapping 映射的请求 URL 必须保证全局唯一。

            用户可以使用如下 URL 访问 register 方法(请求处理方法),在访问 register 方法之前需要事先在 /WEB-INF/jsp/ 目录下创建 register.jsp。

              http://localhost:9090/index/register

            注:本系列文章的示例代码,所用端口都是 9090

        (2) 类级别注解

            类级别注解的示例代码如下。

 1             package com.example.controller;
 2 
 3             import org.springframework.stereotype.Controller;
 4             import org.springframework.web.bind.annotation.RequestMapping;
 5             
 6             @Controller
 7             @RequestMapping("/index")
 8             public class IndexController {
 9 
10                 @RequestMapping("/register")
11                 public String register() {
12                     return "register";
13                 }
14 
15             }


            在类级别注解的情况下,控制器类中的所有方法都将映射为类级别的请求。用户可以使用如下 URL 访问 register方法。

              http://localhost:9090/index/register

            为了方便维护程序,建议开发者采用类级别注解,将相关处理放在同一个控制器类中。

    4) 通过请求参数(params)、请求方法(method)进行映射

        @RequestMapping 除了可以使用请求 URL 映射请求之外,还可以使用请求参数、请求方法来映射请求,通过多个条件可以让请求映射更加精确。

 1             package com.example.controller;
 2 
 3             import org.springframework.stereotype.Controller;
 4             import org.springframework.web.bind.annotation.RequestMapping;
 5             import org.springframework.web.bind.annotation.RequestParam;
 6             import org.springframework.web.bind.annotation.RequestMethod;
 7 
 8             @Controller
 9             public class IndexController {
10 
11                 @RequestMapping(value = "/index/success", method=RequestMethod.GET, params="username")
12                 public String success(@RequestParam String username) {
13                     return "success";
14                 }
15 
16             }


        上述代码中,@RequestMapping 的 value 表示请求的 URL;method 表示请求方法,此处设置为 GET 请求,若是 POST 请求,则无法进入 success 这个处理方法中。params 表示请求参数,此处参数名为 username。

    5) 请求处理方法

        在控制类中每个请求处理方法可以有多个不同类型的参数,以及一个多种类型的返回结果。

        (1) 请求处理方法中常出现的参数类型

            常用的参数类型有 Servlet 对象类型(比如 HttpSession、HttpServletRequest) 、输入输出流、表单实体类、注解类型、与 Spring 框架相关的类型等。

            其中特别重要的类型是 org.springframework.ui.Model 类型,该类型是一个包含 Map 的 Spring MVC类型。在每次调用请求处理方法时 Spring MVC 都将创建 org.springframework.ui.Model 对象。
            
            示例代码如下:

 1                 package com.example.controller;
 2 
 3                 import org.springframework.stereotype.Controller;
 4                 import org.springframework.web.bind.annotation.RequestMapping;
 5                 import org.springframework.ui.Model;
 6                 import javax.servlet.http.HttpSession;
 7                 import javax.servlet.http.HttpServletRequest;
 8 
 9                 @Controller
10                 @RequestMapping("/index")
11                 public class IndexController {
12 
13                     @RequestMapping("/test1")
14                     public String test1(HttpSession session, HttpServletRequest request) {
15                         session.setAttribute("key1", "session's value");
16                         request.setAttribute("key2", "request's value");
17                         return "test1";
18                     }
19 
20                     @RequestMapping("/test2")
21                     public String test2(Model model) {
22                         model.addAttribute("message", "It is OK");
23                         return "test2";
24                     }
25 
26                 }


        (2) 请求处理方法常见的返回类型

            a) ModelAndView
            b) Model
            c) 包含模型属性的 Map
            d) View
            e) 代表逻辑视图名的 String
            f) void
            g) 其它任意 Java 类型

            最常见的返回类型就是代表逻辑视图名称的 String 类型。

示例

    在 “Spring基础知识(11)- Spring MVC (一)” 里创建的 SpringmvcBasic 项目基础上,修改如下。

    1)修改配置文件 springmvc-beans.xml

 1         <?xml version="1.0" encoding="UTF-8"?>
 2         <beans xmlns="http://www.springframework.org/schema/beans"
 3             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4             xmlns:context="http://www.springframework.org/schema/context"
 5             xmlns:mvc="http://www.springframework.org/schema/mvc"
 6             xsi:schemaLocation="http://www.springframework.org/schema/beans
 7                                 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
 8                                 http://www.springframework.org/schema/context
 9                                 http://www.springframework.org/schema/context/spring-context-4.0.xsd
10                                 http://www.springframework.org/schema/mvc
11                                 http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
12 
13             <!-- 扫描 com.example 包 -->
14             <context:component-scan base-package="com.example" />
15 
16             <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
17                 <property name="prefix" value="/WEB-INF/jsp/" />
18                 <property name="suffix" value=".jsp" />
19             </bean>
20             
21             <!-- TestController 类映射到 "/demo" -->
22             <bean name="/demo" class="com.example.controller.TestController"/>
23 
24         </beans>


    2) 创建 View

        (1) 创建 src/main/webapp/WEB-INF/jsp/register.jsp 文件

 1             <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
 2             <html>
 3             <head>
 4                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                 <title>Register</title>
 6             </head>
 7             <body>
 8                 <h3>Register Page</h3>
 9             </body>
10             </html>


        (2) 创建 src/main/webapp/WEB-INF/jsp/success.jsp 文件

 1             <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
 2             <html>
 3             <head>
 4                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                 <title>Success</title>
 6             </head>
 7             <body>
 8                 <h3>Success Page</h3>
 9                 <p>&nbsp;</p>
10                 <p>Message: ${message}</p>
11             </body>
12             </html>


        (3) 创建 src/main/webapp/WEB-INF/jsp/test.jsp 文件

 1             <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
 2             <html>
 3             <head>
 4                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 5                 <title>Test</title>
 6             </head>
 7             <body>
 8                 <h3>Test Page</h3>
 9                 <p>&nbsp;</p>
10                 <p>key1: ${key1}</p>
11                 <p>key2: ${key2}</p>
12             </body>
13             </html>


    3) 创建 Controller

        创建 src/main/java/com/example/controller/IndexController.java 文件

 1             package com.example.controller;
 2 
 3             import javax.servlet.http.HttpSession;
 4             import javax.servlet.http.HttpServletRequest;
 5 
 6             import org.springframework.ui.Model;
 7             import org.springframework.stereotype.Controller;
 8             import org.springframework.web.bind.annotation.RequestMapping;
 9             import org.springframework.web.bind.annotation.RequestParam;
10             import org.springframework.web.bind.annotation.RequestMethod;
11 
12             @Controller
13             @RequestMapping("/index")
14             public class IndexController {
15 
16                 @RequestMapping("/register")
17                 public String register() {
18                     return "register";
19                 }
20 
21                 // URL和GET参数都要匹配,/index/success?username=xxx
22                 @RequestMapping(value = "/success", method=RequestMethod.GET, params="username")
23                 public String success(@RequestParam String username, Model model) {
24                     model.addAttribute("message", "username is " + username);
25                     return "success";
26                 }
27 
28                 @RequestMapping("/test")
29                 public String test(HttpSession session, HttpServletRequest request) {
30                     session.setAttribute("key1", "session's value");
31                     request.setAttribute("key2", "request's value");
32                     return "test";
33                 }
34 
35             }

 

    访问 http://localhost:9090/index/register

        Register Page

    访问 http://localhost:9090/index/success?username=Tester

        Success Page
 
        Message: username is Tester

    访问 http://localhost:9090/index/test

        Test Page
 
        key1: session's value
        key2: request's value

 

posted @ 2022-03-26 13:33  垄山小站  阅读(257)  评论(0编辑  收藏  举报