【前置內容】Spring 學習筆記全系列傳送門:
SpingMVC 學習筆記全系列傳送門:
1、SpringMVC概述
SpringMVC是一種基於Java實現MVC模型的輕量級Web框架
優點
- 使用簡單、開發便捷(相比於Servlet)
- 靈活性強
SpringMVC主要負責的就是
- controller如何接收請求和資料
- 如何將請求和資料轉發給業務層
- 如何將響應資料轉換成json發回到前端
-
三層架構與MVC模式
-
瀏覽器傳送一個請求給後端伺服器,後端伺服器現在是使用Servlet來接收請求和資料
如果所有的處理都交給Servlet來處理的話,所有的東西都耦合在一起,對後期的維護和擴充套件極為不利
-
將後端伺服器Servlet拆分成三層,分別是
web
、service
和dao
- web層主要由servlet來處理,負責頁面請求和資料的收集以及響應結果給前端
- service層主要負責業務邏輯的處理
- dao層主要負責資料的增刪改查操作
servlet處理請求和資料的時候,存在的問題是一個servlet只能處理一個請求
-
針對web層進行了最佳化,採用了MVC設計模式,將其設計為
controller
、view
和Model
- controller負責請求和資料的接收,接收後將其轉發給service進行業務處理
- service根據需要會呼叫dao對資料進行增刪改查
- dao把資料處理完後將結果交給service,service再交給controller
- controller根據需求組裝成Model和View,Model和View組合起來生成頁面轉發給前端瀏覽器
- 這樣做的好處就是controller可以處理多個請求,並對請求進行分發,執行不同的業務操作。
-
隨著網際網路的發展,上面的模式因為是同步呼叫,效能慢慢的跟不是需求,所以非同步呼叫慢慢的走到了前臺,是現在比較流行的一種處理方式
- 因為是非同步呼叫,所以後端不需要返回view檢視,將其去除
- 前端如果透過非同步呼叫的方式進行互動,後臺就需要將返回的資料轉換成json格式進行返回
-
2、SpringMVC入門案例
2.1 注意事項
- SpringMVC是基於Spring的,在pom.xml只匯入了
spring-webmvc
jar包的原因是它會自動依賴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 案例製作
-
建立 Maven-webapp 專案,整理包結構
-
匯入依賴
<?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>
-
建立 Controller
// 定義Controller,宣告為Spring的bean @Controller public class UserController { // 設定當前操作的訪問路徑 @RequestMapping("/save") // 設定當前操作的返回值型別 @ResponseBody public String save() { System.out.println("user----save"); // 相應的內容直接返回 return "{'hello':'springmvc'}"; } }
-
建立配置類
// 建立SpringMVC的配置檔案,載入controller對應的bean @Configuration @ComponentScan("priv.dandelion.controller") public class SpringMvcConfig { }
-
定義 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 啟動伺服器初始化過程
-
伺服器啟動,執行 web 伺服器配置類 ServletContainersInitConfig,初始化web容器
- 功能類似於以前的web.xml
-
執行createServletApplicationContext方法,建立了WebApplicationContext物件(存在於 ServletContext 中)
-
該方法載入SpringMVC的配置類SpringMvcConfig來初始化SpringMVC的容器
// 載入SpringMVC容器配置 @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); // 註冊配置 ctx.register(SpringMvcConfig.class); // tomcat伺服器啟動時就可以載入到SpringMvcConfig.class return ctx; }
-
-
載入SpringMvcConfig配置類,以在下一步載入所需的bean
// 建立SpringMVC的配置檔案,載入controller對應的bean @Configuration @ComponentScan("priv.dandelion.controller") public class SpringMvcConfig { }
-
執行@ComponentScan載入對應的bean
掃描指定包及其子包下所有類上的註解,如Controller類上的@Controller註解
-
載入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'}"; } }
-
-
執行getServletMappings方法,設定SpringMVC攔截請求的路徑規則
-
/
代表所攔截請求的路徑規則,只有被攔截後才能交給SpringMVC來處理請求/
代表所攔截請求的路徑規則,只有被攔截後才能交給SpringMVC來處理請求// 設定那些請求歸屬於SpringMVC處理 @Override protected String[] getServletMappings() { // 將所有請求交給SpringMVC處理 return new String[]{"/"}; }
-
2.4.2 單次請求過程
-
傳送請求
http://localhost/save
-
web容器發現該請求滿足SpringMVC攔截規則,將請求交給 SpringMVC 處理
-
解析請求路徑 /save
-
由 /save 匹配執行對應的方法 save()
- 上面的第五步已經將請求路徑和方法建立了對應關係,透過 /save 就能找到對應的save方法
-
執行 save()
-
檢測到有 @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等)
- SpringMVC載入其相關bean
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
- 修改 createRootApplicationContext() 方法中的內容
- 說明
- 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,就會出現衝突
- UserController有一個save方法,訪問路徑為
-
解決方案:為不同模組設定模組名作為請求路徑前置
- 對於Book模組的save,將其訪問路徑設定
http://localhost/book/save
- 對於User模組的save,將其訪問路徑設定
http://localhost/user/save
- 對於Book模組的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-urlencoded
(form-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(查詢)
-
請求方式:按照不同的請求方式代表不同的操作型別
- 傳送GET請求是用來做查詢
- 傳送POST請求是用來做新增
- 傳送PUT請求是用來做修改
- 傳送DELETE請求是用來做刪除
-
-
注意
- 上述行為是約定方式,約定不是規範,可以打破,所以稱REST風格,而不是REST規範
- REST提供了對應的架構方式,按照這種架構設計專案可以降低開發的複雜性,提高系統的可伸縮性
- REST中規定GET/POST/PUT/DELETE針對的是查詢/新增/修改/刪除,但是我們如果非要用GET請求做刪除,這點在程式上執行是可以實現的
- 但是如果絕大多數人都遵循這種風格,你寫的程式碼讓別人讀起來就有點莫名其妙了。
- 描述模組的名稱通常使用複數,也就是加s的格式描述,表示此類資源,而非單個資源,例如:users、books、accounts......
- 上述行為是約定方式,約定不是規範,可以打破,所以稱REST風格,而不是REST規範
-
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'}"; } }
-
簡化過程(參見注釋)
- 將請求路徑中共同的部分抽取
- 將方法上@RequestMapping中不需要的value屬性省略
- 將所有方法上的@ResponseBody抽取
- 存在@Controller和@ResponseBody,可以使用@RestController代替
- 使用@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 { }
-