SpringMVC2️⃣响应&请求、RESTful

1、响应资源

响应内容有 2 种类型

  1. 页面跳转:通过字符串、通过 ModelAndView 对象
  2. 响应数据:普通字符串、JSON 字符串(对象、集合)

搭建环境

  1. 导入依赖,整合 Tomcat。

  2. 添加 web 框架支持,项目结构添加 lib 目录。

  3. web.xml 中配置 DispatcherServlet

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
  4. 创建 spring-mvc.xml:开启注解扫描、自定义视图前后缀。

    <context:component-scan base-package="indi.jaywee.controller"/>
    
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    

1.1、页面跳转

页面跳转的 2 种方式:字符串、ModelAndView 对象

:两种方式都会与视图解析器的前后缀拼接后生成虚拟路径。

  • 自定义前缀 /jsp/,自定义后缀 .jsp

  • 视图名 hello 经过拼接,虚拟路径为 /jsp/hello.jsp

    image-20220329235011694

1.1.1、字符串

@RequestMapping("/hello1")
public String helloJsp1() {
    return "hello";
}

1.1.2、ModelAndView 对象

  • 回顾 SpringMVC 流程:Handler 处理完请求后,返回的 ModelAndView。

  • 使用方式

    • 局部变量

    • 方法形参SpringMVC 会自动注入实参(推荐)

      @RequestMapping("/hello2")
      public ModelAndView helloJsp2() {
          ModelAndView mv = new ModelAndView();
          mv.setViewName("hello");
          return mv;
      }
      @RequestMapping("/hello3")
      public ModelAndView helloJsp3(ModelAndView mv) {
          mv.setViewName("hello");
          return mv;
      }
      

1.1.3、页面间转发数据

注:使用内部资源视图解析器的页面跳转方式是转发,而非重定向(属于外部跳转)

如何实现在页面转发时传递数据

  1. 传统 Java Web 思路:通过 request作用域

    @RequestMapping("/hello11")
    public String helloJsp11(HttpServletRequest req) {
        req.setAttribute("username", "Jaywee");
        return "hello";
    }
    
  2. Spring MVC:通过 ModelAndView 的 addObject()

    (还可使用 ModelModelMap,此处不作展示)

    @RequestMapping("/hello22")
    public ModelAndView helloJsp22(ModelAndView mv) {
        mv.addObject("username", "Jaywee");
        mv.setViewName("hello");
        return mv;
    }
    

1.2、响应数据(❗)

响应数据的 2 种类型

  • 普通字符串
  • JSON 字符串对象、集合

任何类型的参数都是以字符串的形式进行传输。

  • 基本类型:表面上是直接传数值,实际上内部自动进行转换。

  • 引用类型:发送端将对象转换为 JSON 字符串,接收端将 JSON 字符串解析成对象。

1.2.1、普通字符串

  • 传统 Java Web:通过 response 的 getWriter()

    @RequestMapping("/data1")
    public void data1(HttpServletResponse resp) throws IOException {
        resp.getWriter().print("hello");
    }
    
  • Spring MVC:使用 @ResponseBody 表示此方法返回字符串,而非视图。

    • 具体来说,此注解的作用是标识方法返回的内容作为响应报文的报文实体

    • 相比使用 resp.getWriter(),会设置默认编码为 ISO-8859-1。

    • 涉及 HTTP 知识:Java Web 简单介绍 HTTP计算机网络:HTTP协议

      @RequestMapping("/data2")
      @ResponseBody
      public String data2() {
          return "hello";
      }
      

1.2.2、JSON 字符串(❗)

JSON

发送端将对象转换为 JSON 字符串,接收端将 JSON 字符串解析成对象。

示例:User(name, age)

  1. 手动拼接:麻烦,易出错(可自定义工具类,但不如使用第三方工具)

    @RequestMapping("/json1")
    @ResponseBody
    public String jsonStr1() {
        User user = new User("Jaywee", "123456");
        return "{\"name\":\"" + user.getName() + "\",\"password\":" + user.getPassword() + "}";
    }
    
  2. 调用第三方工具的方法(如 FastJson、Jackson)

    @RequestMapping("/json2")
    @ResponseBody
    public String jsonStr2() {
        User user = new User("Jaywee", "123456");
        return JSON.toJSONString(user);
    }
    
  3. 配置 Jackson,方法返回值可直接声明为 POJO 类型。

    • 导入依赖

      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <version>2.13.1</version>
      </dependency>
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.13.1</version>
      </dependency>
      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>2.13.1</version>
      </dependency>
      
    • spring-mvc 配置:处理器适配器

      <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
          <property name="messageConverters">
              <list>
                  <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
              </list>
          </property>
      </bean>
      
    • 使用:返回值直接声明为 User 类型。

      @RequestMapping("/json3")
      @ResponseBody
      public User jsonStr3() {
          return new User("Jaywee", "123456");
      }
      
  4. SpringMVC 注解驱动(!)

    • 以上的配置太繁琐,因此 SpringMVC 提供了注解驱动。

    • 该配置自动载入 处理器映射器、处理器适配器(底层集成 Jackson

    • 建议为 POJO 添加 getter 和 setter,避免封装实体时出错。

    • 使用方式同上,方法返回值声明为 POJO 类型。

      <mvc:annotation-driven/>
      

1.2.3、@RestController

以上案例中,均使用 @Controller + @ResponseBody 使方法返回字符串。

@Controller
public class xxxController {
    @RequestMapping("/xxx")
    @ResponseBody
    public String hello(){
     return "";
    }
}

@RestController 相当于以上两个注解的结合。

  • 使用:在 Controller 上使用,将 Controller 注册到 Spring 容器中。

  • 说明:使用该注解的类,会忽略视图解析器配置,所有方法均会返回字符串,而非视图。

    @RestController
    public class xxxController {
        @RequestMapping("/xxx")
        public String hello(){
            return "";
        }
    }
    

2、请求:获取参数(❗)

请求的参数类型分为 3 类

  1. 简单类型

    • 基本类型、字符串类型
    • 简单类型数组
  2. POJO 类型

  3. 集合类型

说明

  1. 参数格式:默认情况下,客户端请求参数是以键值对格式提交( name=value&name=value
  2. 类型转换:SpringMVC 会将请求参数进行类型转换(如 int 转 String)。
  3. 注解@RequestParam@RequestBody

2.1、简单类型

请求参数的 name 属性Controller 的方法参数名一致。

两种实现方式

  1. 方法参数名设置成与 name 属性同名。
  2. 方法参数使用 @RequestParam 映射的 name 属性(此时参数名可任意)

2.1.1、基本类型、字符串类型

以字符串类型为例

  • 页面:注意表单项的 name 属性

    用户<input type="text" name="name"><br>
    密码<input type="password" name="password">
    <input type="submit">
    
  • Controller:设置方法参数名或使用 @RequestParam

    @RequestMapping("/login1")
    public void login1(String name, String password) {
        System.out.println(name + ": " + password);
    }
    @RequestMapping("/login11")
    public void login11(@RequestParam("name") String username, String password) {
        System.out.println(username + ": " + password);
    }
    

2.1.2、简单类型数组

请求参数的 name 属性Controller 的方法参数名一致,通过 [] 区分下标。

  • 页面:注意表单项的 name 属性

    用户1<input type="text" name="nameList"><br>
    用户2<input type="text" name="nameList"><br>
    用户3<input type="text" name="nameList"><br>
    
  • Controller:设置方法参数名或使用 @RequestParam

    @RequestMapping("/login2")
    public void login2(String[] nameList) {
        System.out.println(Arrays.asList(nameList));
    }
    
    @RequestMapping("/login22")
    public void login22(@RequestParam("nameList") String[] list) {
        System.out.println(Arrays.asList(list));
    }
    

2.2、POJO 类型(❗)

请求参数的 name 属性POJO 的成员变量名一致。

工作原理

  1. 优先使用无参构造方法,并通过 setter 注入。
  2. 若没有无参构造方法,使用有参构造方法。

示例

  • POJO:根据以上工作原理,需提供相应的构造方法和 setter(最好都添加)

    public class User {
        private String name;
        private String password;
        // 构造方法、getter/setter、toString()
    }
    
  • 页面:注意表单项的 name 属性,与 POJO 的成员变量名一致。

    用户<input type="text" name="name"><br>
    密码<input type="password" name="password">
    <input type="submit">
    
  • Controller:使用 POJO 作为方法参数。

    @RequestMapping("/login3")
    public void login2(User user) {
        System.out.println(user);
    }
    

2.3、集合类型

  • 数组:接收多个相同 name 属性简单类型参数时,通常使用数组。
  • 集合:接收多个 POJO 类型参数时,通常使用集合。

2.3.1、VO

VO(Value Object、View Object)

获取集合类型的请求参数时,需要将集合参数封装到一个 POJO 中,即 VO

  1. 将集合定义为 VO 的成员变量(而不是直接作为 Controller 方法参数)。
  2. 使用 VO 作为 Controller 方法参数
  3. 通过请求参数的 name 属性与 VO 成员变量名的映射关系接收参数。

2.3.2、示例

以接收多个 User 为例。

  • VO:封装 POJO 集合(根据 2.3 的工作原理,需提供构造方法和 setter)

    public class UserVO {
        private List<User> userList;
    	// 构造方法、setter、toString()
    }
    
  • 页面:注意表单项的 name 属性

    1. 格式集合[下标].属性

    2. 集合名称与 VO 的成员变量名一致

    3. 通过下标区分元素

      用户1<input type="text" name="userList[0].name"><br>
      密码1<input type="password" name="userList[0].password"><br>
      用户2<input type="text" name="userList[1].name"><br>
      密码2<input type="password" name="userList[1].password"><br>
      用户3<input type="text" name="userList[2].name"><br>
      密码3<input type="password" name="userList[2].password"><br>
      <input type="submit">
      
  • Controller:使用 VO 作为方法参数

    @RequestMapping("/login4")
    public void login4(UserVO userVO) {
        System.out.println(userVO);
    }
    

2.4、@RequestBody(❗)

通常情况下,前后端通过 JSON 格式交换数据

  • 若前端页面通过 AJAX 发送请求参数,且请求方式为 POST,请求参数为 JSON 格式。
  • Controller 可使用相应类型的对象(包括集合对象)作为方法参数,并在参数前使用 @RequestBody 注解。

2.4.1、使用

  • 页面

    • 导入 jquery 工具,通过 AJAX 发送 POST 请求,参数类型为 JSON 格式。

    • :<script> 必须是成对标签,自闭合可能无法生效。

      <script src="${pageContext.request.contextPath}/js/jquery.js"></script>
      <script>
          let userList = new Array();
          userList.push({name: "Jaywee1", password: "123456"})
          userList.push({name: "Jaywee2 ", password: "888888"})
      
          $.ajax({
              type: "POST",
              url: "/req_resp/login5",
              data: JSON.stringify(userList),
              contentType: "application/json;charset=utf-8"
          });
      </script>
      
  • Controller:使用集合对象作为方法参数,无需封装 VO

    @RequestMapping("/login5")
    public void login5(@RequestBody List<User> userList) {
        System.out.println(userList);
    }
    
  • spring-mvc.xml:配置静态资源过滤,避免 JS 文件被 DispatcherServlet 拦截

    <!-- mapping 表示访问资源的虚拟路径,location 表示真实路径-->
    <mvc:resources mapping="/js/**" location="/js/"/>
    

2.4.2、对比 @RequestParam

@RequestParam

用于标注 Controller 方法上的简单类型参数,有以下常用属性

  • value:请求参数名
  • required:请求中是否必须包含该参数
    • 默认 true:若请求中没有该参数则报错
    • false:可有可无,相当于 @Nullable
  • defaultValue:当没有指定请求参数时,使用该默认值

对比

尤其是前后端分离式开发中,需注意这两个注解的区别。

@RequestParam @RequestBody
接收参数格式 键值对格式(name=value) JSON 格式
请求方式 通常是 GET。
若使用 POST 也只能接收 URL 中的参数,无法接收请求体参数
只能是 POST,接收请求体中的参数
传参方式 URL 拼接属性
表单(enctype=multipart/urlencoder
AJAX(contentType: "application/json

2.5、常见问题

2.5.1、静态资源过滤

当请求中涉及静态资源时,必须在 spring-mvc.xml 中配置静态资源过滤,避免被 DispatcherServlet 拦截过滤。

原因DispatcherServleturl-pattern 配置的是 /,表示过滤所有资源

<!-- mapping 表示访问资源的虚拟路径,location 表示真实路径-->
<mvc:resources mapping="/js/**" location="/js/"/>

在实际开发中,涉及很多类型的静态资源(HTML、JS、CSS 等)

  • 若通过以上方式,需要配置所有类型的静态资源过滤。

    <mvc:resources mapping="/js/**" location="/js/"/>
    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/html/**" location="/html/"/>
    
  • SpringMVC 提供 <mvc:default-servlet-handler>,可以简化以上配置工作

    • DispatcherServlet 只能对处理器(Controller)进行处理,无法处理静态资源。

    • SpringMVC 会将静态资源交给默认 Servlet 处理器(即 Tomcat)进行处理。

      <mvc:default-servlet-handler/>
      

2.5.2、乱码问题

当使用 POST 方式提交参数时,服务器端接收的参数会出现乱码(原因见 Servlet 3.3-请求&响应

传统 Java Web 中解决乱码问题的方式

  1. 在 Serlvet 中分别设置 request 和 response 对象的字符编码格式

    request.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");
    
  2. Filter:在过滤器中设置字符编码格式(全局),有两种配置方式

    1. 注解@WebFilter

      @WebFilter("/*")
      public class EncodingFilter implements Filter {
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
              request.setCharacterEncoding("UTF-8");
              response.setContentType("text/html;charset=UTF-8");
              chain.doFilter(request, response);
          }
      
          // init()、destory()
      }
      
    2. web.xml<filter>

      <filter>
          <filter-name>encodingFilter</filter-name>
          <filter-class>indi.jaywee.filter.EncodingFilter</filter-class>
      </filter>
      <filter-mapping>
          <filter-name>encodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>
      

SpringMVC 解决乱码问题

基于 XML 配置,使用 Spring 提供的字符编码过滤器(同以上方案二)

<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2.5.3、类型转换器

  • SpringMVC 默认配置了常用的类型转换器,将客户端的请求参数进行类型转换(如 int 转 String)
  • 支持自定义类型转换器(如转换日期格式)

示例:将客户端的日期字符串转换为日期对象

  1. 实现 Converter 接口:泛型分别为源类型、目标类型。

    import org.springframework.core.convert.converter.Converter;
    
    public class DateConverter implements Converter<String, Date> {
        @Override
        public Date convert(String source) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            try {
                return sdf.parse(source);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
  2. 配置:注册转换器、引用转换器

    <bean id="myConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="indi.jaywee.utils.DateConverter"/>
            </set>
        </property>
    </bean>
    
    <mvc:annotation-driven conversion-service="myConversionService"/>
    
  3. Controller

    @RequestMapping("/date")
    public void date(Date date) {
        System.out.println(date);
    }
    
  4. 测试:成功将字符串格式进行转换为对象。

    image-20220331143639133

说明

  1. SpringMVC 默认的日期格式:yyyy/MM/dd
  2. 建议前后端以时间戳的格式传输日期参数,以提高通用性。

2.5.4、Controller 实参注入

以上案例中,可直接在 Controller 方法中声明方法形参,SpringMVC 在调用时会自动注入实参

常用对象

  • Servlet API 相关:
    • HttpServletRequest
    • HttpServletResponse
    • HttpSession
  • Spring MVC 相关
    • ModelAndView
    • Model

5.5.5、@RequestHeader

获取方式

  • 传统 Java Web:通过 request.getHeader(name)
  • Spring MVC:使用 @RequestHeader(name)

示例获取 Cookie

image-20220331150042514

通过以下方式,获取的是一个 Cookie 字符串,内容是上图展示的所有 Cookie 值。

@RequestMapping("/header1")
public void header1(HttpServletRequest req) {
    String cookies = req.getHeader("cookie");
    System.out.println(cookies);
}

@RequestMapping("/header2")
public void header2(@RequestHeader(value = "cookie", required = false) String cookies) {
    System.out.println(cookies);
}

如何直接获得某个 Cookie?

  • 传统 Java Web:通过 request.getCookies(),按名称遍历查找
  • Spring MVC:使用 @CookieValue(name)

示例获取名为 JSESSIONID 的 Cookie

显然,通过 @CookieValue 获取的方式,代码更加简洁。

@RequestMapping("/cookie1")
public void cookie1(HttpServletRequest req) {
    Cookie[] cookies = req.getCookies();
    for (Cookie cookie : cookies) {
        if ("JSESSIONID".equals(cookie.getName())) {
            System.out.println(cookie.getValue());
        }
    }
}
@RequestMapping("/cookie2")
public void cookie2(@CookieValue("JSESSIONID") String sessionId) {
    System.out.println(sessionId);
}

3、*请求转发和重定向

在前后端开发中,这部分内容不是重点,了解即可。

在 Spring MVC 中使用视图解析器,都属于请求转发

  • 原因:视图解析器是对服务器内部资源进行解析,而重定向属于外部资源跳转。
  • 若要实现重定向,参考 Java Web 3.4、请求转发 & 重定向

4、RESTful 风格

4.1、REST

Representational State Transfer

  • REST 定义了架构的约束条件和原则,满足这些约束条件和原则的就是 RESTful。
  • RESTful 是软件设计风格,而不是标准或协议。
  • 基于 RESTful 风格设计的软件更简洁,更有层次,更易实现缓存机制等。

4.2、请求方式

RESTful 风格通过 url 和 请求方式】表示一次请求

通常使用以下 4 种请求方式(具体见 3.1.1 HTTP 请求方法

  • 新建资源:POST
  • 删除资源:DELETE
  • 更新资源:PUT
  • 获取资源:GET

对比

  • 传统方式:通过不同的请求地址,代表不同的功能。

  • RESTful 风格相同的请求地址可实现不同的功能,通过请求方式区分。

    传统方式 RESTful 风格
    添加 /item/add /item
    删除 /item/delete?id=1 /item/1
    更新 /item/update /item
    查询 /item/query?id=1 /item/1

4.3、使用

4.3.1、Controller

  • 映射地址:使用 {} 占位符

  • 方法参数:使用 @PathVariable 绑定 URI 模板变量。

    @PostMapping("/add")
    public void addUser(User user) {
        System.out.println(user);
    }
    
    @DeleteMapping("/delete/{username}")
    public void deleteUser(@PathVariable String username) {
        System.out.println(username);
    }
    
    @PutMapping("/update")
    public void updateUser(User user) {
        System.out.println(user);
    }
    
    @GetMapping("/query/{username}")
    public void queryUser(@PathVariable int username) {
        System.out.println(username);
    }
    

4.3.1、@XxxMapping

RESTful 通过请求方式区分功能,因此 Controller 方法需指定请求方式。

2 种指定方式:

  1. @RequestMapping :method 属性

    • 格式

      @RequestMapping(value="",method = 请求方式)
      
    • 请求方式:Spring 框架定义的枚举类

      public enum RequestMethod {
          GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
      }
      
  2. @RequestMapping 的衍生注解

    • @GetMapping
    • @PostMapping
    • @PutMapping
    • @DeleteMapping
    • ...
posted @ 2021-08-13 14:28  Jaywee  阅读(145)  评论(0编辑  收藏  举报

👇