【前置內容】Spring 學習筆記全系列傳送門: Spring學習筆記 - 第一章 - IoC(控制反轉)、IoC容器、Bean的實例化與生命周期、DI(依賴註入) Spring學習筆記 - 第二章 - 註解開發、配置管理第三方Bean、註解管理第三方Bean、Spring 整合 MyBatis 和 ...
【前置內容】Spring 學習筆記全系列傳送門:
目錄SpingMVC 學習筆記全系列傳送門:
- 1、SpringMVC概述
- 2、SpringMVC入門案例
- 3、請求與相應
- 4、REST風格
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風格的優點
- 隱藏資源的訪問行為,無法通過地址得知對資源是何種
- 隱藏資源的訪問行為,無法通過地址得知對資源是何種