[Java]SpringMVC-Day 1

[Java]SpringMVC-Day1

学习使用工具

黑马2023新版Java视频教程 https://www.bilibili.com/video/BV1Fv4y1q7ZH?p=8&vd_source=03da0cdb826d78c565cd22a83928f4c2

Java程序员进阶之路 https://tobebetterjavaer.com/overview/java-can-do-what.html

黑马程序员SSM框架教程 https://www.bilibili.com/video/BV1Fi4y1S7ix?p=2&vd_source=03da0cdb826d78c565cd22a83928f4c2

零、Javaweb项目结构

javaweb项目结构

  • src文件夹

    这个文件夹用来存放后端项目的源代码。比如后端的 servlet,需要用到的 JavaBean 类,以及负责业务逻辑的 java 类和负责数据库操作的 java 类(这个文件夹与运行并不直接相关,需要将对应的 java 代码编译后放到 classes 文件夹下才能在项目中使用)。

  • webapp文件夹

    这个文件夹是项目的主要文件夹,将这个文件夹放入 Tomcat 的 webapps 文件夹中,就能在 Tomcat 启动后访问到对应的 web 项目。这个文件夹里面一般也会存放 jsp 文件,jsp 文件是 javaweb 中用于视图渲染的文件

  • WEB-INF文件夹

    里面存放着源代码编译后的 class 文件(存放在 classes 文件夹内)和 web.xml 文件

  • web.xml文件

    这个文件是项目部署文件,里面规定了 servlet 与 url 的一一映射,还有一些其他的配置文件。

一、SpringMVC概述

SpringMVC是隶属于Spring框架的一部分,主要是用来进行Web开发,是对Servlet进行了封装。SpringMVC是处于Web层的框架,其主要的作用就是用来接收前端发过来的请求和数据然后经过处理并将处理的结果响应给前端,所以如何处理请求和响应是SpringMVC中非常重要的一块内容。

  1. web程序三层架构

    浏览器发送一个请求给后端服务器,后端服务器现在是使用Servlet来接收请求和数据。如果所有的处理都交给Servlet来处理的话,所有的东西都耦合在一起,对后期的维护和扩展极为不利。

    因此将后端服务器Servlet拆分成三层,分别是webservicedao

    • web层主要由servlet来处理,负责页面请求和数据的收集以及响应结果给前端
    • service层主要负责业务逻辑的处理
    • dao层主要负责数据的增删改查操作

    但servlet处理请求和数据的时候,一个servlet只能处理一个请求。因此针对web层进行了优化,采用了MVC设计模式,将其设计为controllerviewModel

    • M 即Model(模型层),主要负责处理业务逻辑以及与数据库的交互。
    • V 即View(视图层),主要用于显示数据和提交数据。
    • C 即Controller(控制器),主要用于接受请求并控制请求转发。
    • controller负责请求和数据的接收,接收后将其转发给service进行业务处理。service根据需要会调用dao对数据进行增删改查。dao把数据处理完后将结果交给service,service再交给controller
    • controller根据需求组装成Model和View,Model和View组合起来生成页面转发给前端浏览器
    • 这样做的好处就是controller可以处理多个请求,并对请求进行分发,执行不同的业务操作。

    随着互联网的发展,上面的模式因为是同步调用,性能慢慢的跟不是需求,所以异步调用慢慢的走到了前台,是现在比较流行的一种处理方式。

    同步和异步通常用来形容一次方法的调用。
    
    同步方法的调用必须等到该方法返回后才能继续接下来的行为。
    
    异步方法更像一个消息传递,一旦调用就会立即返回,调用者可以继续接下来的操作,
    
    而异步方法通常会在另一个线程中执行,不会妨碍调用者的工作。
    

    因为是异步调用,所以后端不需要返回view视图,将其去除。前端如果通过异步调用的方式进行交互,后台就需要将返回的数据转换成json格式进行返回。

    • SpringMVC主要负责的就是
      • controller如何接收请求和数据
      • 如何将请求和数据转发给业务层
      • 如何将响应数据转换成json发回到前端

    SpringMVC是一种基于Java实现MVC模型的轻量级Web框架。优点是使用简单、开发便捷、灵活性强。

二、SpringMVC入门案例

  1. 创建web工程(Maven结构)
  2. 设置tomcat服务器,加载web工程(tomcat插件)
  3. 导入坐标(SpringMVC+Servlet)
  4. 定义处理请求的功能类(UserController)
  5. 设置请求映射(配置映射关系)
  6. 将SpringMVC设定加载到Tomcat容器中
  • servlet与tomcat简介

    在JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上。为了实现这一目的,JavaEE提供了Servlet API,我们使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口,实现底层功能。

    Servlet容器也叫做Servlet引擎,是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于 MIME的请求,格式化基于MIME的响应。Servlet没有main方法,不能独立运行,它必须被部署到Servlet容器中,由容器来实例化和调用 Servlet的方法(如doGet()和doPost()),Servlet容器在Servlet的生命周期内包容和管理Servlet。在JSP技术推出后,管理和运行Servlet/JSP的容器也称为Web容器。Tomcat是一个免费的开放源代码的Servlet容器。

    SpringMVC是一个Web框架,用于替换Servlet。

  1. 创建Maven项目,补全目录结构

  2. 导入jar包。使用SpringMVC技术需要先导入SpringMVC坐标与Servlet坐标。SpringMVC是基于Spring的,在pom.xml只导入spring-webmvcjar包即可,它会自动依赖spring相关坐标

    <?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>
        <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>
      </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>
    

    servlet的坐标为什么需要添加<scope>provided</scope>?

    • scope是maven中jar包依赖作用范围的描述,
    • 如果不设置默认是compile在在编译、运行、测试时均有效
    • 如果运行有效的话就会和tomcat中的servlet-api包发生冲突,导致启动报错
    • provided代表的是该包只在编译和测试的时候用,运行的时候无效直接使用tomcat中的,就避免冲突
  3. 创建配置类和Controller类

    @Configuration
    @ComponentScan("com.itheima.controller")
    public class SpringMvcConfig {
    }
    
    @Controller
    public class UserController {
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("user save ...");
            return "{'info':'springmvc'}";
        }
    }
    
    名称 @Controller
    类型 类注解
    位置 SpringMVC控制器类定义上方
    作用 设定SpringMVC的核心控制器bean
    名称 @RequestMapping
    类型 类注解或方法注解
    位置 SpringMVC控制器类或方法定义上方
    作用 设置当前控制器方法请求访问路径
    相关属性 value(默认),请求访问路径
    名称 @ResponseBody
    类型 类注解或方法注解
    位置 SpringMVC控制器类或方法定义上方
    作用 设置当前控制器方法响应内容为当前返回值,无需解析

    不加@ResponseBody会导致方法返回的内容被解析为字符串。加上之后被识别为json。

  4. 使用配置类替换web.xml

    将web.xml删除,换成ServletContainersInitConfig。该类用于Tomcat的配置,告知Tomcat使用SpringMVC配置。

    public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
        //加载springmvc配置类
        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;
        }
    }
    

    AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类

    AbstractDispatcherServletInitializer提供了三个接口方法供用户实现

    • createServletApplicationContext方法,创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围
    • getServletMappings方法,设定SpringMVC对应的请求映射路径,即SpringMVC拦截哪些请求
    • createRootApplicationContext方法,如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式和createServletApplicationContext相同。
    • createServletApplicationContext用来加载SpringMVC环境
    • createRootApplicationContext用来加载Spring环境
  5. 配置Tomcat环境,启动运行项目,浏览器访问

  • 开发流程总结(1+N)

    • 一次性工作(只需要做一次)
      • 创建工程,设置服务器,加载工程
      • 导入坐标
      • 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
      • SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
    • 多次工作
      • 定义处理请求的控制器类
      • 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)
  • 工作流程分析

    SpringMVC的使用过程总共分两个阶段,分别是启动服务器初始化过程单次请求过程

    • 启动服务器初始化过程
      1. 服务器启动,执行ServletContainersInitConfig类,初始化web容器
      2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象
        • 该方法加载SpringMVC的配置类SpringMvcConfig来初始化SpringMVC的容器
      3. 加载SpringMvcConfig配置类
      4. 执行@ComponentScan加载对应的bean
        • 扫描指定包及其子包下所有类上的注解,如Controller类上的@Controller注解
      5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法
        • 此时就建立了 /save 和 save方法的对应关系
      6. 执行getServletMappings方法,设定SpringMVC拦截请求的路径规则
        • /代表所拦截请求的路径规则,只有被拦截后才能交给SpringMVC来处理请求
    • 单次请求过程
      1. 发送请求http://localhost/save
      2. web容器发现该请求满足SpringMVC拦截规则,将请求交给SpringMVC处理
      3. 解析请求路径/save
      4. 由/save匹配执行对应的方法save()
        • 上面的第五步已经将请求路径和方法建立了对应关系,通过/save就能找到对应的save方法
      5. 执行save()
      6. 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方

三、Bean加载控制

目前的项目目录结构:

  • config目录存入的是配置类,写过的配置类有:

    • ServletContainersInitConfig
    • SpringConfig
    • SpringMvcConfig
    • JdbcConfig
    • MybatisConfig
  • controller目录存放的是SpringMVC的controller类,service目录存放的是service接口和实现类,dao目录存放的是dao/Mapper接口

  • SpringMVC加载其相关bean(表现层bean),也就是controller包下的类

  • Spring控制的bean

    • 业务bean(Service)
    • 功能bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)

因为功能不同,如何避免Spring错误加载到SpringMVC的bean?

  • 方式一:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等

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

    真正在做开发的时候,因为Dao最终是交给MapperScannerConfigurer对象来进行扫描处理的,我们只需要将其扫描到service包即可。

  • 方式二:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包中的bean

    @Configuration
    @ComponentScan(value="com.itheima",
        excludeFilters=@ComponentScan.Filter(
        	type = FilterType.ANNOTATION,
            classes = Controller.class
        )
    )
    public class SpringConfig {
    }
    
    • excludeFilters属性:设置扫描加载bean时,排除的过滤规则
    • type属性:设置排除规则,当前使用按照bean定义时的注解类型进行排除
      • ANNOTATION:按照注解排除
      • ASSIGNABLE_TYPE:按照指定的类型过滤
      • ASPECTJ:按照Aspectj表达式排除,基本上不会用
      • REGEX:按照正则表达式排除
      • CUSTOM:按照自定义规则排除
    • classes属性:设置排除的具体注解类,当前设置排除@Controller定义的bean
  • 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中[了解即可]

有了Spring的配置类,要想在tomcat服务器启动将其加载,我们需要修改ServletContainersInitConfig

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    protected WebApplicationContext createRootApplicationContext() {
      AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
}

对于上述的配置方式,Spring还提供了一种更简单的配置方式,可以不用再去创建AnnotationConfigWebApplicationContext对象,不用手动register对应的配置类。

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[]{"/"};
    }
}

四、PostMan

HTTP请求方法:

序号 方法 描述
1 GET 请求指定的页面信息,并返回实体主体。
2 HEAD 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。
5 DELETE 请求服务器删除指定的页面。
6 CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
7 OPTIONS 允许客户端查看服务器的性能。
8 TRACE 回显服务器收到的请求,主要用于测试或诊断。
9 PATCH 是对 PUT 方法的补充,用来对已知资源进行局部更新 。

代码编写完后,我们要想测试,只需要打开浏览器直接输入地址发送请求即可。发送的是GET请求可以直接使用浏览器,但是如果要发送的是POST请求,就得准备页面在页面上准备form表单,测试起来比较麻烦。所以我们就需要借助一些第三方工具,如PostMan。PostMan是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件,用于进行接口测试。

五、请求

SpringMVC是web层的框架,主要的作用是接收请求、接收数据、响应结果。

请求,顾名思义,就是使用者希望从服务器端索取一些资源,向服务器发出询问。在B/S架构中,就是客户端浏览器向服务器发出询问。在我们的JavaEE工程中,客户浏览器发出询问,要遵循HTTP协议所规定的。

  • 请求映射路径

    @Controller
    public class UserController {
    
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("user save ...");
            return "{'module':'user save'}";
        }
        
        @RequestMapping("/delete")
        @ResponseBody
        public String save(){
            System.out.println("user delete ...");
            return "{'module':'user delete'}";
        }
    }
    
    @Controller
    public class BookController {
    
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("book save ...");
            return "{'module':'book save'}";
        }
    }
    

    UserController有一个save方法,访问路径为http://localhost/save

    BookController也有一个save方法,访问路径为http://localhost/save

    当访问http://localhost/saved的时候,到底是访问UserController还是BookController?

    • 解决思路:为不同模块设置模块名作为请求路径前置

      对于Book模块的save,将其访问路径设置http://localhost/book/save

      对于User模块的save,将其访问路径设置http://localhost/user/save

    因此要设置映射路径。可以简单地将每个方法前面改为@RequestMapping("/book/save") @RequestMapping("/user/save")。但是每个方法前面都需要进行修改,写起来比较麻烦而且还有很多重复代码,如果/user后期发生变化,所有的方法都需要改,耦合度太高。

    优化方法:

    @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 save(){
            System.out.println("user delete ...");
            return "{'module':'user delete'}";
        }
    }
    
    @Controller
    @RequestMapping("/book")
    public class BookController {
    
        @RequestMapping("/save")
        @ResponseBody
        public String save(){
            System.out.println("book save ...");
            return "{'module':'book save'}";
        }
    }
    
  • 请求参数

    请求路径设置好后,只要确保页面发送请求地址和后台Controller类中配置的路径一致,就可以接收到前端的请求。

    关于请求参数的传递与接收是和请求方式有关系的,目前比较常见的两种请求方式为:

    • GET

      发送参数:http://localhost/commonParam?name=itcast

      接受参数:

      @Controller
      public class UserController {
      
          @RequestMapping("/commonParam")
          @ResponseBody
          public String commonParam(String name){
              System.out.println("普通参数传递 name ==> "+name);
              return "{'module':'commonParam'}";
          }
      }
      

      多个参数的情况:http://localhost/commonParam?name=itcast&age=15

      @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':'commonParam'}";
          }
      }
      

      接收到的参数会出现中文乱码问题,需要修改pom.xml来解决GET请求中文乱码问题。

      <build>
          <plugins>
            <plugin>
              <groupId>org.apache.tomcat.maven</groupId>
              <artifactId>tomcat7-maven-plugin</artifactId>
              <version>2.1</version>
              <configuration>
                <port>80</port><!--tomcat端口号-->
                <path>/</path> <!--虚拟目录-->
                <uriEncoding>UTF-8</uriEncoding><!--访问路径编解码字符集-->
              </configuration>
            </plugin>
          </plugins>
        </build>
      
    • POST

      接收参数:和GET一致,不用做任何修改

      中文乱码问题:配置过滤器

      public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
          protected Class<?>[] getRootConfigClasses() {
              return new Class[0];
          }
      
          protected Class<?>[] getServletConfigClasses() {
              return new Class[]{SpringMvcConfig.class};
          }
      
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
      
          //乱码处理
          @Override
          protected Filter[] getServletFilters() {
              CharacterEncodingFilter filter = new CharacterEncodingFilter();
              filter.setEncoding("UTF-8");
              return new Filter[]{filter};
          }
      }
      

      CharacterEncodingFilter是在spring-web包中,所以用之前需要导入对应的jar包。

    不同种类参数的传递方式不同。

    • 普通参数

      与前面中的举例相同。如果形参与地址参数名不一致,需要使用@RequestParam注解。

      http://localhost/commonParamDifferentName?name=张三&age=18

      @RequestMapping("/commonParamDifferentName")
          @ResponseBody
          public String commonParamDifferentName(@RequestPaam("name") String userName , int age){
              System.out.println("普通参数传递 userName ==> "+userName);
              System.out.println("普通参数传递 age ==> "+age);
              return "{'module':'common param different name'}";
          }
      
    • POJO类型参数

      POJO:一个简单的、普通Java对象。请求参数名与形参对象属性名相同时,定义POJO类型形参即可接收参数。

      定义POJO类:

      public class User {
          private String name;
          private int age;
          //setter...getter...略
      }
      

      发送和请求参数:http://localhost/pojoParam?name=itcast&age=15

      后台接受参数:

      //POJO参数:请求参数与形参对象中的属性对应即可完成参数传递
      @RequestMapping("/pojoParam")
      @ResponseBody
      public String pojoParam(User user){
          System.out.println("pojo参数传递 user ==> "+user);
          return "{'module':'pojo param'}";
      }
      
      • POJO参数接收,前端GET和POST发送请求数据的方式不变。
      • 请求参数key的名称要和POJO中属性的名称一致,否则无法封装。
    • 嵌套POJO类型参数

      POJO对象中嵌套了其他的POJO类。如:

      public class Address {
          private String province;
          private String city;
          //setter...getter...略
      }
      public class User {
          private String name;
          private int age;
          private Address address;
          //setter...getter...略
      }
      

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

      发送和请求参数:http://localhost/pojoContainPojoParam?name=itcast&age=15&address.city=beijing&address.province=beijing

      后台接受参数:

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

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

      发送和请求参数:http://localhost/arrayParam?likes= game&likes=music&likes=travel

      后台接受参数:

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

      发送和请求参数:http://localhost/arrayParam?likes= game&likes=music&likes=travel

      后台接受参数:

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

      不使用@RequestParam的话会导致SpringMVC将List看做是一个POJO对象来处理,将其创建一个对象并准备把前端的数据封装到对象中,但是List是一个接口无法创建对象,所以报错。

      名称 @RequestParam
      类型 形参注解
      位置 SpringMVC控制器方法形参定义前面
      作用 绑定请求参数与处理器方法形参间的关系
      相关参数 required:是否为必传参数
      defaultValue:参数默认值
  • json数据传输参数

    对于JSON数据类型,我们常见的有三种:

    • json普通数组(["value1","value2","value3",...])

      SpringMVC默认使用的是jackson来处理json的转换,所以需要在pom.xml添加jackson依赖。

      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.0</version>
      </dependency>
      

      在SpringMVC的配置类中开启SpringMVC的注解支持,这里面就包含了将JSON转换成对象的功能。

      @Configuration
      @ComponentScan("com.itheima.controller")
      //开启json数据类型自动转换
      @EnableWebMvc
      public class SpringMvcConfig {
      }
      

      发送JSON数据:http://localhost/listParamForJson

      接受JSON数据:

      //使用@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'}";
      }
      
    • json对象({key1:value1,key2:value2,...})

      发送JSON数据:http://localhost/listParamForJson

      接受JSON数据:

      @RequestMapping("/pojoParamForJson")
      @ResponseBody
      public String pojoParamForJson(@RequestBody User user){
          System.out.println("pojo(json)参数传递 user ==> "+user);
          return "{'module':'pojo for json param'}";
      }
      
    • json对象数组([{key1:value1,...},{key2:value2,...}])

      发送JSON数据:http://localhost/listParamForJson

      接受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'}";
      }
      
      名称 @RequestBody
      类型 形参注解
      位置 SpringMVC控制器方法形参定义前面
      作用 将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次
      • 该注解可以写在类上或者方法上
      • 写在类上就是该类下的所有方法都有@ReponseBody功能
      • 当方法上有@ReponseBody注解后
        • 方法的返回值为字符串,会将其作为文本内容直接响应给前端
        • 方法的返回值为对象,会将对象转换成JSON响应给前端
      • @RequestBody与@RequestParam区别
        • @RequestParam用于接收url地址传参,表单传参【application/x-www-form-urlencoded】
        • @RequestBody用于接收json数据【application/json】
      • 应用
        • 后期开发中,发送json格式数据为主,@RequestBody应用较广
        • 如果发送非json格式数据,选用@RequestParam接收请求参数
  • 日期类型参数传递

    日期类型比较特殊,因为对于日期的格式有多种输入方式,比如:

    • 2088-08-18
    • 2088/08/18
    • 08/18/2088
    • ......

    发送JSON数据:http://localhost/dataParam?date=2088/08/08

    接受JSON数据:

    @RequestMapping("/dataParam")
    @ResponseBody
    public String dataParam(Date date)
        System.out.println("参数传递 date ==> "+date);
        return "{'module':'data param'}";
    }
    

    但当传递的日期不符合其默认格式yyyy/MM/dd,SpringMVC就无法进行格式转换,会报错。解决该问题需要使用@DateTimeFormat

    @RequestMapping("/dataParam")
    @ResponseBody
    public String dataParam(Date date,
                            @DateTimeFormat(pattern="yyyy-MM-dd") Date date1)
        System.out.println("参数传递 date ==> "+date);
    	System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
        return "{'module':'data param'}";
    }
    

    携带时间的日期:

    @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'}";
    }
    

六、响应

响应,它表示了服务器端收到请求,同时也已经处理完成,把处理的结果告知用户。简单来说,指的就是服务器把请求的处理结果告知客户端。在B/S架构中,响应就是把结果带回浏览器。

SpringMVC接收到请求和数据后,进行一些处理,或转发给Service,Service层再调用Dao层完成。不管怎样,处理完以后,都需要将结果告知给用户。

对于响应,主要就包含两部分内容:响应页面和响应数据。响应数据包括文本数据和json数据。

  • 环境准备

    创建一个Web的Maven项目,pom.xml添加Spring依赖,创建对应的配置类,编写模型类User,webapp下创建page.jsp

    <html>
    <body>
    <h2>Hello Spring MVC!</h2>
    </body>
    </html>
    

    编写UserController。

  • 响应页面

    • 设置返回页面

      @Controller
      public class UserController {
          
          @RequestMapping("/toJumpPage")
          //注意
          //1.此处不能添加@ResponseBody,如果加了该注入,会直接将page.jsp当字符串返回前端
          //2.方法需要返回String
          public String toJumpPage(){
              System.out.println("跳转页面");
              return "page.jsp";
          }
          
      }
      
    • 启动程序测试

      直接打开浏览器,输入http://localhost/toJumpPage

  • 响应文本数据

    @Controller
    public class UserController {
        
       	@RequestMapping("/toText")
    	//注意此处该注解就不能省略,如果省略了,会把response text当前页面名称去查找,如果没有回报404错误
        @ResponseBody
        public String toText(){
            System.out.println("返回纯文本数据");
            return "response text";
        }
    }
    

    因为我们现在发送的是GET请求,可以使用浏览器也可以使用PostMan进行测试,输入地址http://localhost/toText访问。

  • 响应JSON数据

    • 响应POJO对象

      @Controller
      public class UserController {
      
          @RequestMapping("/toJsonPOJO")
          @ResponseBody
          public User toJsonPOJO(){
              System.out.println("返回json对象数据");
              User user = new User();
              user.setName("itcast");
              user.setAge(15);
              return user;
          }
      
      }
      

      返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解。

    • 响应POJO集合对象

      @Controller
      public class UserController {
          
          @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;
          }
          
      }
      
      

七、Rest风格

REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格。当我们想表示一个网络资源的时候,可以使用两种方式:

  • 传统风格资源描述形式
    • http://localhost/user/getById?id=1 查询id为1的用户信息
    • http://localhost/user/saveUser 保存用户信息
  • REST风格描述形式
    • http://localhost/user/1
    • http://localhost/user

传统方式一般是一个请求url对应一种操作,这样做不仅麻烦,也不安全,因为读取了请求url地址就大概知道该url实现的是一个什么样的操作。

REST风格的描述中请求地址变简单了,并且光看请求URL并不是很能猜出来该URL的具体功能。所以REST的优点有:

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 书写简化

但是一个相同的url地址既可以是新增也可以是修改或者查询,该如何区分该请求到底是什么操作呢?

  • 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作

    • http://localhost/users 查询全部用户信息 GET(查询)
    • http://localhost/users/1 查询指定用户信息 GET(查询)
    • http://localhost/users 添加用户信息 POST(新增/保存)
    • http://localhost/users 修改用户信息 PUT(修改/更新)
    • http://localhost/users/1 删除用户信息 DELETE(删除)
  • 按照不同的请求方式代表不同的操作类型。

    • 发送GET请求是用来做查询
    • 发送POST请求是用来做新增
    • 发送PUT请求是用来做修改
    • 发送DELETE请求是用来做删除
  • 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范

    • REST提供了对应的架构方式,按照这种架构设计项目可以降低开发的复杂性,提高系统的可伸缩性
    • REST中规定GET/POST/PUT/DELETE针对的是查询/新增/修改/删除,但如果用GET请求做删除也是可以的
  • 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts......

  • 根据REST风格对资源进行访问称为RESTful

八、RESTful

@Controller
public class UserController {
	@RequestMapping("/save")
    @ResponseBody
    public String save(@RequestBody User user) {
        System.out.println("user save..."+user);
        return "{'module':'user save'}";
    }

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

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

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

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


@Controller
public class BookController {
    
	@RequestMapping(value = "/books",method = RequestMethod.POST)
    @ResponseBody
    public String save(@RequestBody Book book){
        System.out.println("book save..." + book);
        return "{'module':'book save'}";
    }

    @RequestMapping(value = "/books/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }

    @RequestMapping(value = "/books",method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody Book book){
        System.out.println("book update..." + book);
        return "{'module':'book update'}";
    }

    @RequestMapping(value = "/books/{id}",method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("book getById..." + id);
        return "{'module':'book getById'}";
    }

    @RequestMapping(value = "/books",method = RequestMethod.GET)
    @ResponseBody
    public String getAll(){
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
    
}
  • 修改RESTful风格

    • 新增

      @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'}";
          }
      }
      
      • 将请求路径更改为/users
        • 访问该方法使用 POST: http://localhost/users
      • 使用method属性限定该方法的访问方式为POST
        • 如果发送的不是POST请求,比如发送GET请求,则会报错
    • 删除

      @Controller
      public class UserController {
          //设置当前请求方法为DELETE,表示REST风格中的删除操作
      	@RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
          @ResponseBody
          public String delete(@PathVariable Integer id) {
              System.out.println("user delete..." + id);
              return "{'module':'user delete'}";
          }
      }
      
      • 将请求路径更改为/users
        • 访问该方法使用 DELETE: http://localhost/users

      前端发送请求的时候使用:http://localhost/users/1,路径中的1就是我们想要传递的参数。

      多个参数:前端发送请求的时候使用:http://localhost/users/1/tom,路径中的1tom就是传递的两个参数。

      后端获取:

      @Controller
      public class UserController {
          //设置当前请求方法为DELETE,表示REST风格中的删除操作
      	@RequestMapping(value = "/users/{id}/{name}",method = RequestMethod.DELETE)
          @ResponseBody
          public String delete(@PathVariable Integer id,@PathVariable String name) {
              System.out.println("user delete..." + id+","+name);
              return "{'module':'user delete'}";
          }
      }
      
    • 修改

      @Controller
      public class UserController {
          //设置当前请求方法为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'}";
          }
      }
      

      将请求路径更改为/users

      • 访问该方法使用 PUT: http://localhost/users
    • 根据ID查询

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

      将请求路径更改为/users

      • 访问该方法使用 GET: http://localhost/users/666
    • 查询所有

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

      将请求路径更改为/users

      • 访问该方法使用 GET: http://localhost/users
  • 总结RESTful

    (1)设定Http请求动作(动词)

    @RequestMapping(value="",method = RequestMethod.POST|GET|PUT|DELETE)

    (2)设定请求参数(路径变量)

    @RequestMapping(value="/users/{id}",method = RequestMethod.DELETE)

    @ReponseBody

    public String delete(@PathVariable Integer id){}

    名称 @PathVariable
    类型 形参注解
    位置 SpringMVC控制器方法形参定义前面
    作用 绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应
    • 区别
      • @RequestParam用于接收url地址传参或表单传参
      • @RequestBody用于接收json数据
      • @PathVariable用于接收路径参数,使用{参数名称}描述路径参数
    • 应用
      • 后期开发中,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
      • 如果发送非json格式数据,选用@RequestParam接收请求参数
      • 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值
  • RESTful快速开发

    • 每个方法的@RequestMapping注解中都定义了访问路径/books,重复性太高。

      可以将@RequestMapping提到类上面,用来定义所有方法共同的访问路径。

    • 每个方法的@RequestMapping注解中都要使用method属性定义请求方式,重复性太高。

      使用@GetMapping @PostMapping @PutMapping @DeleteMapping代替

    • 每个方法响应json都需要加上@ResponseBody注解,重复性太高。

      将ResponseBody提到类上面,让所有的方法都有@ResponseBody的功能

      使用@RestController注解替换@Controller与@ResponseBody注解,简化书写

    修改后:

    @RestController //@Controller + ReponseBody
    @RequestMapping("/books")
    public class BookController {
        
    	//@RequestMapping(method = RequestMethod.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("/{id}")
        public String delete(@PathVariable Integer id){
            System.out.println("book delete..." + id);
            return "{'module':'book delete'}";
        }
    
        //@RequestMapping(method = RequestMethod.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("/{id}")
        public String getById(@PathVariable Integer id){
            System.out.println("book getById..." + id);
            return "{'module':'book getById'}";
        }
    
        //@RequestMapping(method = RequestMethod.GET)
        @GetMapping
        public String getAll(){
            System.out.println("book getAll...");
            return "{'module':'book getAll'}";
        }
        
    }
    

posted @ 2023-04-30 19:23  无机呱子  阅读(16)  评论(0编辑  收藏  举报