📘博客主頁:程序員葵安
🫶感謝大家點贊👍🏻收藏?評論?🏻
????????????????文章目錄
一、請求
1.1 postman
1.2 簡單參數?
1.3 實體參數
1.4 數組集合參數
1.5 日期參數
1.6 JSON參數?
1.7 路徑參數
二、響應
2.1 @ResponseBody
2.2 統一響應結果
三、分層解耦
3.1 三層架構
3.2 分層解耦
3.3 IOC&DI
一、請求
接收頁面傳遞過來的請求數據
1.1 postman
-
Postman是一款功能強大的網頁調試與發送網頁HTTP請求的Chrome插件。
Postman原是Chrome瀏覽器的插件,可以模擬瀏覽器向后端服務器發起任何形式(如:get、post)的HTTP請求
使用Postman還可以在發起請求時,攜帶一些請求參數、請求頭等信息
-
作用:常用于進行接口測試
-
特征
-
簡單
-
實用
-
美觀
-
大方
-
postman使用
創建工作空間:
創建請求: ?
?點擊"Save",保存當前請求
1.2 簡單參數?
簡單參數:在向服務器發起請求時,向服務器傳遞的是一些普通的請求數據。
SpringBoot方式?
?在Springboot的環境中,對原始的API進行了封裝,接收參數的形式更加簡單。 如果是簡單參數,參數名與形參變量名相同,定義同名的形參即可接收參數。
@RestController
public class RequestController {// http://localhost:8080/simpleParam?name=Tom&age=10// 第1個請求參數: name=Tom 參數名:name,參數值:Tom// 第2個請求參數: age=10 參數名:age , 參數值:10//springboot方式@RequestMapping("/simpleParam")public String simpleParam(String name , Integer age ){//形參名和請求參數名保持一致System.out.println(name+" : "+age);return "OK";}
}
postman測試( GET 請求):
?postman測試( POST請求 ):
結論:不論是GET請求還是POST請求,對于簡單參數來講,只要保證==請求參數名和Controller方法中的形參名保持一致==,就可以獲取到請求參數中的數據值。
請求參數名和形參名不同時:沒報錯,但無法接收到請求數據。
可使用Spring提供的@RequestParam注解完成映射
@RestController
public class RequestController {// http://localhost:8080/simpleParam?name=Tom&age=20// 請求參數名:name//springboot方式@RequestMapping("/simpleParam")public String simpleParam(@RequestParam("name") String username , Integer age ){System.out.println(username+" : "+age);return "OK";}
}
注意:@RequestParam中的required屬性默認為true(默認值也是true),代表該請求參數必須傳遞,如果不傳遞將報錯,可改為false,表示參數可選
1.3 實體參數
在使用簡單參數做為數據傳遞方式時,前端傳遞了多少個請求參數,后端controller方法中的形參就要書寫多少個。如果請求參數比較多,通過上述的方式一個參數一個參數的接收,會比較繁瑣。
此時,我們可以考慮將請求參數封裝到一個實體類對象中。 要想完成數據封裝,需要遵守如下規則:請求參數名與實體類的屬性名相同
簡單實體參數:
定義POJO實體類:
public class User {private String name;private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}
Controller方法:
@RestController
public class RequestController {//實體參數:簡單實體對象@RequestMapping("/simplePojo")public String simplePojo(User user){System.out.println(user);return "OK";}
}
復雜實體對象:
復雜實體對象指的是,在實體類中有一個或多個屬性,也是實體對象類型的。如下:
-
User類中有一個Address類型的屬性(Address是一個實體類)
復雜實體對象的封裝,需要遵守如下規則:
-
請求參數名與形參對象屬性名相同,按照對象層次結構關系即可接收嵌套實體類屬性參數。
定義POJO實體類:
-
Address實體類
public class Address {private String province;private String city;public String getProvince() {return province;}public void setProvince(String province) {this.province = province;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Overridepublic String toString() {return "Address{" +"province='" + province + '\'' +", city='" + city + '\'' +'}';}
}
- User實體類
public class User {private String name;private Integer age;private Address address; //地址對象public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", address=" + address +'}';}
}
Controller方法:
@RestController
public class RequestController {//實體參數:復雜實體對象@RequestMapping("/complexPojo")public String complexPojo(User user){System.out.println(user);return "OK";}
}
1.4 數組集合參數
數組集合參數的使用場景:在HTML的表單中,有一個表單項是支持多選的(復選框),可以提交選擇的多個值。
后端程序接收多個值的方式有兩種:
-
數組
-
集合
數組
數組參數:請求參數名與形參數組名稱相同且請求參數為多個,定義數組類型形參即可接收參數
?Controller方法:
@RestController
public class RequestController {//數組集合參數@RequestMapping("/arrayParam")public String arrayParam(String[] hobby){System.out.println(Arrays.toString(hobby));return "OK";}
}
Postman測試:
在前端請求時,有兩種傳遞形式:
方式一: xxxxxxxxxx?hobby=game&hobby=java
方式二:xxxxxxxxxxxxx?hobby=game,java
集合
集合參數:請求參數名與形參集合對象名相同且請求參數為多個,@RequestParam 綁定參數關系
默認情況下,請求中參數名相同的多個值,是封裝到數組。如果要封裝到集合,要使用@RequestParam綁定參數關系
?Controller方法:
@RestController
public class RequestController {//數組集合參數@RequestMapping("/listParam")public String listParam(@RequestParam List<String> hobby){System.out.println(hobby);return "OK";}
}
Postman測試:
方式一: xxxxxxxxxx?hobby=game&hobby=java
方式二:xxxxxxxxxxxxx?hobby=game,java
1.5 日期參數
在一些特殊的需求中,可能會涉及到日期類型數據的封裝。
為日期的格式多種多樣(如:2022-12-12 10:05:45 、2022/12/12 10:05:45),那么對于日期類型的參數在進行封裝的時候,需要通過@DateTimeFormat注解,以及其pattern屬性來設置日期的格式。
-
@DateTimeFormat注解的pattern屬性中指定了哪種日期格式,前端的日期參數就必須按照指定的格式傳遞。
-
后端controller方法中,需要使用Date類型或LocalDateTime類型,來封裝傳遞的參數。
Controller方法:
@RestController
public class RequestController {//日期時間參數@RequestMapping("/dateParam")public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){System.out.println(updateTime);return "OK";}
}
1.6 JSON參數?
JSON是開發中最常用的前后端數據交互方式
我們學習JSON格式參數,主要從以下兩個方面著手:
-
Postman在發送請求時,如何傳遞json格式的請求參數
-
在服務端的controller方法中,如何接收json格式的請求參數
Postman發送JSON格式數據:
服務端Controller方法接收JSON格式數據:
-
傳遞json格式的參數,在Controller中會使用實體類進行封裝。
-
封裝規則:JSON數據鍵名與形參對象屬性名相同,定義POJO類型形參即可接收參數。需要使用 @RequestBody標識。
-
@RequestBody注解:將JSON數據映射到形參的實體類對象中(JSON中的key和實體類中的屬性名保持一致)
實體類:Address
public class Address {private String province;private String city;//省略GET , SET 方法
}
實體類:User
public class User {private String name;private Integer age;private Address address;//省略GET , SET 方法
}
Controller方法:
@RestController
public class RequestController {//JSON參數@RequestMapping("/jsonParam")public String jsonParam(@RequestBody User user){System.out.println(user);return "OK";}
}
1.7 路徑參數
在現在的開發中,經常還會直接在請求的URL中傳遞參數。
路徑參數:
-
前端:通過請求URL直接傳遞參數
-
后端:使用{…}來標識該路徑參數,需要使用@PathVariable獲取路徑參數
?Controller方法:
@RestController
public class RequestController {//路徑參數@RequestMapping("/path/{id}")public String pathParam(@PathVariable Integer id){System.out.println(id);return "OK";}
}
傳遞多個路徑參數:
Controller方法:
@RestController
public class RequestController {//路徑參數@RequestMapping("/path/{id}/{name}")public String pathParam2(@PathVariable Integer id, @PathVariable String name){System.out.println(id+ " : " +name);return "OK";}
}
二、響應
前面我們學習過HTTL協議的交互方式:請求響應模式(有請求就有響應)
那么Controller程序呢,除了接收請求外,還可以進行響應。
2.1 @ResponseBody
在我們前面所編寫的controller方法中,都已經設置了響應數據。
controller方法中的return的結果,使用了@ResponseBody注解響應給瀏覽器。
@ResponseBody注解:
-
類型:方法注解、類注解
-
位置:書寫在Controller方法上或類上
-
作用:將方法返回值直接響應給瀏覽器
-
如果返回值類型是實體對象/集合,將會轉換為JSON格式后在響應給瀏覽器
-
但是在我們所書寫的Controller中,只在類上添加了@RestController注解、方法添加了@RequestMapping注解,并沒有使用@ResponseBody注解,怎么給瀏覽器響應呢?
@RestController
public class HelloController {@RequestMapping("/hello")public String hello(){System.out.println("Hello World ~");return "Hello World ~";}
}
原因:在類上添加的@RestController注解,是一個組合注解。
-
@RestController = @Controller + @ResponseBody
結論:在類上添加@RestController就相當于添加了@ResponseBody注解。
-
類上有@RestController注解或@ResponseBody注解時:表示當前類下所有的方法返回值做為響應數據
-
方法的返回值,如果是一個POJO對象或集合時,會先轉換為JSON格式,在響應給瀏覽器
-
2.2 統一響應結果
大家有沒有發現一個問題,我們在前面所編寫的這些Controller方法中,返回值各種各樣,沒有任何的規范。
?在真實的項目開發中,我們會定義一個統一的返回結果。方案如下:
前端:只需要按照統一格式的返回結果進行解析(僅一種解析方案),就可以拿到數據。
統一的返回結果使用類來描述,在這個結果中包含:
-
響應狀態碼:當前請求是成功,還是失敗
-
狀態碼信息:給頁面的提示信息
-
返回的數據:給前端響應的數據(字符串、對象、集合)
定義在一個實體類Result來包含以上信息。代碼如下:
public class Result {private Integer code;//響應碼,1 代表成功; 0 代表失敗private String msg; //響應碼 描述字符串private Object data; //返回的數據public Result() { }public Result(Integer code, String msg, Object data) {this.code = code;this.msg = msg;this.data = data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}//增刪改 成功響應(不需要給前端返回數據)public static Result success(){return new Result(1,"success",null);}//查詢 成功響應(把查詢結果做為返回數據響應給前端)public static Result success(Object data){return new Result(1,"success",data);}//失敗響應public static Result error(String msg){return new Result(0,msg,null);}
}
改造Controller:
@RestController
public class ResponseController { //響應統一格式的結果@RequestMapping("/hello")public Result hello(){System.out.println("Hello World ~");//return new Result(1,"success","Hello World ~");return Result.success("Hello World ~");}//響應統一格式的結果@RequestMapping("/getAddr")public Result getAddr(){Address addr = new Address();addr.setProvince("廣東");addr.setCity("深圳");return Result.success(addr);}//響應統一格式的結果@RequestMapping("/listAddr")public Result listAddr(){List<Address> list = new ArrayList<>();Address addr = new Address();addr.setProvince("廣東");addr.setCity("深圳");Address addr2 = new Address();addr2.setProvince("陜西");addr2.setCity("西安");list.add(addr);list.add(addr2);return Result.success(list);}
}
使用Postman測試:
三、分層解耦
3.1 三層架構
在我們進行程序設計以及程序開發時,盡可能讓每一個接口、類、方法的職責更單一些(單一職責原則)。
單一職責原則:一個類或一個方法,就只做一件事情,只管一塊功能。
這樣就可以讓類、接口、方法的復雜度更低,可讀性更強,擴展性更好,也更利用后期的維護。
我們之前開發的程序呢,并不滿足單一職責原則。下面我們來分析下下面的程序:
那其實我們上述案例的處理邏輯呢,從組成上看可以分為三個部分:
-
數據訪問:負責業務數據的維護操作,包括增、刪、改、查等操作。
-
邏輯處理:負責業務邏輯處理的代碼。
-
請求處理、響應數據:負責,接收頁面的請求,給頁面響應數據。
按照上述的三個組成部分,在我們項目開發中呢,可以將代碼分為三層:
-
Controller:控制層。接收前端發送的請求,對請求進行處理,并響應數據。
-
Service:業務邏輯層。處理具體的業務邏輯。
-
Dao:數據訪問層(Data Access Object),也稱為持久層。負責數據訪問操作,包括數據的增、刪、改、查。
基于三層架構的程序執行流程:
-
前端發起的請求,由Controller層接收(Controller響應數據給前端)
-
Controller層調用Service層來進行邏輯處理(Service層處理完后,把處理結果返回給Controller層)
-
Serivce層調用Dao層(邏輯處理過程中需要用到的一些數據要從Dao層獲取)
-
Dao層操作文件中的數據(Dao拿到的數據會返回給Service層)
思考:按照三層架構的思想,如何要對業務邏輯(Service層)進行變更,會影響到Controller層和Dao層嗎?
答案:不會影響。 (程序的擴展性、維護性變得更好了)
3.2 分層解耦
解耦:解除耦合。
耦合問題
首先需要了解軟件開發涉及到的兩個概念:內聚和耦合。
-
內聚:軟件中各個功能模塊內部的功能聯系。
-
耦合:衡量軟件中各個層/模塊之間的依賴、關聯的程度。
軟件設計原則:高內聚低耦合。
高內聚指的是:一個模塊中各個元素之間的聯系的緊密程度,如果各個元素(語句、程序段)之間的聯系程度越高,則內聚性越高,即 "高內聚"。
低耦合指的是:軟件中各個層、模塊之間的依賴關聯程序越低越好。
程序中高內聚的體現:
-
EmpServiceA類中只編寫了和員工相關的邏輯處理代碼
程序中耦合代碼的體現:
-
把業務類變為EmpServiceB時,需要修改controller層中的代碼
?高內聚、低耦合的目的是使程序模塊的可重用性、移植性大大增強。
解耦思路
之前我們在編寫代碼時,需要什么對象,就直接new一個就可以了。 這種做法呢,層與層之間代碼就耦合了,當service層的實現變了之后, 我們還需要修改controller層的代碼。
- ?首先不能在EmpController中使用new對象。代碼如下:
-
此時,就存在另一個問題了,不能new,就意味著沒有業務層對象(程序運行就報錯),怎么辦呢?
-
我們的解決思路是:
-
提供一個容器,容器中存儲一些對象(例:EmpService對象)
-
controller程序從容器中獲取EmpService類型的對象
-
-
我們想要實現上述解耦操作,就涉及到Spring中的兩個核心概念:
-
控制反轉: Inversion Of Control,簡稱IOC。對象的創建控制權由程序自身轉移到外部(容器),這種思想稱為控制反轉。
對象的創建權由程序員主動創建轉移到容器(由容器創建、管理對象)。這個容器稱為:IOC容器或Spring容器
-
依賴注入: Dependency Injection,簡稱DI。容器為應用程序提供運行時,所依賴的資源,稱之為依賴注入。
程序運行時需要某個資源,此時容器就為其提供這個資源。
例:EmpController程序運行時需要EmpService對象,Spring容器就為其提供并注入EmpService對象
IOC容器中創建、管理的對象,稱之為:bean對象
3.3 IOC&DI
上面我們引出了Spring中IOC和DI的基本概念,下面我們就來具體學習下IOC和DI的代碼實現。
IOC&DI入門
任務:完成Controller層、Service層、Dao層的代碼解耦
-
思路:
-
刪除Controller層、Service層中new對象的代碼
-
Service層及Dao層的實現類,交給IOC容器管理
-
為Controller及Service注入運行時依賴的對象
-
Controller程序中注入依賴的Service層對象
-
Service程序中注入依賴的Dao層對象
-
-
第1步:刪除Controller層、Service層中new對象的代碼
第2步:Service層及Dao層的實現類,交給IOC容器管理
-
使用Spring提供的注解:@Component ,就可以實現類交給IOC容器管理
第3步:為Controller及Service注入運行時依賴的對象
-
使用Spring提供的注解:@Autowired ,就可以實現程序運行時IOC容器自動注入需要的依賴對象
完整的三層代碼:
-
Controller層:
@RestController
public class EmpController {@Autowired //運行時,從IOC容器中獲取該類型對象,賦值給該變量private EmpService empService ;@RequestMapping("/listEmp")public Result list(){//1. 調用service, 獲取數據List<Emp> empList = empService.listEmp();//3. 響應數據return Result.success(empList);}
}
- Service層:
@Component //將當前對象交給IOC容器管理,成為IOC容器的bean
public class EmpServiceA implements EmpService {@Autowired //運行時,從IOC容器中獲取該類型對象,賦值給該變量private EmpDao empDao ;@Overridepublic List<Emp> listEmp() {//1. 調用dao, 獲取數據List<Emp> empList = empDao.listEmp();//2. 對數據進行轉換處理 - gender, jobempList.stream().forEach(emp -> {//處理 gender 1: 男, 2: 女String gender = emp.getGender();if("1".equals(gender)){emp.setGender("男");}else if("2".equals(gender)){emp.setGender("女");}//處理job - 1: 講師, 2: 班主任 , 3: 就業指導String job = emp.getJob();if("1".equals(job)){emp.setJob("講師");}else if("2".equals(job)){emp.setJob("班主任");}else if("3".equals(job)){emp.setJob("就業指導");}});return empList;}
}
- Dao層:
@Component //將當前對象交給IOC容器管理,成為IOC容器的bean
public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {//1. 加載并解析emp.xmlString file = this.getClass().getClassLoader().getResource("emp.xml").getFile();System.out.println(file);List<Emp> empList = XmlParserUtils.parse(file, Emp.class);return empList;}
}
IOC詳解
通過IOC和DI的入門程序呢,我們已經基本了解了IOC和DI的基礎操作。接下來呢,我們學習下IOC控制反轉和DI依賴注入的細節。
bean的聲明
前面我們提到IOC控制反轉,就是將對象的控制權交給Spring的IOC容器,由IOC容器創建及管理對象。IOC容器創建的對象稱為bean對象。
在之前的入門案例中,要把某個對象交給IOC容器管理,需要在類上添加一個注解:@Component
而Spring框架為了更好的標識web應用程序開發當中,bean對象到底歸屬于哪一層,又提供了@Component的衍生注解:
-
@Controller (標注在控制層類上)
-
@Service (標注在業務層類上)
-
@Repository (標注在數據訪問層類上)
修改入門案例代碼:
-
Controller層:
@RestController //@RestController = @Controller + @ResponseBody public class EmpController {@Autowired //運行時,從IOC容器中獲取該類型對象,賦值給該變量private EmpService empService ;@RequestMapping("/listEmp")public Result list(){//1. 調用service, 獲取數據List<Emp> empList = empService.listEmp();//3. 響應數據return Result.success(empList);} }
-
Service層:
@Service
public class EmpServiceA implements EmpService {@Autowired //運行時,從IOC容器中獲取該類型對象,賦值給該變量private EmpDao empDao ;@Overridepublic List<Emp> listEmp() {//1. 調用dao, 獲取數據List<Emp> empList = empDao.listEmp();//2. 對數據進行轉換處理 - gender, jobempList.stream().forEach(emp -> {//處理 gender 1: 男, 2: 女String gender = emp.getGender();if("1".equals(gender)){emp.setGender("男");}else if("2".equals(gender)){emp.setGender("女");}//處理job - 1: 講師, 2: 班主任 , 3: 就業指導String job = emp.getJob();if("1".equals(job)){emp.setJob("講師");}else if("2".equals(job)){emp.setJob("班主任");}else if("3".equals(job)){emp.setJob("就業指導");}});return empList;}
}
-
Dao層
@Repository
public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {//1. 加載并解析emp.xmlString file = this.getClass().getClassLoader().getResource("emp.xml").getFile();System.out.println(file);List<Emp> empList = XmlParserUtils.parse(file, Emp.class);return empList;}
}
要把某個對象交給IOC容器管理,需要在對應的類上加上如下注解之一:
注解 | 說明 | 位置 |
---|---|---|
@Controller | @Component的衍生注解 | 標注在控制器類上 |
@Service | @Component的衍生注解 | 標注在業務類上 |
@Repository | @Component的衍生注解 | 標注在數據訪問類上(由于與mybatis整合,用的少) |
@Component | 聲明bean的基礎注解 | 不屬于以上三類時,用此注解 |
在IOC容器中,每一個Bean都有一個屬于自己的名字,可以通過注解的value屬性指定bean的名字。如果沒有指定,默認為類名首字母小寫。
注意事項:
聲明bean的時候,可以通過value屬性指定bean的名字,如果沒有指定,默認為類名首字母小寫。
使用以上四個注解都可以聲明bean,但是在springboot集成web開發中,聲明控制器bean只能用@Controller。
組件掃描
問題:使用前面學習的四個注解聲明的bean,一定會生效嗎?
答案:不一定。(原因:bean想要生效,還需要被組件掃描)
下面我們通過修改項目工程的目錄結構,來測試bean對象是否生效:
運行程序后,報錯:
為什么沒有找到bean對象呢?
-
使用四大注解聲明的bean,要想生效,還需要被組件掃描注解@ComponentScan掃描
@ComponentScan注解雖然沒有顯式配置,但是實際上已經包含在了引導類聲明注解 @SpringBootApplication 中,==默認掃描的范圍是SpringBoot啟動類所在包及其子包==。
-
解決方案:手動添加@ComponentScan注解,指定要掃描的包 (==僅做了解,不推薦==)
推薦做法(如下圖):
-
將我們定義的controller,service,dao這些包呢,都放在引導類所在包com.itheima的子包下,這樣我們定義的bean就會被自動的掃描到
DI詳解
上一小節我們講解了控制反轉IOC的細節,接下來呢,我們學習依賴注解DI的細節。
依賴注入,是指IOC容器要為應用程序去提供運行時所依賴的資源,而資源指的就是對象。
在入門程序案例中,我們使用了@Autowired這個注解,完成了依賴注入的操作,而這個Autowired翻譯過來叫:自動裝配。
@Autowired注解,默認是按照類型進行自動裝配的(去IOC容器中找某個類型的對象,然后完成注入操作)
入門程序舉例:在EmpController運行的時候,就要到IOC容器當中去查找EmpService這個類型的對象,而我們的IOC容器中剛好有一個EmpService這個類型的對象,所以就找到了這個類型的對象完成注入操作。
那如果在IOC容器中,存在多個相同類型的bean對象,會出現什么情況呢?
-
程序運行會報錯
如何解決上述問題呢?Spring提供了以下幾種解決方案:
-
@Primary
-
@Qualifier
-
@Resource
使用@Primary注解:當存在多個相同類型的Bean注入時,加上@Primary注解,來確定默認的實現。
使用@Qualifier注解:指定當前要注入的bean對象。 在@Qualifier的value屬性中,指定注入的bean的名稱。
-
@Qualifier注解不能單獨使用,必須配合@Autowired使用
使用@Resource注解:是按照bean的名稱進行注入。通過name屬性指定要注入的bean的名稱。