11

一篇文章带你掌握主流服务层框架——SpringMVC - 秋落雨微凉

 1 year ago
source link: https://www.cnblogs.com/qiuluoyuweiliang/p/16757184.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一篇文章带你掌握主流服务层框架——SpringMVC

在之前的文章中我们已经学习了Spring的基本内容,SpringMVC隶属于Spring的一部分内容

但由于SpringMVC完全针对于服务层使用,所以我们在介绍时常常把SpringMVC单独当作一个大章节来学习

温馨提醒:在学习SpringMVC前请确保已学习Spring内容

SpringMVC简介

首先我们先来简单了解一下SpringMVC:

  • SpringMVC是一种基于Java实现MVC模型的轻量级Web框架
  • SpringMVC致力于服务层,同Servlet一样应用于服务层,用于服务层开发
  • SpringMVC隶属于Spring,同样具有简化代码,使用简单,开发便捷,灵活性强的优点

SpringMVC入门案例

在未学习SpringMVC之前,我们的服务端开发通常采用Servlet:

package com.itheima.web.servlet.old;

import com.alibaba.fastjson.JSON;
import com.itheima.pojo.Brand;
import com.itheima.service.BrandService;
import com.itheima.service.impl.BrandServiceImpl;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;

//@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {

    private BrandService brandService = new BrandServiceImpl();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //1. 接收品牌数据
        BufferedReader br = request.getReader();
        String params = br.readLine();//json字符串

        //转为Brand对象
        Brand brand = JSON.parseObject(params, Brand.class);

        //2. 调用service添加
        brandService.add(brand);

        //3. 响应成功的标识
        response.getWriter().write("success");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

我们可以注意到其过程非常繁琐,因为我们需要获取参数并进行类型转换,包括添加至Service等过程

但是SpringMVC秉承着简化代码的原则,将大部分内容转化为Java代码进行封装,大大减少了繁琐的过程

接下来我们来介绍SpringMVC版:

  1. 导入jar包
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.itheima</groupId>
  <artifactId>springmvc_01_quickstart</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
<!--    servlet坐标-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
<!--    SpringMVC坐标-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
  </dependencies>
<!--Tomcat配置-->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>80</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
  </build>


</project>
  1. 创建SpringMVC控制类(等同于Servlet类)
package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

//定义表现层控制器bean
@Controller
public class UserController {

    //设置映射路径为/save,即外部访问路径
    @RequestMapping("/save")
    //设置当前操作返回结果为指定json数据(本质上是一个字符串信息)
    @ResponseBody
    public String save(){
        System.out.println("user save ...");
        return "{'info':'springmvc'}";
    }

    //设置映射路径为/delete,即外部访问路径
    @RequestMapping("/delete")
    @ResponseBody
    public String delete(){
        System.out.println("user save ...");
        return "{'info':'springmvc'}";
    }
}
  1. 初始化SpringMVC环境(同Spring一样创建Config配置Java类)
package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//springmvc配置类,本质上还是一个spring配置类
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
  1. 初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求
/*
我们服务层的实际操作都是放置于Servlet容器中
我们配置的SpringMVC和Spring环境都是用于服务层,所以我们需要把相关Config加载仅Servlet容器中
*/

package com.itheima.config;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;

// web容器配置类
// AbstractDispatcherServletInitializer是SpringMVC为我们设置好的类,继承并实现相关方法即可
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    //加载springmvc配置类,产生springmvc容器(本质还是spring容器)
    protected WebApplicationContext createServletApplicationContext() {
        //初始化WebApplicationContext对象
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        //加载指定配置类
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }

    //设置由springmvc控制器处理的请求映射路径
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //加载spring配置类
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}

我们对上述新的内容进行解析:

  1. @Controller

    • 名称:@Controller

    • 类型:类注解

    • 位置:SpringMVC控制类定义上方

    • 作用:设定SpringMVC的核心控制器Bean

  2. @RequestMapping

    • 名称:@RequestMapping

    • 类型:方法注解

    • 位置:SpringMVC控制器方法定义上方

    • 作用:设置当前控制器方法请求访问路径

    • 相关属性:value(请求访问路径)

  3. @ResponseBody

    • 名称:@ResponseBody

    • 类型:方法注释

    • 位置:SpringMVC控制器方法定义上方

    • 作用:设置当前控制器方法响应内容为当前返回值,无需解析

  4. AbstractDispatcherServletInitializer类

    • AbstractDispatcherServletInitializer是SpringMVC提供的快速初始化Web3.0容器的抽象类
    • AbstractDispatcherServletInitializer提供三个接口方法供用户实现
    • createServletApplicationContext方法用于创建Servlet容器时,加载SpringMVC对应的Bean并放入
    • AnnotationConfigWebApplicationContext的作用范围对应整个Web容器范围,必须使用WebApplicationcontext类型

最后我们总结一下上述操作的出现频率:

  • 一次性工作
    • 创建工程,设置服务器,加载工程
    • 创建Web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
    • SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器Bean)
  • 常态工作
    • 定义处理请求的控制类
    • 定义处理请求的操作方法,并设置映射路径(@RequestMapper)与返回Json数据(@ResponseBody)

SpringMVC工作流程

在分析SpringMVC工作流程前,我们需要知道服务层是由下面的框架组成的:

2886527-20221006104703535-906970175.png

启动服务器初始化过程:

  1. 服务器启动,执行ServletContainersInitConfig类,初始化Web容器
  2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象
  3. 加载SpringMvcConfig
  4. 执行@ComponentScan加载对应的bean
  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法
  6. 执行getServletMappings方法,定义所有的请求都通过SpringMVC

单次请求过程:

  1. 发送请求localhost/save
  2. Web容器发现所有请求都经过SpirngMVC,将请求交给SpringMVC处理
  3. 解析请求路径/save
  4. 由/save匹配执行对应的方法save()
  5. 执行save()
  6. 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方

SpringMVC加载控制

在学习SpringMVC之后,我们的Bean的范围逐渐变大:

  • SpringMVC相关bean(表现层bean)
  • Spring相关bean(业务层Service,功能DataSource等)

但是我们在使用时,需要区分相关bean的导入路径:

  • SpringMVC加载的bean对应的包均在com.itheima.controller包内
  • Spring加载的bean却包含有多个文件夹

因而我们给出两种方法来解决Spring的扫描问题:

  1. Spring加载的bean设定范围为com.itheima,并排除掉controller包内的bean
package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@Configuration
/*
@ComponentScan注解设置扫描范围
@ComponentScan中包含有value,excludeFilters属性
value:用于控制扫描范围
excludeFilters:用于控制排除范围,需要采用@ComponentScan.Filter过滤器
type:设置排除规则,当前使用按照bean定义时的注解类型进行排除
classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
*/
@ComponentScan(value="com.itheima",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = Controller.class
    )
)
public class SpringConfig {
}

/*
这里做一个小补充内容:
@ComponentScan中除了excludeFilters,还包括有includeFilters
includeFilters:加载指定的bean,需要指定类型(type)和具体项(classes)
*/
  1. Spring加载的bean设定范围为精准范围,例如service包,dao包等
package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

@Configuration
@ComponentScan({"com.itheima.service","com.itheima.dao"})
public class SpringConfig {
}

Servlet容器简化写法

我们的Servlet容器中可以定义Spring和SpringMVC的配置文件

package com.itheima.config;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    
    // 配置SpringMVC配置文件
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    
    // 配置Spring配置文件
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
    
    // 配置拦截路径
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}

我们可以注意到:

Spring和SpringMVC导入方法中均采用AnnotationConfigWebApplicationContext来创建对象

两者之间的区别仅仅是class包的不同

Spring给了我们一种新的继承类用于简化开发:

package com.itheima.config;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
//web配置类简化开发,仅设置配置类类名即可
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

AbstractAnnotationConfigDispatcherServletInitializer是createServletApplicationContext的继承类

我们同样继承它的三个方法,但这次我们只需要在里面标明相关类和路径即可

常用工具推荐Postman

在我们的SpringMVC中岔开一个话题:

  • 关于我们的网页调试的复杂性

我们在一个网页开发中,会不断的调试网页,通过各种路径反复查询或者采用不同的访问方式(GET/POST)

如果我们采用正常的网页进行测试,无疑会出现非常麻烦的步骤

所以我们推荐采用Postman软件,下面我们将会简单做一下介绍

Postman链接

首先为大家附上链接:

Postman操作讲解

在了解操作前,我们需要明白Postman的作用:

  • 用于分类存储网页请求
  • 用于发送请求进行测试

关于安装注册的过程我们不再赘述

Postman页面展示

我们先来查看Postman的主页:

2886527-20221006104727702-667867710.png

首先我们可以看到左上角的Workspaces,这个是最大的分类空间

我们可以看到左上角SpringMVC,这是我所创建的WorkSpaces,关于我在SpringMVC所做的网页测试部分将都在这里进行

2886527-20221006104733834-763921487.png

除此之外,我们可以看到右侧的DEMO1,以及内部的测试用例文件夹,以及项目save

以上就是我们的Postman的基本页面

Postman具体使用

我们的Postman的具体使用流程如下:

  1. 创建新的Workspaces
2886527-20221006104744467-263434821.png
  1. 选定主界面,创建对应文件夹
2886527-20221006104750381-907733485.png
  1. 创建项目(点击中间区域的加号)
2886527-20221006104755232-413789271.png
  1. 书写项目内容(GET可以更换其他类型,后面书写URL,下方key,value书写传递数据)
2886527-20221006104800016-1000261286.png
  1. 下方的数据传递可以更换类型,例如更换为body体的raw来书写JSON格式
2886527-20221006104804455-1218562104.png
  1. 书写后保存到相应列表并标注名称
2886527-20221006104808801-1922887898.png

到这里,我们Postman的基本使用基本就结束了,到后面我们会对具体内容做具体补充~

SpringMVC设置请求与响应

SpringMVC和Servlet同属于服务层的工具,那么必不可少的就是请求与响应的反馈问题

接下来我们将一一介绍请求与响应的相关知识

请求映射路径设置

首先我们先来想一想我们之前的路径设置是否有那么一点点缺陷?

// Book的服务层

package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BookController {
    //请求路径映射
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("book save ...");
        return "{'module':'book save'}";
    }
}
// User的服务层

package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {
    //请求路径映射
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("user save ...");
        return "{'module':'user save'}";
    }
    //请求路径映射
    @RequestMapping("/delete")
    @ResponseBody
    public String delete(){
        System.out.println("user delete ...");
        return "{'module':'user delete'}";
    }

}

我们可以注意到我们的单个项目中不可能只包括有一个服务层

但我们的请求映射路径却只是简单设计为相同的名称,就会导致我们访问该页面时,系统无法匹配

所以我们需要给他们采用不同的映射路径,我们常有的操作是直接在前面加上一层该类的路径名:

package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BookController {
    //请求路径映射
    @RequestMapping("/book/save")
    @ResponseBody
    public String save(){
        System.out.println("book save ...");
        return "{'module':'book save'}";
    }
}

但当项目逐渐增多,我们多次书写路径名就有可能导致错误,所以我们采用类注解@RequestMapping来解决:

package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
//类上方配置的请求映射与方法上面配置的请求映射连接在一起,形成完整的请求映射路径
@RequestMapping("/user")
public class UserController {
    //请求路径映射
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("user save ...");
        return "{'module':'user save'}";
    }
    //请求路径映射
    @RequestMapping("/delete")
    @ResponseBody
    public String delete(){
        System.out.println("user delete ...");
        return "{'module':'user delete'}";
    }

}

注意:@RequestMapping不仅仅可以用于方法表示映射,也可以用于整个Bean类中表示映射前缀

参数传递问题

关于参数传递我们从三个方面来讲解:

  • 传递参数类型
  • 特殊参数类型

按传递方式

我们的传递方式通常采用GET或者POST方式

但在前面的学习中我们可以知道我们的传递方式是有不同的,我们在Postman的书写形式也是不同的

例如我们先给出一个简单的参数传递函数

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //普通参数:请求参数与形参名称对应即可完成参数传递
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name ,int age){
        System.out.println("普通参数传递 name ==> "+name);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'common param'}";
    }

}

我们的GET方式直接在网页后用?和&来书写传递参数:

2886527-20221006104831550-1415129772.png

我们的POST方式只能在下方的body中书写参数:

2886527-20221006104835886-1147194824.png

然后我们需要注意到的是这里的中文同样会出现乱码行为

这次我们选择在ServletContainersInitConfig中处理数据:

// 下述代码基本属于我们创建项目的固定代码

package com.itheima.config;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.Filter;

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //乱码处理
    @Override
    protected Filter[] getServletFilters() {
        // CharacterEncodingFilter 属于处理中文编码的过滤器,我们直接创建即可(一次性操作)
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}

按参数方式

我们按参数来分类主要分为五种:

  • POJO类
  • 嵌套式POJO类

我们下面来一一介绍

普通参数:请求参数和形参变量名相同时,自动匹配

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //普通参数:请求参数与形参名称对应即可完成参数传递
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name ,int age){
        System.out.println("普通参数传递 name ==> "+name);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'common param'}";
    }

}

Postman操作:

2886527-20221006104845490-681858293.png

这里需要注意:当请求参数名与形参名不同时,使用@RequestParam注解关联请求参数名称与形参名称之间的关系

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //普通参数:请求参数名与形参名不同时,使用@RequestParam注解关联请求参数名称与形参名称之间的关系
    @RequestMapping("/commonParamDifferentName")
    @ResponseBody
    public String commonParamDifferentName(@RequestParam("name") String userName , int age){
        System.out.println("普通参数传递 userName ==> "+userName);
        System.out.println("普通参数传递 age ==> "+age);
        return "{'module':'common param different name'}";
    }

}

Postman操作:

2886527-20221006104912425-1992088891.png

@RequestParam:绑定请求参数与处理器方法形参间的关系

包含有两个参数

required:是否为必传参数

defaultValue:参数默认值

POJO参数

POJO参数:请求参数名与形参对象属性名相同,定义POJO类型形参即可接收参数

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
    @RequestMapping("/pojoParam")
    @ResponseBody
    public String pojoParam(User user){
        System.out.println("pojo参数传递 user ==> "+user);
        return "{'module':'pojo param'}";
    }

}

Postman操作:

2886527-20221006104919780-1343891561.png
嵌套POJO参数

嵌套POJO参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //嵌套POJO参数:嵌套属性按照层次结构设定名称即可完成参数传递
    @RequestMapping("/pojoContainPojoParam")
    @ResponseBody
    public String pojoContainPojoParam(User user){
        System.out.println("pojo嵌套pojo参数传递 user ==> "+user);
        return "{'module':'pojo contain pojo param'}";
    }

}

Postman操作:

2886527-20221006104926094-1243517932.png

数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型形参即可接收参数

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
    @RequestMapping("/arrayParam")
    @ResponseBody
    public String arrayParam(String[] likes){
        System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
        return "{'module':'array param'}";
    }

}

Postman操作:

2886527-20221006104931704-331567937.png
集合保存普通参数

集合保存普通参数:请求参数与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
    @RequestMapping("/listParam")
    @ResponseBody
    public String listParam(@RequestParam List<String> likes){
        System.out.println("集合参数传递 likes ==> "+ likes);
        return "{'module':'list param'}";
    }

}

Postman参数:

2886527-20221006104937412-1584330890.png

按特殊参数方式

我们的特殊参数主要介绍两种:

  • JSON类型

我们下面一一介绍

JSON类型

JSON类型是我们Web开发中最常用的类型,所以这一部分算是一个小重点

我们将一一讲解JSON类型传递的步骤:

  1. 导入JSON坐标
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.itheima</groupId>
  <artifactId>springmvc_04_request_param</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
      <!--JSON坐标--->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <port>80</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

  1. 在SpringMVC配置类中添加JSON类型转换注解
package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import javax.servlet.Filter;
import javax.servlet.annotation.WebFilter;

@Configuration
@ComponentScan("com.itheima.controller")
//开启json数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
  1. 在Controller中书写相关Web代码(注意:需要使用@RequestBody表示将请求体数据传递给请求参数)
package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //集合参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
    @RequestMapping("/listParamForJson")
    @ResponseBody
    public String listParamForJson(@RequestBody List<String> likes){
        System.out.println("list common(json)参数传递 list ==> "+likes);
        return "{'module':'list common for json param'}";
    }

    //POJO参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中,要求属性名称一一对应
    @RequestMapping("/pojoParamForJson")
    @ResponseBody
    public String pojoParamForJson(@RequestBody User user){
        System.out.println("pojo(json)参数传递 user ==> "+user);
        return "{'module':'pojo for json param'}";
    }

    //集合参数:json格式
    //1.开启json数据格式的自动转换,在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的保存实体类对象的集合对象中,要求属性名称一一对应
    @RequestMapping("/listPojoParamForJson")
    @ResponseBody
    public String listPojoParamForJson(@RequestBody List<User> list){
        System.out.println("list pojo(json)参数传递 list ==> "+list);
        return "{'module':'list pojo for json param'}";
    }
    
}

Postman操作:

2886527-20221006104948436-2136218154.png
2886527-20221006104954585-2096772875.png
2886527-20221006105000398-649093211.png

在上面我们有两个注解需要特别注意一下:

  1. @EnableWebMvc
  • 名称:@EnableWebMvc
  • 类型:配置类注解
  • 位置:SpringMVC配置类定义上方
  • 作用:开启SpringMVC多项辅助功能
  1. @RequestBody
  • 名称:@RequestBody
  • 类型:形参注解
  • 位置:SpringMVC控制器方法形参定义前面
  • 作用:将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

@RequestBody和@RequestParam区别

  • 区别
    • @RequestParam用于接收url地址传参,表单传参[application/x-www-form-urlencoded]
    • @RequestBody用于接收JSON数据[application/json]
  • 应用
    • 后期开发中,发送json数据为主,@RequestBody应用较广
    • 如果发送非json格式数据,选用@RequestParam接收请求参数
日期型参数类型

我们的日期类型数据基于系统不同格式也不相同,大致有以下几种:

  • 2022-10-05
  • 2022/10/05
  • 10/05/2022

接收形参时,我们根据不同的日期格式设置不同的接收方式

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
//请求参数
@Controller
public class UserController {

    //日期参数
    //使用@DateTimeFormat注解设置日期类型数据格式,默认格式yyyy/MM/dd
    @RequestMapping("/dataParam")
    @ResponseBody
    public String dataParam(Date date,
                            @DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
                            @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){
        System.out.println("参数传递 date ==> "+date);
        System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
        System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
        return "{'module':'data param'}";
    }

}

/*
名称:@DateTimeFormat
类型:形参注解
位置:SpringMVC控制器方法前
作用:设定日期时间型数据格式
属性:pattern:日期时间格式字符串
*/

Postman操作:

2886527-20221006105011431-1747363676.png

这里我们简单介绍一下@DateTimeFormat的转换原理Converter接口:

public interface Converter<S,T>{
	@Nullable
    T convert(S var1)
}

Converter接口属于顶层接口,由它为起源创建了许多相关的接口与类用于各种转化:

  • 请求参数年龄数据(String->Integer)
  • 日期格式转发(String->Date)

@EnableWebMvc功能之一:根据类型匹配对应的类型转换器

在了解请求的相关知识之后,我们回到Controller代码中学习一下响应

在正常情况下,我们的响应给出的是当前项目的文档,相当于页面的跳转效应:

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Controller
public class UserController {

    //响应页面/跳转页面
    //返回值为String类型,设置返回值为页面名称,即可实现页面跳转
    @RequestMapping("/toJumpPage")
    public String toJumpPage(){
        System.out.println("跳转页面");
        return "page.jsp";
    }

}

如果我们希望得到一些信息响应,就需要采用注解解释:

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Controller
public class UserController {
    
    //响应文本数据
    //返回值为String类型,设置返回值为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解
    @RequestMapping("/toText")
    @ResponseBody
    public String toText(){
        System.out.println("返回纯文本数据");
        return "response text";
    }

    //响应POJO对象
    //返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
    @RequestMapping("/toJsonPOJO")
    @ResponseBody
    public User toJsonPOJO(){
        System.out.println("返回json对象数据");
        User user = new User();
        user.setName("itcast");
        user.setAge(15);
        return user;
    }

    //响应POJO集合对象
    //返回值为集合对象,设置返回值为集合类型,即可实现返回对应集合的json数组数据,需要依赖@ResponseBody注解和@EnableWebMvc注解
    @RequestMapping("/toJsonList")
    @ResponseBody
    public List<User> toJsonList(){
        System.out.println("返回json集合数据");
        User user1 = new User();
        user1.setName("传智播客");
        user1.setAge(15);

        User user2 = new User();
        user2.setName("黑马程序员");
        user2.setAge(12);

        List<User> userList = new ArrayList<User>();
        userList.add(user1);
        userList.add(user2);

        return userList;
    }
}

/*
名称:@ResponseBody
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器返回值作为响应体
*/

当我们使用Postman访问该链接时就会给出对应反馈,这里就不做演示了

REST风格

首先我们来简单介绍一下REST:

  • REST(Representational State Transfer),表现形式状态转换

我们给出正常风格和REST风格两种书写形式,我们可以明显看到REST的内容做出大规模的省略:

REST风格优点:

  • 隐藏资源的访问行为,无法通过地址得知对资源的操作

REST风格简介

我们来对REST风格做出简单解释:

  • REST风格是采用访问资源的行为动作来区别对资源进行了何种操作

我们给出五种常见行为动作:

我们通常将根据REST风格进行的访问称为RESTful

上述行为是约定方式,约定不是规范,是可以打破的,所以称为REST风格,而不是REST规范

描述模块的名称通常使用负数,也就是加s的格式描述,表示此类,而非单个资源

RESTful入门案例

从本质上而言,REST只是一种规范形式,我们对于REST的风格修改仅针对于Controller

我们下面将逐步进行RESTful的修改:

  1. 设置http请求动作
package com.itheima.controller;

import com.itheima.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class BookController {

    // RequestMapping中包含value和method两种属性
    // value:访问路径
    // method:访问方法
    @RequestMapping(value = "/users",method = Request.POST)
    @RequestBody
	public String save(@RequestBody User user){
		System.out.println("user save" + user);
        return "{'module':'user save'}"
    }
}

/*
名称:@RequestMapping
类型:方法注解
位置:SpringMVC控制器方法定义上方
作用:设置当前控制器方法请求访问路径
属性:value访问路径,method请求动作
*/
  1. 设置请求参数(路径变量)
package com.itheima.controller;

import com.itheima.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class BookController {

    // 首先针对我们所需参数给出@PathVariable注解,并在访问路径中采用{}占位表示所传数据
    // 简单来说就是,系统根据请求路径,得到所需数据,再带入到方法中
    @RequestMapping(value = "/users/{id}" ,method = RequestMethod.DELETE)
    @RequestBody
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }
}

/*
名称:@PathVariable
类型:形参注解
位置:SpringMVC控制器方法形参定义前面
作用:绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应
*/

下面我们给出所有情况案例:

package com.itheima.controller;

import com.itheima.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class UserController {

    //设置当前请求方法为POST,表示REST风格中的添加操作
    @RequestMapping(value = "/users",method = RequestMethod.POST)
    @ResponseBody
    public String save(){
        System.out.println("user save...");
        return "{'module':'user save'}";
    }

    //设置当前请求方法为DELETE,表示REST风格中的删除操作
    //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
    @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }

    //设置当前请求方法为PUT,表示REST风格中的修改操作
    @RequestMapping(value = "/users",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user){
        System.out.println("user update..."+user);
        return "{'module':'user update'}";
    }

    //设置当前请求方法为GET,表示REST风格中的查询操作
    //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
    @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }

    //设置当前请求方法为GET,表示REST风格中的查询操作
    @RequestMapping(value = "/users",method = RequestMethod.GET)
    @ResponseBody
    public String getAll(){
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }

}


/*
	下述为原有代码:

    @RequestMapping
    @ResponseBody
    public String delete(){
        System.out.println("user delete...");
        return "{'module':'user delete'}";
    }

    @RequestMapping
    @ResponseBody
    public String update(){
        System.out.println("user update...");
        return "{'module':'user update'}";
    }

    @RequestMapping
    @ResponseBody
    public String getById(){
        System.out.println("user getById...");
        return "{'module':'user getById'}";
    }

    @RequestMapping
    @ResponseBody
    public String getAll(){
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }
*/

我们在这里给出@RequestBody,@RequestParam,@PathVariable区别

  • @RequestParam用于接收url地址传参或表单传参
  • @RequestBody用于接收json数据
  • @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
  • 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
  • 如果发送非json格式数据,选用@RequestParam接受请求参数
  • 采用RESTful进行开发,当参数数量较少时,如1个,可以采用@PathVariable接收请求路径变量,常用来传递id值

REST快速开发

我们在上一小节中会发现有许多重复性的代码:

// 每次都填写value,method导致代码繁冗
// 包括每次填写ResponseBody使代码繁冗

@RequestMapping(value = "/users",method = RequestMethod.GET)
@ResponseBody

所以我们可以采用一些小技巧来简化代码:

  • 将前缀地址和相关注解放于类中:
package com.itheima.controller;

import com.itheima.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

//@Controller
//@ResponseBody
@RestController
@RequestMapping("/books")
public class BookController {

}
/*
正常情况下,我们的类本身具有@Controller,并且为了省略类中的@ResponseBody而直接标注在类头
但Spring提供了一种新的注解@RestController,相当于@Controller和@ResponseBody的结合,我们只需要书写这一个注解即可 

名称:@RestController
类型:类注解
位置:基于SpringMVC的RESTful开发控制器类定义上方
作用:设置当前控制器为RESTful风格,等同于@Controller与@ResponseBody两个注解组合功能
*/
  • 采用新的地址注解代替老注解:
package com.itheima.controller;

import com.itheima.domain.Book;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/books")
public class BookController {

	//@RequestMapping( method = RequestMethod.POST)
    //使用@PostMapping简化Post请求方法对应的映射配置
    @PostMapping        
    public String save(@RequestBody Book book){
        System.out.println("book save..." + book);
        return "{'module':'book save'}";
    }

	//@RequestMapping(value = "/{id}" ,method = RequestMethod.DELETE)
    //使用@DeleteMapping简化DELETE请求方法对应的映射配置
    @DeleteMapping("/{id}")     
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }

	//@RequestMapping(method = RequestMethod.PUT)
    //使用@PutMapping简化Put请求方法对应的映射配置
    @PutMapping         
    public String update(@RequestBody Book book){
        System.out.println("book update..."+book);
        return "{'module':'book update'}";
    }

	//@RequestMapping(value = "/{id}" ,method = RequestMethod.GET)
    //使用@GetMapping简化GET请求方法对应的映射配置
    @GetMapping("/{id}")    
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..."+id);
        return "{'module':'book getById'}";
    }

	//@RequestMapping(method = RequestMethod.GET)
    //使用@GetMapping简化GET请求方法对应的映射配置
    @GetMapping             
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
}

/*
名称:@GetMapping @PostMapping @PutMapping @DeleteMapping
类型:方法注解
位置:基于SpringMVC的RESTful开发控制器方法定义上方
作用:设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping对应GET请求
参数:value请求访问路径
*/

好的,关于SpringMVC的内容就介绍到这里,希望能为你带来帮助!

该文章属于学习内容,具体参考B站黑马程序员李老师的SSM框架课程

这里附上链接:SpringMVC-01-SpringMVC简介_哔哩哔哩_bilibili


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK