SpringMVC學習筆記 - 第一章 - 工作流程、Bean載入控制、請求與響應(引數接收與內容返回)、RESTful

Dandelion_000發表於2023-01-26

【前置內容】Spring 學習筆記全系列傳送門:

SpingMVC 學習筆記全系列傳送門:

目錄

1、SpringMVC概述

  • SpringMVC是一種基於Java實現MVC模型的輕量級Web框架

  • 優點

    • 使用簡單、開發便捷(相比於Servlet)
    • 靈活性強
  • SpringMVC主要負責的就是

    • controller如何接收請求和資料
    • 如何將請求和資料轉發給業務層
    • 如何將響應資料轉換成json發回到前端
  • 三層架構與MVC模式

    三層架構與MVC模式

    • 瀏覽器傳送一個請求給後端伺服器,後端伺服器現在是使用Servlet來接收請求和資料

      如果所有的處理都交給Servlet來處理的話,所有的東西都耦合在一起,對後期的維護和擴充套件極為不利

    • 將後端伺服器Servlet拆分成三層,分別是webservicedao

      • web層主要由servlet來處理,負責頁面請求和資料的收集以及響應結果給前端
      • service層主要負責業務邏輯的處理
      • dao層主要負責資料的增刪改查操作

      servlet處理請求和資料的時候,存在的問題是一個servlet只能處理一個請求

    • 針對web層進行了最佳化,採用了MVC設計模式,將其設計為controllerviewModel

      • controller負責請求和資料的接收,接收後將其轉發給service進行業務處理
      • service根據需要會呼叫dao對資料進行增刪改查
      • dao把資料處理完後將結果交給service,service再交給controller
      • controller根據需求組裝成Model和View,Model和View組合起來生成頁面轉發給前端瀏覽器
      • 這樣做的好處就是controller可以處理多個請求,並對請求進行分發,執行不同的業務操作。

    三層架構與MVC模式傳遞

    • 隨著網際網路的發展,上面的模式因為是同步呼叫,效能慢慢的跟不是需求,所以非同步呼叫慢慢的走到了前臺,是現在比較流行的一種處理方式

      • 因為是非同步呼叫,所以後端不需要返回view檢視,將其去除
      • 前端如果透過非同步呼叫的方式進行互動,後臺就需要將返回的資料轉換成json格式進行返回

2、SpringMVC入門案例

2.1 注意事項

  • SpringMVC是基於Spring的,在pom.xml只匯入了spring-webmvcjar包的原因是它會自動依賴spring相關座標
  • 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環境

2.2 案例製作

  1. 建立 Maven-webapp 專案,整理包結構

    image-20221228060317972

  2. 匯入依賴

    <?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>priv.dandelion</groupId>
      <artifactId>01_quickstart</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
    
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    
      <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>
    
    
  3. 建立 Controller

    // 定義Controller,宣告為Spring的bean
    @Controller
    public class UserController {
    
        // 設定當前操作的訪問路徑
        @RequestMapping("/save")
        // 設定當前操作的返回值型別
        @ResponseBody
        public String save() {
            System.out.println("user----save");
            // 相應的內容直接返回
            return "{'hello':'springmvc'}";
        }
    }
    
  4. 建立配置類

    // 建立SpringMVC的配置檔案,載入controller對應的bean
    @Configuration
    @ComponentScan("priv.dandelion.controller")
    public class SpringMvcConfig {
    }
    
  5. 定義 Servlet 容器啟動的配置類(代替 web.xml

    // 定義一個servlet容器啟動的配置類,在此處載入spring配置
    public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    
        // 載入SpringMVC容器配置
        @Override
        protected WebApplicationContext createServletApplicationContext() {
            AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            // 註冊配置
            ctx.register(SpringMvcConfig.class);
            // tomcat伺服器啟動時就可以載入到SpringMvcConfig.class
            return ctx;
        }
    
        // 設定那些請求歸屬於SpringMVC處理
        @Override
        protected String[] getServletMappings() {
            // 將所有請求交給SpringMVC處理
            return new String[]{"/"};
        }
    
        // 載入Spring容器配置,此處暫時未用到
        @Override
        protected WebApplicationContext createRootApplicationContext() {
            return null;
        }
    }
    

2.3 相關知識點

  • @Controller

    名稱 @Controller
    型別 類註解
    位置 SpringMVC控制器類定義上方
    作用 設定SpringMVC的核心控制器bean
  • @RequestMapping

    名稱 @RequestMapping
    型別 類註解或方法註解
    位置 SpringMVC控制器類或方法定義上方
    作用 設定當前控制器方法請求訪問路徑
    相關屬性 value(預設),請求訪問路徑
  • @ResponseBody

    名稱 @ResponseBody
    型別 類註解或方法註解
    位置 SpringMVC控制器類或方法定義上方
    作用 設定當前控制器方法響應內容為當前返回值,無需解析

2.4 工作流程解析

包含關係:

  • Web容器
    • ServletContext
      • WebApplicationContext
        • UserController
          • /save -> save() 【SpringMVC 的對映並不是放在 bean 中管理的】

2.4.1 啟動伺服器初始化過程

  1. 伺服器啟動,執行 web 伺服器配置類 ServletContainersInitConfig,初始化web容器

    • 功能類似於以前的web.xml
  2. 執行createServletApplicationContext方法,建立了WebApplicationContext物件(存在於 ServletContext 中)

    • 該方法載入SpringMVC的配置類SpringMvcConfig來初始化SpringMVC的容器

      // 載入SpringMVC容器配置
      @Override
      protected WebApplicationContext createServletApplicationContext() {
          AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
          // 註冊配置
          ctx.register(SpringMvcConfig.class);
          // tomcat伺服器啟動時就可以載入到SpringMvcConfig.class
          return ctx;
      }
      
  3. 載入SpringMvcConfig配置類,以在下一步載入所需的bean

    // 建立SpringMVC的配置檔案,載入controller對應的bean
    @Configuration
    @ComponentScan("priv.dandelion.controller")
    public class SpringMvcConfig {
    }
    
  4. 執行@ComponentScan載入對應的bean

    掃描指定包及其子包下所有類上的註解,如Controller類上的@Controller註解

  5. 載入UserController,每個@RequestMapping的名稱對應一個具體的方法

    • 此時就建立了 /save 和 save() 方法的對應關係

      // 定義Controller,宣告為Spring的bean
      @Controller
      public class UserController {
                      
          // 設定當前操作的訪問路徑
          @RequestMapping("/save")
          // 設定當前操作的返回值型別
          @ResponseBody
          public String save() {
              System.out.println("user----save");
              // 相應的內容直接返回
              return "{'hello':'springmvc'}";
          }
      }
      
  6. 執行getServletMappings方法,設定SpringMVC攔截請求的路徑規則

    • /代表所攔截請求的路徑規則,只有被攔截後才能交給SpringMVC來處理請求/代表所攔截請求的路徑規則,只有被攔截後才能交給SpringMVC來處理請求

      // 設定那些請求歸屬於SpringMVC處理
      @Override
      protected String[] getServletMappings() {
          // 將所有請求交給SpringMVC處理
          return new String[]{"/"};
      }
      

2.4.2 單次請求過程

  1. 傳送請求http://localhost/save

  2. web容器發現該請求滿足SpringMVC攔截規則,將請求交給 SpringMVC 處理

  3. 解析請求路徑 /save

  4. 由 /save 匹配執行對應的方法 save()

    • 上面的第五步已經將請求路徑和方法建立了對應關係,透過 /save 就能找到對應的save方法
  5. 執行 save()

  6. 檢測到有 @ResponseBody 直接將 save() 方法的返回值作為響應體返回給請求方

2.5 bean 載入控制

2.5.1 問題分析

問題:

  • 哪些 bean 交給 SpringMVC 管理,哪些包交給 Spring 管理
  • 因為功能不同,如何避免 Spring 錯誤載入到 SpringMVC 的 bean
  • 包結構

    • config目錄存入的是配置類,本篇和前面的內容已經寫過的配置類有:

      • ServletContainersInitConfig
      • SpringConfig
      • SpringMvcConfig
      • JdbcConfig
      • MybatisConfig
    • controller 目錄存放的是 SpringMVC 的 controller 類

    • service 目錄存放的是 service 介面和實現類

    • dao 目錄存放的是 dao/Mapper 介面

  • 管理

    • SpringMVC載入其相關bean
      • 表現層 bean(Controller),也就是controller包下的類
    • Spring控制的bean
      • 業務 bean(Service)
      • 功能 bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer等)

2.5.2 思路分析

載入Spring控制的bean的時候排除掉 SpringMVC 控制的 bean

  • 方式一:Spring載入的bean設定掃描範圍為精準範圍,例如service包、dao包等
  • 方式二:Spring載入的bean設定掃描範圍為priv.dandelion,排除掉controller包中的bean
  • 方式三:不區分Spring與SpringMVC的環境,載入到同一個環境中[瞭解即可]

2.5.3 環境準備

  • 建立 Web 的 Maven 專案,刪除 web.xml 配置檔案

  • 依賴

    <?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>priv.dandelion</groupId>
        <artifactId>02_bean_load</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>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.16</version>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.6</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.3.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>
    
  • 建立對應的配置類

    • 替代 web.xml 的 web 伺服器配置類 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() {
              return null;
          }
      }
      
    • SpringMVC 配置類

      @Configuration
      @ComponentScan("priv.dandelion.controller")
      public class SpringMvcConfig {
      }
      
    • Spring 配置類

      @Configuration
      @ComponentScan("priv.dandelion")
      public class SpringConfig {
      }
      
  • 實體類

    public class User {
        private Integer id;
        private String name;
        private Integer age;
        // Getter
        // Settrt
        // toString
    }
    
  • Dao 介面

    public interface UserDao {
        @Insert("insert into tbl_user(name,age)values(#{name},#{age})")
        public void save(User user);
    }
    
  • Service 實現類 (介面不表)

    @Service
    public class UserServiceImpl implements UserService {
        public void save(User user) {
            System.out.println("user service ...");
        }
    }
    
  • Controller

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

2.5.4 設定 bean 載入控制

  • web伺服器啟動時載入配置類的相關配置

    • 標準方式

      • 方式
        • 修改 createRootApplicationContext() 方法中的內容
          • 與 createServletApplicationContext() 基本相同但是載入 SpringConfig.class
      • 說明
        • createServletApplicationContext() 載入的是 SpringMVC 環境配置
        • createRootApplicationContext() 載入的是 Spring 環境配置
      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;
          }
      }
      
    • 簡化方式

      說明:

      • AbstractDispatcherServletInitializer 類包含一個子類 AbstractAnnotationConfigDispatcherServletInitializer 可以簡化配置
      • 方法名中包含 RootConfig 的是 Spring 的配置,包含 ServletConfig 的是對 SpringMVC 的配置,與標準方式中的相同
      public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
      
          @Override
          protected Class<?>[] getRootConfigClasses() {
              return new Class[]{SpringConfig.class};
          }
      
          @Override
          protected Class<?>[] getServletConfigClasses() {
              return new Class[]{SpringMvcConfig.class};
          }
      
          @Override
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
      }public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
      
          @Override
          protected Class<?>[] getRootConfigClasses() {
              return new Class[]{SpringConfig.class};
          }
      
          @Override
          protected Class<?>[] getServletConfigClasses() {
              return new Class[]{SpringMvcConfig.class};
          }
      
          @Override
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
      }
      
  • bean 載入控制方式

    • 方案一:修改Spring 配置類,精準掃描

      說明:

      • 此處使用了MyBatis技術且是自動代理,所以可以不掃Dao
      • 但是建議按照標準開發規則書寫,通用性強
      @Configuration
      @ComponentScan({"priv.dandelion.service","priv.dandelion.dao"})
      public class SpringConfig {
      }
      
    • 方案二

      說明:

      • 掃描所有的包

      • 但是使用過濾器進行排除

        • 過濾的型別是按註解過濾,過濾Controller註解

      使用到的屬性:

      • excludeFilters屬性:設定掃描載入bean時,排除的過濾規則

      • type屬性:設定排除規則,當前使用按照bean定義時的註解型別進行排除

        • ANNOTATION:按照註解排除
        • ASSIGNABLE_TYPE:按照指定的型別過濾
        • ASPECTJ:按照Aspectj表示式排除,基本上不會用
        • REGEX:按照正規表示式排除
        • CUSTOM:按照自定義規則排除

        大家只需要知道第一種ANNOTATION即可

      • classes屬性:設定排除的具體註解類,當前設定排除@Controller定義的bean

      @Configuration
      @ComponentScan(
              value = "priv.dandelion",
              excludeFilters = @ComponentScan.Filter(
                      type = FilterType.ANNOTATION,
                      classes = Controller.class
              )
      )
      public class SpringConfig {
      }
      
    • 方式三(不區分 Spring 與 SpringMVC 的環境)【此處不做詳細說明】

2.5.5 相關知識點:@ComponentScan

名稱 @ComponentScan
型別 類註解
位置 類定義上方
作用 設定spring配置類掃描路徑,用於載入使用註解格式定義的bean
相關屬性 excludeFilters:排除掃描路徑中載入的bean,需要指定類別(type)和具體項(classes)
includeFilters:載入指定的bean,需要指定類別(type)和具體項(classes)

3、請求與相應

3.1 設定請求對映路徑

本小節注意:

  • 當類上和方法上都新增了@RequestMapping註解,前端傳送請求的時候,要和兩個註解的value值相加匹配才能訪問到。
  • @RequestMapping註解value屬性前面加不加/都可以

3.1.1 環境準備

  • 依賴

    <?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>priv.dandelion</groupId>
        <artifactId>03_request_mapping</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>
    
  • 配置類

    • Spring 配置類(此處未使用到)

      @Configuration
      @ComponentScan(value = "priv.dandelion",
              excludeFilters = @ComponentScan.Filter(
                      type = FilterType.ANNOTATION,
                      classes = Controller.class
              )
      )
      public class SpringConfig {
      }
      
    • SpringMVC 配置類

      @Configuration
      @ComponentScan("priv.dandelion.controller")
      public class SpringMvcConfig {
      }
      
    • web伺服器配置類 (簡化配置)

      public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
      
          protected Class<?>[] getServletConfigClasses() {
              return new Class[]{SpringMvcConfig.class};
          }
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
          protected Class<?>[] getRootConfigClasses() {
              return new Class[0];
          }
      }
      
  • Controller

    • UserController

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

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

3.1.2 問題分析

  • 以上環境準備完成後,啟動伺服器時會報錯

    [INFO] Initializing Servlet 'dispatcher'
    [WARNING] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'userController' method 
    priv.dandelion.controller.UserController#save()
    to { /save}: There is already 'bookController' bean method
    priv.dandelion.controller.BookController#save() mapped.
    [ERROR] Context initialization failed
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'userController' method 
    priv.dandelion.controller.UserController#save()
    to { /save}: There is already 'bookController' bean method
    priv.dandelion.controller.BookController#save() mapped.
        at ...
        at ...
        ...
    
  • 從錯誤資訊可知

    • 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

3.1.3 設定對映路徑

  • 方案一(耦合度高不推薦):對每一個資源的 RequestMapping 進行修改

    • UserController

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

  • 方案二:為 Controller 新增一個整體的 RequestMapping(稱為請求路徑字首),其他不變

    • UserController

      @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'}";
          }
      }
      
    • BookController(不表)

3.2 請求引數

3.2.1 環境準備

  • 依賴、配置類見 3.1.1

  • 實體類

    • Address

      public class Address {
          private String province;
          private String city;
          
          // Getter,Setter,toString不表
      }
      
    • User

      public class User {
          private String name;
          private int age;
      
          // Getter,Setter,toString不表
      }
      
  • Controller

    @Controller
    public class UserController {
    
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(){
            return "{'module':'commonParam'}";
        }
    }
    

3.2.2 引數傳遞及中文亂碼處理方案

Get請求與引數:

http://localhost/commonParam?name=dandelion&age=18

POST請求與引數:

  • 傳送Post請求時,引數放在請求體中,若使用PostMan工具,引數需要寫在Body模組中,傳送表單資料時使用 x-www-from-urlencodedform-data除了傳送表單之外還可以傳送檔案)
  • GET請求

    • 接收引數

      @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'}";
          }
      }
      
    • GET中文亂碼:配置pom.xml

      Tomcat8.5以後的版本已經處理了中文亂碼的問題,但是IDEA中的Tomcat外掛目前只到Tomcat7,所以需要修改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請求

    • 接收引數

      POST請求接收引數程式碼與GET請求一致

    • POST中文亂碼問題:設定過濾器

      • 在web伺服器配置類 ServletContainersInitConfig 中重寫 getServletFilters() 方法,建立所需的過濾器物件,並設定編碼字符集為UTF-8

      • CharacterEncodingFilter 是在 spring-web 包中,所以用之前需要匯入對應的 jar 包

        import org.springframework.web.filter.CharacterEncodingFilter;
        
      @Override
      protected Filter[] getServletFilters() {
          CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
          characterEncodingFilter.setEncoding("UTF-8");
          return new Filter[]{characterEncodingFilter};
      }
      

3.3 五種型別引數傳遞

3.3.1 普通引數

普通引數的基本使用已經實現過,詳見 3.2.2 引數傳遞及中文亂碼處理方案

  • 解決請求中的引數名稱和 Controller 方法的引數不一致問題

    • 當出現請求中的引數名稱和Controller中方法的引數名不匹配時,無法正常接收到引數
    • 使用 @RequestPaam() 註解修飾不一致的引數,為其指定需要匹配的請求引數
    @Controller
    public class UserController {
    
        // `http://localhost/commonParam?username=dandelion&age=12`
        @RequestMapping("/commonParam")
        @ResponseBody
        public String commonParam(@RequestParam("username") String name, int age){
            System.out.println("普通引數name:"+ name);
            System.out.println("普通引數age:"+ age);
            return "{'module':'commonParam'}";
        }
    }
    

3.3.2 POJO 資料型別

  • 直接使用一個實體類作為形參,框架會使用 setter 自動將資料進行寫入
  • 實體類中的屬性名稱需要和請求引數的名稱保持一致,否則接收不到
  • 若實體類中沒有對應的 setter 可以和請求引數的引數名匹配,則預設零假空(未進行寫入),可以使用該特性在實際開發中減少工作量
  • 實體類

    public class User {
        private String name;
        private int age;
    
        // Getter,Setter,toString不表
    }
    
  • Controller

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

3.3.3 巢狀 POJO 型別引數

對於巢狀的POJO型別,在進行引數傳遞時,也要使用巢狀的形式來書寫請求引數名稱

http://localhost/pojoParam?name=dandelion&age=12&address.province=hubei&address.city=wuhan

  • 實體類

    • User

      public class User {
          private String name;
          private int age;
          
          private Address address;
      
          // Getter,Setter,toString不表
      }
      
    • Address

      public class Address {
          private String province;
          private String city;
          
          // Getter,Setter,toString不表
      }
      
  • Controller

    Controller 部分程式碼與 3.3.3 巢狀 POJO 型別引數一致

3.3.4 陣列型別引數

  • 請求引數為陣列時,不同的陣列元素使用相同的請求引數名稱
  • 接收請求引數時,使用陣列作為形參
  • 請求

    http://localhost/arrayParam?person=zhangsan&person=lisi&person=wangwu
    
  • 接收

    @Controller
    public class UserController {
    
        @RequestMapping("/arrayParam")
        @ResponseBody
        public String arrayParam(String[] person){
            System.out.println("陣列引數:"+ Arrays.toString(person));
            return "{'module':'arrayParam'}";
        }
    }
    

3.3.5 集合型別引數

  • 請求

    與陣列引數的請求方式相同

  • 接收

    • 錯誤案例

      以下程式碼段執行時會報錯:NoSuchMethodException: java.util.List.<init>(),缺少構造,(內心:廢話 List 介面哪來的構造)

      嚴重: Servlet.service() for servlet [dispatcher] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No primary or default constructor found for interface java.util.List] with root cause
      java.lang.NoSuchMethodException: java.util.List.<init>()
      
      @Controller
      public class UserController {
      
          @RequestMapping("/listParam")
          @ResponseBody
          public String listParam(List<String> person){
              System.out.println("集合引數:"+ person);
              return "{'module':'listParam'}";
          }
      }
      
    • 原因及解決方案

      • 問題原因

        SpringMVC 將 List 看做是一個 POJO 物件來處理,將其建立一個物件並準備把前端的資料封裝到物件中,但是 List 是一個介面無法建立物件,所以報錯。

      • 解決方案:使用@RequestParam註解

        • 集合儲存普通引數:請求引數名與形參集合物件名相同且請求引數為多個,@RequestParam 繫結引數關係
        • 顯而易見,對於簡單資料型別使用陣列會比集合更簡單些。
        @Controller
        public class UserController {
        
            @RequestMapping("/listParam")
            @ResponseBody
            public String listParam(@RequestParam List<String> person){
                System.out.println("集合引數:"+ person);
                return "{'module':'listParam'}";
            }
        }
        

3.3.6 相關知識點:@RequestParam

名稱 @RequestParam
型別 形參註解
位置 SpringMVC控制器方法形參定義前面
作用 繫結請求引數與處理器方法形參間的關係
相關引數 required:是否為必傳引數
defaultValue:引數預設值

3.4 JSON 資料傳輸引數

  • 引數放在請求體中,若使用 PostMan 工具,引數需要寫在Body模組中,傳送表單資料時使用 raw,並將資料格式修改為 JSON

3.4.1 JSON 資料傳輸引數分類與準備工作

  • 引數分類

    // json普通陣列
    ["value1","value2","value3",...]
    // json物件
     {"key1":"value1","key2":"value2",...}
    // json物件陣列
    [{"key11":"value11",...},{"key21":"value21",...}]
    
  • 準備工作

    • 新增依賴

      <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.9.0</version>
      </dependency>
      
    • 開啟SpringMVC註解驅動,用於開啟 json 資料型別自動轉換:@EnableWebMvc

      @Configuration
      @ComponentScan("priv.dandelion.controller")
      @EnableWebMvc
      public class SpringMvcConfig {
      }
      
    • 引數前新增@RequestBody

      • 使用@RequestBody註解將外部傳遞的json陣列資料對映到形參的集合物件中作為資料
      • 區別於 @RequestParam,本小節總結部分會進行說明
      • 區別於 @ResponseBody,書寫要正確
      • 下文中展示

3.4.2 三種引數傳輸格式

3.4.2.1 json普通陣列
  • JSON

    ["zhangsan","lisi","wangwu"]
    
  • 接收

    @RequestMapping("/listParamForJson")
    @ResponseBody
    // //使用@RequestBody註解將外部傳遞的json陣列資料對映到形參的集合物件中作為資料
    public String listParamForJson(@RequestBody List<String> person){
        System.out.println("list common(json)引數傳遞 list:" + person);
        return "{'module':'list common for json param'}";
    }
    
3.4.2.2 json物件
  • 若 JSON 物件中的 key 與實體類中的 setter 名稱(標準書寫)不能匹配時,不執行 setter ,實體類中的資料不變(不進行其他操作時預設為零假空)
  • 同理,若不傳遞某一屬性的值,實體類中的資料不變(不進行其他操作時預設為零假空)
  • JSON

    // 單個POJO
    {
    	"name":"dandelion",
    	"age":12
    }
    
    // 巢狀POJO
    {
    	"name1":"dandelion",
    	"age":12,
        "address":{
            "province":"provinceName",
            "city":"cityName"
        }
    }
    
  • 實體類

    見 3.3.3

  • 接收

    @RequestMapping("/pojoParamForJson")
    @ResponseBody
    public String pojoParamForJson(@RequestBody User user){
        System.out.println("pojo(json)引數傳遞 user:"+user);
        return "{'module':'pojo for json param'}";
    }
    
3.4.2.3 json物件陣列
  • JSON

    [
        {"name":"dandelion","age":15,"address":{"province":"provinceName","city":"cityName"}},
        {"name":"dandelion000","age":12}
    ]
    
  • 接收

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

3.4.3 相關知識點

  • 知識點1:@EnableWebMvc

    名稱 @EnableWebMvc
    型別 配置類註解
    位置 SpringMVC配置類定義上方
    作用 開啟SpringMVC多項輔助功能
  • 知識點2:@RequestBody

    • 整理

      名稱 @RequestBody
      型別 形參註解
      位置 SpringMVC控制器方法形參定義前面
      作用 將請求中請求體所包含的資料傳遞給請求引數,此註解一個處理器方法只能使用一次
    • @RequestBody與@RequestParam區別

      • 區別

        • @RequestParam用於接收url地址傳參,表單傳參【application/x-www-form-urlencoded】
        • @RequestBody用於接收json資料【application/json】
      • 應用

        • 後期開發中,傳送json格式資料為主,@RequestBody應用較廣
        • 如果傳送非json格式資料,選用@RequestParam接收請求引數

3.5 日期型別引數傳遞

  • 接收案例

    • 請求

      http://localhost/dataParam?date=2022/02/22
      
    • 接收

      @RequestMapping("/dataParam")
      @ResponseBody
      public String dataParam(Date date, Date date1){
          System.out.println("引數傳遞 date:"+date);
          System.out.println("引數傳遞 date1:"+date1);
          return"{'module':'data param'}";
      }
      
    • 結果

      • 傳送請求後會發現該部分程式碼會報錯,但若請求引數中只有date而沒有date1時則正常接收

      • 報錯資訊:

        方法引數型別不匹配,在將 String 轉換為 Date 時出現問題,轉換失敗

        [WARNING] Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.Date] for value '2022-02-22'; nested exception is java.lang.IllegalArgumentException]
        
        
  • 解決方案(接收任意日期格式的方法):使用 @DateTimeFormat 指定日期格式

    • 請求

      http://localhost/dataParam?date=2022/02/22&date1=22-02-2022&date2=2022-02-22 22:22:22
      
    • 接收

      @RequestMapping("/dataParam")
      @ResponseBody
      public String dataParam(Date date,
                              @DateTimeFormat(pattern = "dd-MM-yyyy") Date date1,
                              @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date date2){
          System.out.println("引數傳遞 date:"+ date);
          System.out.println("引數傳遞 date1(dd-MM-yyyy):"+ date1);
          System.out.println("引數傳遞 date2(yyyy-MM-dd HH:mm:ss):"+ date2);
          return"{'module':'data param'}";
      }
      
  • 相關知識點

    • @DateTimeFormat

      名稱 @DateTimeFormat
      型別 形參註解
      位置 SpringMVC控制器方法形參前面
      作用 設定日期時間型資料格式
      相關屬性 pattern:指定日期時間格式字串
    • 內部實現原理

      SpringMVC中提供了很多型別轉換介面和實現類,其中有 Converter 介面

      • Converter 介面

        • Converter所屬的包為org.springframework.core.convert.converter

        • 框架中有提供很多對應Converter介面的實現類,用來實現不同資料型別之間的轉換,如:

        • 請求引數年齡資料(String→Integer)

        • 日期格式轉換(String → Date)

        /**
        *	S: the source type
        *	T: the target type
        */
        public interface Converter<S, T> {
            @Nullable
            //該方法就是將從頁面上接收的資料(S)轉換成我們想要的資料型別(T)返回
            T convert(S source);
        }
        
      • HttpMessageConverter 介面

        該介面是實現物件與 JSON 之間的轉換工作,使用時在SpringMVC的配置類把@EnableWebMvc當做標配配置上去,不省略

3.6 響應

3.6.1 環境準備

  • 依賴

    <?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>priv.dandelion</groupId>
        <artifactId>05_response</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>
            <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>
    
  • 配置類

    • 伺服器配置類

      public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
      
          protected Class<?>[] getServletConfigClasses() {
              return new Class[]{SpringMvcConfig.class};
          }
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
          protected Class<?>[] getRootConfigClasses() {
              return new Class[0];
          }
      
          @Override
          protected Filter[] getServletFilters() {
              CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
              characterEncodingFilter.setEncoding("UTF-8");
              return new Filter[]{characterEncodingFilter};
          }
      }
      
    • SpringMVC 配置類

      @Configuration
      @ComponentScan("priv.dandelion.controller")
      public class SpringMvcConfig {
      }
      
  • 實體類

    public class User {
        private String name;
        private int age;
        //getter...setter...toString省略
    }
    
  • webapp下建立頁面 page.jsp

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

    @Controller
    public class UserController {
    
    }
    

3.6.2 響應頁面(瞭解)

  • 注意此處不能使用@ResponseBody,否則會將返回值內容作為字串返回給前端
  • 注意進行頁面跳轉時,返回值為頁面名稱,返回值型別為字串
@RequestMapping("/toJumpPage")
public String toJumpPage() {
    System.out.println("跳轉頁面");
    return "page.jsp";
}

3.6.3 返回文字資料(瞭解)

  • 注意此處 @ResponseBody 註解就不能省略
  • 如果省略了會把response text當前頁面名稱去查詢,如果沒有回報404
@RequestMapping("/toText")
@ResponseBody
public String toText() {
    System.out.println("返回純文字資料");
    return "response text";
}

3.6.4 響應 JSON 資料

準備工作:

  • 開啟SpringMVC註解驅動,用於開啟 json 資料型別自動轉換:@EnableWebMvc

    @Configuration
    @ComponentScan("priv.dandelion.controller")
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
3.6.4.1 響應 POJO 物件
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO() {
    System.out.println("返回JSON資料物件");
    User user = new User();
    user.setName("dandelion");
    user.setAge(12);
    return user;
}
3.6.4.2 響應 POJO 集合物件

此處返回的是POJO的集合,基本資料型別的集合同理

@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList() {
    System.out.println("返回JSON資料物件");
    User user1 = new User();
    user1.setName("dandelion");
    user1.setAge(12);

    User user2 = new User();
    user2.setName("dandelion000");
    user2.setAge(15);

    List<User> users = new ArrayList<>();
    users.add(user1);
    users.add(user2);
    return users;
}
3.6.4.3 相關知識點:@ResponseBody
  • 整理

    名稱 @ResponseBody
    型別 方法\類註解
    位置 SpringMVC控制器方法定義上方和控制類上
    作用 設定當前控制器返回值作為響應體,
    寫在類上,該類的所有方法都有該註解功能
    相關屬性 pattern:指定日期時間格式字串
  • 說明

    • 該註解可以寫在類上或者方法上

    • 寫在類上就是該類下的所有方法都有@ReponseBody功能

    • 當方法上有@ReponseBody註解後

      • 方法的返回值為字串,會將其作為文字內容直接響應給前端
      • 方法的返回值為物件,會將物件轉換成JSON響應給前端
    • 此處又使用到了型別轉換,內部還是透過Converter介面的實現類完成的,所以Converter除了前面所說的功能外,它還可以實現:

      • 物件轉Json資料(POJO -> json)

      • 集合轉Json資料(Collection -> json)

4、REST風格

4.1 REST 簡介

  • REST(Representational State Transfer),表現形式狀態轉換,它是一種軟體架構風格

  • REST風格與傳統風格的區別

    • 傳統風格資源描述形式

      • http://localhost/user/getById?id=1 查詢id為1的使用者資訊
      • http://localhost/user/saveUser 儲存使用者資訊
    • REST風格描述形式

      • http://localhost/user/1
      • http://localhost/user
  • REST風格的優點

    • 隱藏資源的訪問行為,無法透過地址得知對資源是何種操作
    • 書寫簡化
  • REST風格的使用

    • 按照REST風格訪問資源時使用行為動作區分對資源進行了何種操作

      • GET(查詢)http://localhost/users 查詢全部使用者資訊
      • GET(查詢)http://localhost/users/1 查詢指定使用者資訊
      • POST(新增/儲存)http://localhost/users 新增使用者資訊
      • PUT(修改/更新)http://localhost/users 修改使用者資訊
      • DELETE(刪除)http://localhost/users/1 刪除使用者資訊
    • 請求方式:按照不同的請求方式代表不同的操作型別

      • 傳送GET請求是用來做查詢
      • 傳送POST請求是用來做新增
      • 傳送PUT請求是用來做修改
      • 傳送DELETE請求是用來做刪除
  • 注意

    • 上述行為是約定方式,約定不是規範,可以打破,所以稱REST風格,而不是REST規範
      • REST提供了對應的架構方式,按照這種架構設計專案可以降低開發的複雜性,提高系統的可伸縮性
      • REST中規定GET/POST/PUT/DELETE針對的是查詢/新增/修改/刪除,但是我們如果非要用GET請求做刪除,這點在程式上執行是可以實現的
      • 但是如果絕大多數人都遵循這種風格,你寫的程式碼讓別人讀起來就有點莫名其妙了。
    • 描述模組的名稱通常使用複數,也就是加s的格式描述,表示此類資源,而非單個資源,例如:users、books、accounts......
  • RESTful

    • 根據REST風格對資源進行訪問稱為RESTful

4.2 RESTful 入門案例

4.2.1 環境準備

  • 依賴

    <?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>priv.dandelion</groupId>
        <artifactId>06_rest</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>
            <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>
    
    
  • 配置類

    • 伺服器配置類

      public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
      
          protected Class<?>[] getServletConfigClasses() {
              return new Class[]{SpringMvcConfig.class};
          }
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
          protected Class<?>[] getRootConfigClasses() {
              return new Class[0];
          }
      
          @Override
          protected Filter[] getServletFilters() {
              CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
              characterEncodingFilter.setEncoding("UTF-8");
              return new Filter[]{characterEncodingFilter};
          }
      }
      
    • SpringMVC配置類,開啟json資料型別自動轉換

      @Configuration
      @ComponentScan("priv.dandelion.controller")
      @EnableWebMvc
      public class SpringMvcConfig {
      }
      
  • 實體類

    • User

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

      public class Book {
          private String name;
          private double price;
           //getter...setter...toString省略
      }
      
  • Controller

    • UserController

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

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

4.2.2 思路分析

  • 將之前的增刪改查替換成RESTful的開發方式
    • 修改前: 新增: /save ,修改: /update,刪除 /delete...
  • 修改後:
    • 增刪改查: /users
    • 根據GET查詢、POST新增、PUT修改、DELETE刪除對方法的請求方式進行限定

4.2.3 修改 RESTFUL 風格

4.2.3.1 各項操作
  • 新增

    • 請求

      POST http://localhost/users
      
    • 接收

      @RequestMapping(value = "/users", method = RequestMethod.POST)
      @ResponseBody
      public String save(@RequestBody User user) {
          System.out.println("user save..."+user);
          return "{'module':'user save'}";
      }
      
  • 刪除

    • 請求

      DELETE http://localhost/users/1
      
    • 接收

      • 使用REST風格時,引數寫在請求路徑中

      • 接收時對引數使用 @PathVariable 註解,在資源路徑的引數位置使用{},其中的值應和引數名稱保持一致

      • 若引數名稱和{}中的內容不一致,應手動進行繫結,使用 @PathVariable 註解的 value 屬性,如下:

        @RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
        @ResponseBody
        public String delete(@PathVariable("id") Integer userId) {}
        
      @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 http://localhost/users
      
    • 接收

      @RequestMapping(value = "/users", method = RequestMethod.PUT)
      @ResponseBody
      public String update(@RequestBody User user) {
          System.out.println("user update..." + user);
          return "{'module':'user update'}";
      }
      
  • 查詢

    • 查詢單個

      • 請求

        GET http://localhost/users/1
        
      • 接收

        @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 http://localhost/users
        
      • 接收

        @RequestMapping(value = "/users", method = RequestMethod.GET)
        @ResponseBody
        public String getAll() {
            System.out.println("user getAll...");
            return "{'module':'user getAll'}";
        }
        
4.2.3.2 相關知識點:@PathVarlable
名稱 @PathVariable
型別 形參註解
位置 SpringMVC控制器方法形參定義前面
作用 繫結路徑引數與處理器方法形參間的關係,要求路徑引數名與形參名一一對應
4.2.3.3 相關知識點:三種接收引數的註解
  • 區別

    • @RequestParam 用於接收url地址傳參或表單傳參
    • @RequestBody 用於接收json資料
    • @PathVariable 用於接收路徑引數,使用{引數名稱}描述路徑引數
  • 應用

    • 後期開發中,傳送請求引數超過1個時,以json格式為主,@RequestBody 應用較廣
    • 如果傳送非 json 格式資料,選用 @RequestParam 接收請求引數
    • 採用 RESTful 進行開發,當引數數量較少時,例如1個,可以採用 @PathVariable 接收請求路徑變數,通常用於傳遞id值

4.3 RESTFUL 快速開發

4.3.1 開發簡化

  • 未簡化程式碼

    @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'}";
        }
    
    }
    
  • 簡化過程(參見注釋)

    1. 將請求路徑中共同的部分抽取
    2. 將方法上@RequestMapping中不需要的value屬性省略
    3. 將所有方法上的@ResponseBody抽取
    4. 存在@Controller和@ResponseBody,可以使用@RestController代替
    5. 使用@PostMapping、@DeleteMapping等註解代替@RequestMapping
    // @Controller
    // 3.所有方法均需要@ResponseBody,抽取
    // @ResponseBody
    @RestController // 4.其中包含了@Controller和@ResponseBody
    // 1.以字首的形式將請求路徑中相同的內容抽取出來
    @RequestMapping("/books")
    public class BookController {
    
        // 2.value屬性已經完全被抽取,可以省略
        // @RequestMapping(method = RequestMethod.POST)
        // 5.簡化上一行程式碼,下同
        @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'}";
        }
    }
    
  • 簡化後程式碼

    @RestController
    @RequestMapping("/books")
    public class BookController {
    
        @PostMapping
        public String save(@RequestBody Book book){
            System.out.println("book save..." + book);
            return "{'module':'book save'}";
        }
    
        @DeleteMapping("/{id}")
        public String delete(@PathVariable Integer id){
            System.out.println("book delete..." + id);
            return "{'module':'book delete'}";
        }
    
        @PutMapping
        public String update(@RequestBody Book book){
            System.out.println("book update..." + book);
            return "{'module':'book update'}";
        }
    
        @GetMapping("/{id}")
        public String getById(@PathVariable Integer id){
            System.out.println("book getById..." + id);
            return "{'module':'book getById'}";
        }
    
        @GetMapping
        public String getAll(){
            System.out.println("book getAll...");
            return "{'module':'book getAll'}";
        }
    }
    

4.3.2 相關知識點

  • 知識點1:@RestController

    名稱 @RestController
    型別 類註解
    位置 基於SpringMVC的RESTful開發控制器類定義上方
    作用 設定當前控制器類為RESTful風格,
    等同於@Controller與@ResponseBody兩個註解組合功能
  • 知識點2:@GetMapping @PostMapping @PutMapping @DeleteMapping

    名稱 @GetMapping @PostMapping @PutMapping @DeleteMapping
    型別 方法註解
    位置 基於SpringMVC的RESTful開發控制器方法定義上方
    作用 設定當前控制器方法請求訪問路徑與請求動作,每種對應一個請求動作,
    例如@GetMapping對應GET請求
    相關屬性 value(預設):請求訪問路徑

4.4 RESTFUL 案例

4.4.1 需求分析

4.4.2 環境準備

  • 依賴

    <?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>priv.dandelion</groupId>
        <artifactId>07_rest_case</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>
            <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>
    
  • 配置類

    • 伺服器配置類

      public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
      
          protected Class<?>[] getServletConfigClasses() {
              return new Class[]{SpringMvcConfig.class};
          }
          protected String[] getServletMappings() {
              return new String[]{"/"};
          }
          protected Class<?>[] getRootConfigClasses() {
              return new Class[0];
          }
      
          @Override
          protected Filter[] getServletFilters() {
              CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
              characterEncodingFilter.setEncoding("UTF-8");
              return new Filter[]{characterEncodingFilter};
          }
      }
      
    • SpringMVC配置類

      @Configuration
      @ComponentScan("priv.dandelion.controller")
      @EnableWebMvc
      public class SpringMvcConfig {
      }
      
  • 實體類

    public class Book {
        private Integer id;
        private String type;
        private String name;
        private String description;
        //setter...getter...toString略
    }
    
  • 控制器

    @Controller
    public class BookController {
    
    }
    

4.4.3 後臺介面開發

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

    @PostMapping
    public String save(@RequestBody Book book) {
        System.out.println("book save ==> "+ book);
        return "{'module':'book save success'}";
    }

    @GetMapping
    public List<Book> getAll() {
        System.out.println("book getAll is running ...");
        List<Book> bookList = new ArrayList<Book>();

        Book book1 = new Book();
        book1.setType("計算機");
        book1.setName("SpringMVC1");
        book1.setDescription("123");
        bookList.add(book1);

        Book book2 = new Book();
        book2.setType("計算機");
        book2.setName("SpringMVC2");
        book2.setDescription("234");
        bookList.add(book2);

        Book book3 = new Book();
        book3.setType("計算機叢書");
        book3.setName("SpringMVC3");
        book3.setDescription("345");
        bookList.add(book3);

        return bookList;
    }
}

4.4.4 頁面訪問處理

  • 頁面準備

    • 環境:vue.js + axios + elementui

    • 頁面

      <!DOCTYPE html>
      
      <html>
          <head>
              <!-- 頁面meta -->
              <meta charset="utf-8">
              <title>SpringMVC案例</title>
              <!-- 引入樣式 -->
              <link rel="stylesheet" href="../plugins/elementui/index.css">
              <link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
              <link rel="stylesheet" href="../css/style.css">
          </head>
      
          <body class="hold-transition">
      
              <div id="app">
      
                  <div class="content-header">
                      <h1>圖書管理</h1>
                  </div>
      
                  <div class="app-container">
                      <div class="box">
                          <div class="filter-container">
                              <el-input placeholder="圖書名稱" style="width: 200px;" class="filter-item"></el-input>
                              <el-button class="dalfBut">查詢</el-button>
                              <el-button type="primary" class="butT" @click="openSave()">新建</el-button>
                          </div>
      
                          <el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
                              <el-table-column type="index" align="center" label="序號"></el-table-column>
                              <el-table-column prop="type" label="圖書類別" align="center"></el-table-column>
                              <el-table-column prop="name" label="圖書名稱" align="center"></el-table-column>
                              <el-table-column prop="description" label="描述" align="center"></el-table-column>
                              <el-table-column label="操作" align="center">
                                  <template slot-scope="scope">
                                      <el-button type="primary" size="mini">編輯</el-button>
                                      <el-button size="mini" type="danger">刪除</el-button>
                                  </template>
                              </el-table-column>
                          </el-table>
      
                          <div class="pagination-container">
                              <el-pagination
                                  class="pagiantion"
                                  @current-change="handleCurrentChange"
                                  :current-page="pagination.currentPage"
                                  :page-size="pagination.pageSize"
                                  layout="total, prev, pager, next, jumper"
                                  :total="pagination.total">
                              </el-pagination>
                          </div>
      
                          <!-- 新增標籤彈層 -->
                          <div class="add-form">
                              <el-dialog title="新增圖書" :visible.sync="dialogFormVisible">
                                  <el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
                                      <el-row>
                                          <el-col :span="12">
                                              <el-form-item label="圖書類別" prop="type">
                                                  <el-input v-model="formData.type"/>
                                              </el-form-item>
                                          </el-col>
                                          <el-col :span="12">
                                              <el-form-item label="圖書名稱" prop="name">
                                                  <el-input v-model="formData.name"/>
                                              </el-form-item>
                                          </el-col>
                                      </el-row>
                                      <el-row>
                                          <el-col :span="24">
                                              <el-form-item label="描述">
                                                  <el-input v-model="formData.description" type="textarea"></el-input>
                                              </el-form-item>
                                          </el-col>
                                      </el-row>
                                  </el-form>
                                  <div slot="footer" class="dialog-footer">
                                      <el-button @click="dialogFormVisible = false">取消</el-button>
                                      <el-button type="primary" @click="saveBook()">確定</el-button>
                                  </div>
                              </el-dialog>
                          </div>
      
                      </div>
                  </div>
              </div>
          </body>
      
          <!-- 引入元件庫 -->
          <script src="../js/vue.js"></script>
          <script src="../plugins/elementui/index.js"></script>
          <script type="text/javascript" src="../js/jquery.min.js"></script>
          <script src="../js/axios-0.18.0.js"></script>
      
          <script>
              var vue = new Vue({
      
                  el: '#app',
      
                  data:{
      				dataList: [],//當前頁要展示的分頁列表資料
                      formData: {},//表單資料
                      dialogFormVisible: false,//增加表單是否可見
                      dialogFormVisible4Edit:false,//編輯表單是否可見
                      pagination: {},//分頁模型資料,暫時棄用
                  },
      
                  //鉤子函式,VUE物件初始化完成後自動執行
                  created() {
                      this.getAll();
                  },
      
                  methods: {
                      // 重置表單
                      resetForm() {
                          //清空輸入框
                          this.formData = {};
                      },
      
                      // 彈出新增視窗
                      openSave() {
                          this.dialogFormVisible = true;
                          this.resetForm();
                      },
      
                      //新增
                      saveBook () {
                          axios.post("/books",this.formData).then((res)=>{
      
                          });
                      },
      
                      //主頁列表查詢
                      getAll() {
                          axios.get("/books").then((res)=>{
                              this.dataList = res.data;
                          });
                      },
      
                  }
              })
          </script>
      </html>
      
  • 頁面訪問存在問題及解決方案

    • 存在問題:無法訪問到頁面

      • 報錯內容

        [WARNING] Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
        [WARNING] No mapping for GET /pages/books.html
        
      • 原因

        現在傳送請求 http://localhost/pages/books.html 訪問這個頁面,但是被SpringMVC攔截,認為應該有一個 Controller 中方法的 RequestMapping 叫這個名字

    • 解決方案 放行非SpringMVC的請求

      • 遇到這種情況 SpringMVC 應當放行,由 web 伺服器進行處理
      • web 伺服器配置類中 getServletMappings 攔截了所有的請求,交給 SpringMVC 處理,需要為 SpringMVC 的配置新增過濾,放行頁面請求
      • 對頁面的過濾一般單獨抽成一個功能類
      • 功能類 SpringMvcSupport

        • 需要繼承 WebMvcConfigurationSupport,覆寫 addResourceHandlers ,操作 registry 屬性
        • 新增 @Configuration 註解,以用於包掃描
        @Configuration
        public class SpringMvcSupport extends WebMvcConfigurationSupport {
        
            // 新增資源過濾
            @Override
            protected void addResourceHandlers(ResourceHandlerRegistry registry) {
                // 當訪問頁面時,如果請求的是/pages/**,不走MVC,走/pages目錄下的內容
                registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
                registry.addResourceHandler("/js/**").addResourceLocations("/js/");
                registry.addResourceHandler("/css/**").addResourceLocations("/css/");
                registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
                // ...
            }
        }
        
      • 配置包掃描

        @Configuration
        @ComponentScan({"priv.dandelion.controller","priv.dandelion.config"})
        @EnableWebMvc
        public class SpringMvcConfig {
        }
        

相關文章