目錄
- 1. controller層是干什么的?
- 1.1 controller原理圖
- 1.2 controller層為什么要存在?
- 1.2.1 分離關注點
- 1.2.2 響應HTTP請求
- 1.2.3 數據處理與轉換
- 1.2.4 錯誤處理與狀態管理
- 1.2.5 流程控制
- 1.2.6 依賴注入與測試
- 1.3 controller層的優點
- 1.3.1 多端支持
- 1.3.2 安全性保障
- 1.3.3 性能優化
- 1.4 All in All
- 2. 研究Restful風格
- 2.1 什么是Restful風格?
- 2.1.1 功能
- 2.1.2 Restful風格與傳統風格的區別
- 2.1.2.1 請求方法
- 2.1.2.2 數據格式
- 2.1.2.3 狀態碼使用
- 2.1.2.4 異常處理
- 2.1.2.5 無狀態性
- 2.1.2.6 總結
- 3. controller層代碼
- 3.1 定義controller風格的注解
- 3.1.1 @RestController
- 3.1.2 @RequestMapping
- 3.1.3 @Slf4j
- 3.2 依賴注入注解
- 3.2.1 @Resource和@Autowired的相同點
- 3.2.2 @Resource和@Autowired的不同點
- 3.2.3 總結
- 3.3 依賴注入三種方式
- 3.3.1 字段注入(最簡單,有風險)
- 3.3.2 構造器注入(最安全,推薦)
- 3.3.3 Setter方法注入
- 4. controller層接口代碼詳解
- 4.0 HTTP常見請求方法合集
- 4.1 模糊查詢---接口
- 4.2 findAll---接口
- 4.3 根據id查詢---接口
- 4.4 增加---接口
- 4.5 刪除---接口
- 4.5 更新---接口
- 5 總結
1. controller層是干什么的?
1.1 controller原理圖
在Web應用程序中,控制器層(Controller)是模型-視圖-控制器(MVC)架構模式的一個關鍵組成部分。控制器負責接收用戶的輸入,調用業務邏輯層來處理這些輸入,并返回適當的響應給視圖層。
舉個例子吧:
平時登陸賬號的時候,你想要輸入賬號和密碼吧,然后點擊登陸的時候,用戶請求是最先傳到controller層的,然后controller層再傳到service層,為什么不直接傳給service層?例如,密碼長度不符合要求,或者賬號不是有效的電子郵件格式,那么沒有必要將請求轉發到服務層或數據庫層進行更深入的驗證。通過在控制器層進行初步驗證,可以減少不必要的服務層或數據庫層操作,從而提高系統性能和響應速度。另外,在控制器層進行格式驗證可以作為安全措施的一部分,防止惡意或格式錯誤的數據到達后端服務,減少潛在的安全風險。以前的黑客不就玩的sql注入嗎,當時的開發者都沒有意識到在數據庫查詢中直接使用用戶輸入的危險。
1.2 controller層為什么要存在?
在現代Web應用程序中,用戶請求通常不會直接傳遞給服務層(Service),而是通過控制器層(Controller)來處理,這種設計主要是基于以下幾個原因:
1.2.1 分離關注點
控制器層和業務邏輯層分離,使得每一層都只關注自己的職責。控制器層關注如何接收請求、驗證輸入和調用相應的業務邏輯。服務層則關注業務規則和業務流程的具體實現。
1.2.2 響應HTTP請求
在Spring框架中,Controller通過@Controller或@RestController注解標識,配合@RequestMapping等注解,能夠響應不同路徑的HTTP請求。如果沒有Controller,系統將無法正確解析和響應用戶請求,導致功能無法實現。
1.2.3 數據處理與轉換
Controller負責將接收到的數據(如用戶輸入的用戶名和密碼)傳遞給Service層進行驗證,并將驗證結果轉換為適合客戶端展示的格式。例如,使用@ResponseBody注解可以將Java對象自動轉換為JSON格式,方便前端處理。
1.2.4 錯誤處理與狀態管理
Controller還負責處理請求過程中可能出現的錯誤情況,并管理HTTP狀態碼。如果去掉Controller,這些錯誤處理和狀態管理將無處安放,可能導致用戶體驗不佳和安全風險
1.2.5 流程控制
Controller通過方法調用和返回值決定后續執行流程。例如,如果用戶登錄失敗,Controller可以決定重新顯示登錄頁面和錯誤信息;如果登錄成功,則跳轉到用戶的主頁。這種流程控制能力是細粒度處理請求不可或缺的。
1.2.6 依賴注入與測試
Spring框架中的Controller可以利用依賴注入(DI)集成其他組件(如Service、DAO等),并通過@Autowired注解自動裝配所需的依賴。這使得單元測試變得更加容易,同時也降低了組件間的耦合度。
1.3 controller層的優點
1.3.1 多端支持
在一個多端應用(如網頁、移動端)中,不同的前端可能發送類似的請求(如獲取用戶信息)。Controller可以統一處理這些請求,減少重復代碼。
1.3.2 安全性保障
Controller可以結合Spring Security等安全框架,提供認證、授權和跨站請求偽造(CSRF)防護等功能,增強應用的安全性。
1.3.3 性能優化
通過合理的Controller設計(如緩存、異步處理),可以提升系統的響應速度和并發處理能力。
1.4 All in All
Controller在Web應用中扮演著核心角色,負責處理請求、響應、數據轉換、錯誤處理和流程控制等關鍵任務。去掉Controller將導致系統無法正常響應用戶請求,破壞架構的模塊化和可維護性。因此,在實際開發中應充分重視Controller的設計和實現。
以下是控制器層的詳細闡述:
2. 研究Restful風格
2.1 什么是Restful風格?
Restful就是一個資源定位及資源操作的風格。不是標準也不是協議,只是一種風格。基于這個風格設計的軟件可以更簡潔,更有層次,更易于實現緩存等機制。
2.1.1 功能
- 資源:互聯網所有的事物都可以被抽象為資源
- 資源操作:使用POST、DELETE、PUT、GET,使用不同方法對資源進行操作。
- 分別對應 添加、 刪除、修改、查詢。
2.1.2 Restful風格與傳統風格的區別
2.1.2.1 請求方法
Restful風格:Restful風格的Controller全面使用HTTP的方法,包括GET、POST、PUT、DELETE等。每種方法都有具體的含義,例如GET用于獲取資源,POST用于創建新資源,PUT用于更新資源,DELETE用于刪除資源。
傳統風格:傳統風格的Controller通常只使用GET和POST請求方法。這意味著不管需要進行什么樣的操作,基本都依賴于這兩種方法,可能導致一個操作對應多個URL。
2.1.2.2 數據格式
Restful風格:Restful風格的接口通常使用JSON或XML作為數據交換格式,它們易于解析和生成,同時具有良好的可讀性。
傳統風格:通常使用xml。
2.1.2.3 狀態碼使用
Restful風格:Restful風格的Controller充分利用了HTTP狀態碼來表示不同的結果狀態,如200(成功)、201(已創建)、404(未找到)、500(服務器錯誤)等,這些狀態碼能明確告訴客戶端請求的處理結果。
傳統風格:傳統風格的Controller中,狀態碼的使用較為單一,通常只有200(成功)被頻繁使用,其他狀態碼較少出現。
2.1.2.4 異常處理
Restful風格:Restful風格的Controller采用統一的異常處理器,可以集中管理錯誤處理邏輯,并返回統一的錯誤信息和狀態碼。
傳統風格:傳統風格的異常處理可能分散在不同的Controller中,導致代碼重復和管理不便。
2.1.2.5 無狀態性
Restful風格:Restful風格的設計是無狀態的,每次請求都包含了所有必需的信息,這使得系統更加簡單且易于擴展。
傳統風格:傳統風格的Controller可能會依賴存儲在服務器端的會話(Session)狀態,這導致系統在水平擴展時面臨挑戰。
2.1.2.6 總結
Restful風格的Controller在URL設計、HTTP方法使用、狀態碼管理等方面具有明顯優勢,使得后端服務更加標準化和易于維護。在實際開發中,應當盡量采用Restful風格的設計原則來構建Web應用的后端服務。
3. controller層代碼
package com.goblin.BIbackend.controller;import com.goblin.BIbackend.common.BaseResponse;
import com.goblin.BIbackend.common.ListWithTotal;
import com.goblin.BIbackend.common.ResultUtils;
import com.goblin.BIbackend.model.entity.Complaints;
import com.goblin.BIbackend.service.ComplaintsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.util.List;@RestController
@RequestMapping("/complaints")
@Slf4j
public class ComplaintsController {@Resourceprivate ComplaintsService complaintsService;//模糊查詢@PostMapping("/search")public BaseResponse<ListWithTotal<Complaints>> search(@RequestBody Complaints keyword) {log.info("查詢投訴信箱:{}", keyword);List<Complaints> complaints = complaintsService.search1(keyword);int total = complaints.size();log.info("查詢投訴信箱總數:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}@GetMapping("/list")public BaseResponse<ListWithTotal<Complaints>> list() {List<Complaints> complaints = complaintsService.getList();int total = complaintsService.count1();log.info("查詢投訴信箱總數:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}@GetMapping("/select2")public BaseResponse<Complaints> select2(Long id) {log.info("查詢投訴信息:{}", id);Complaints complaints = complaintsService.select2(id);log.info("查詢投訴信息:{}", complaints);return ResultUtils.success(complaints);}@PostMapping("/insert2")public BaseResponse<Complaints> insert2(@RequestBody Complaints complaints) {Complaints result = complaintsService.insert2(complaints);return ResultUtils.success(result);}@DeleteMapping("/delete2")public BaseResponse<Long> delete2(Long id) {complaintsService.delete2(id);return ResultUtils.success(id);}@PutMapping("/update2")public BaseResponse<Complaints> update2(@RequestBody Complaints complaints) {complaintsService.update2(complaints);return ResultUtils.success(complaints);}}
這段代碼是一個Spring Boot應用程序中的ComplaintsController類,它使用Spring MVC框架來處理(Complaints)相關的HTTP請求。以下是對代碼的逐行解析:
3.1 定義controller風格的注解
@RestController
@RequestMapping("/complaints")
@Slf4j
這段代碼的主要作用是定義一個處理與"/complaints"相關的HTTP請求的控制器,并自動生成日志對象。
3.1.1 @RestController
這是Spring 4之后新加入的注解,原來在@Controller中返回json需要@ResponseBody來配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默認返回json格式。這個注解標識這個類是一個RestController,即控制器(Controller),主要用來處理由DispatcherServlet分發的請求,還可以對數據進行校驗等操作。
DispatcherServlet是Spring MVC框架中的一個核心組件,它作為前端控制器(Front Controller),負責將接收到的HTTP請求轉發到相應的處理器(Controller)。DispatcherServlet是Spring MVC中的中央調度器,它擴展了Servlet API,提供了一種機制來映射請求URL到對應的處理器方法。
3.1.2 @RequestMapping
這個注解用于映射Web請求,即訪問"/complaints"路徑時,會執行該類下的某個方法。它可以應用于類或方法上,當應用于類上時,表示類中的所有響應請求的方法都是以該地址作為父路徑。
3.1.3 @Slf4j
這是Lombok庫中的一個注解,用于自動生成日志對象。在使用時,只需在類上添加此注解,就可以自動生成名為log的SLF4J、Log4j、java.util.logging、Commons Logging、Logback以及java.lang.Appendable的實例。
3.2 依賴注入注解
@Resourceprivate ComplaintsService complaintsService;
先說說@Resource和@Autowired。@Resource和@Autowired都是Spring框架提供的依賴注入(DI)注解,用于實現自動裝配bean。
3.2.1 @Resource和@Autowired的相同點
1.依賴注入:兩者都用于將Spring容器中的bean自動注入到字段、構造函數或setter方法中。
2.減少代碼:通過使用這兩個注解,可以減少樣板代碼,即不需要手動編寫代碼來從容器中獲取bean。
3.支持類型注入:它們都支持通過類型來自動裝配,意味著Spring會查找容器中相應類型的bean來注入。
3.2.2 @Resource和@Autowired的不同點
1.來源:@Autowired是Spring自己的注解,屬于org.springframework.beans.factory.annotation包。
@Resource是Java EE的注解,適用于更廣泛的Java應用,不僅限于Spring框架。
2.注入方式:@Autowired:默認按類型(byType)注入,需要時可以結合@Qualifier指定具體的bean名稱。
@Resource:默認按名稱(byName)注入,也可通過配置type屬性改為按類型注入。
3.參數配置:@Autowired:只有一個required參數,表示是否需要這個依賴。
@Resource:包含多個參數,最重要的是name和type,靈活性更高。
4.使用場景:@Autowired更常用于Spring應用程序中,特別是在使用Spring框架的注解配置時。
@Resource可以用于那些需要兼容Java EE注解的場合,或者在某些特定情況下,開發者可能更傾向于使用Java EE的注解風格。
5.應用建議:@Autowired:更適合Spring環境,特別是當需要更靈活的依賴注入時(例如構造器注入)。
@Resource:更適合與Spring解耦的情境,或者在EE環境中使用。
在Spring配置中,如果同時使用了@Autowired和@Resource,Spring會首先尊重@Autowired的配置。
7.異常處理:@Autowired在找不到bean時會拋出NoSuchBeanDefinitionException。
@Resource在找不到bean時可能會拋出ResourceException。
3.2.3 總結
@Autowired和@Resource都可以用來實現依賴注入,但它們在自動裝配的默認行為、來源、使用場景和異常處理等方面存在差異。在Spring應用程序中,推薦使用@Autowired,因為它提供了更好的Spring集成和更靈活的配置選項。
3.3 依賴注入三種方式
依賴注入是一種設計模式,用來實現對象之間的依賴關系。傳統的面向對象編程中,類與類之間的依賴關系是通過硬編碼來實現的,而依賴注入則將這種依賴關系交給外部容器來處理,從而實現了解耦。
在Spring中依賴注入有三大類:字段注入、構造器注入、Setter方法注入。
3.3.1 字段注入(最簡單,有風險)
字段注入是通過在類的字段上使用注解來直接注入依賴對象的一種方式,雖然簡單直接,但并不推薦使用,因為它破壞了類的封裝性。
字段注入會引起以下的問題:
- 對象的外部可見性
- 可能導致循環依賴
- 無法設置注入的對象為final,也無法注入靜態變量
什么是字段注入?舉例如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class MyClass {// 使用@Autowired注解進行字段注入@Autowiredprivate MyService myService;public void doSomething() {myService.performAction();}
}
字段注入非常的簡便,通過以上代碼我們可以輕松的使用MyService類。
MyService myService= new MyService();
myService.performAction();
這樣執行結果為空指針異常,這就是字段注入的第一個問題:對象的外部可見性。
public class TestA(){@Autowiredprivate TestB testB;}
public class TestB(){@Autowiredprivate TestA testA;}
上面兩段代碼卡bug,這段代碼在idea中不會報任何錯誤,但是啟動項目時會發現報錯,大致意思是:創建Bean失敗,原因是當前Bean已經作為循環引用的一部分注入到了其他Bean中。
這就是字段注入的第二個問題:可能導致循環依賴
字段注入還有第三個問題:無法設置注入的對象為final,也無法注入靜態變量,原因是變量必須在類實例化進行初始化。
3.3.2 構造器注入(最安全,推薦)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class MyClass {private final MyService myService;// 使用@Autowired注解進行構造器注入@Autowiredpublic MyClass(MyService myService) {this.myService = myService;}public void doSomething() {myService.performAction();}
}
在這個例子中,MyClass的構造函數接收一個MyService類型的參數,并通過@Autowired注解告訴Spring框架自動注入一個合適的MyService實例。這樣,當創建MyClass的實例時,Spring會自動調用帶有@Autowired注解的構造函數,并將相應的依賴項傳遞給它。
構造器注入通過在類的構造函數上使用注解來自動裝配依賴對象。與字段注入相比,構造器注入有以下優點:
1.強制依賴關系:構造器注入要求必須提供所有必需的依賴項,否則將無法創建類的實例。這有助于確保類在使用之前已經正確地初始化了其依賴項。
2.不可變性:由于構造器注入是在構造函數中完成的,一旦對象被創建,其依賴項就無法更改。這有助于保持對象的不變性,并減少潛在的錯誤和副作用。
3.可測試性:構造器注入使得單元測試更加容易,因為可以在測試時輕松地提供模擬或存根依賴項。
3.3.3 Setter方法注入
Setter方法注入是依賴注入的一種方式,它**通過在目標類中定義一個或多個setter方法來接收依賴項的實例。**這種方式允許將依賴項的創建和管理從使用對象的類中分離出來,從而實現解耦和易于維護的代碼。
public class MyClass {private MyDependency myDependency;// Setter method for dependency injectionpublic void setMyDependency(MyDependency myDependency) {this.myDependency = myDependency;}public void doSomething() {// Use the injected dependencymyDependency.performAction();}
}
在這個例子中,MyClass 有一個 myDependency 屬性,它需要被注入一個 MyDependency類型的實例。通過定義一個名為 setMyDependency的setter方法,我們可以在外部容器(如Spring)中配置并注入所需的依賴項。
當Spring容器啟動時,它會掃描所有的bean定義,并根據配置文件或其他配置信息自動調用相應的setter方法,將依賴項注入到目標對象中。這樣,我們就可以在不修改目標類的代碼的情況下,靈活地更改依賴項的實現或配置。
需要注意的是,盡管setter方法注入是一種常見的依賴注入方式,但它并不是唯一的選擇。其他依賴注入方式,如構造器注入、字段注入和接口注入等,也可以用來滿足不同的需求和場景。
4. controller層接口代碼詳解
4.0 HTTP常見請求方法合集
在RESTful API設計中,通常使用GET、POST、PUT、PATCH和DELETE方法來操作資源。這些方法的選擇反映了操作的語義,有助于保持API的直觀性和一致性。例如,使用GET來請求數據,使用POST來創建新資源,使用PUT來更新現有資源,使用DELETE來刪除資源。
- GET:請求獲取指定的資源。
- POST:向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會導致新的資源的建立和/或已有資源的修改。
- PUT:從客戶端向服務器傳送的數據取代指定的文檔的內容。
- DELETE:請求服務器刪除指定的內容。
- PATCH:對資源進行部分修改。
4.1 模糊查詢—接口
@PostMapping("/search")public BaseResponse<ListWithTotal<Complaints>> search(@RequestBody Complaints keyword) {log.info("查詢投訴信箱:{}", keyword);List<Complaints> complaints = complaintsService.search1(keyword);int total = complaints.size();log.info("查詢投訴信箱總數:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}
模糊查詢:客戶端返回一個Complaints 類對象到controller層,用POST接口。
在Spring框架中,@PostMapping(“/search”)是一個注解,用于將HTTP的POST請求映射到特定的處理方法。這里的"/search"是URL路徑的一部分,表示當客戶端向服務器發送一個POST請求到"/search"路徑時,該請求將被路由到帶有@PostMapping(“/search”)注解的方法進行處理。
具體來說,@PostMapping(“/search”)中的"search"是一個字符串字面量,它定義了處理POST請求的URL路徑。在這個例子中,當客戶端發送一個POST請求到"/search"路徑時,服務器會調用帶有@PostMapping(“/search”)注解的方法來處理這個請求。這個方法可以接收和處理來自客戶端的數據,并返回相應的響應。
BaseResponse是一個通用的響應類,用于封裝API接口的返回結果。它包含一些常見的屬性,如code、message和data。BaseResponse表示返回的數據類型是一個包含投訴的列表和總數的對象。
@RequestBody Complaints keyword是一個Spring框架中的注解,用于將HTTP請求體中的數據綁定到方法參數上。在上面的代碼中,Complaints是一個Java類,表示投訴信息的數據結構。keyword 是這個類的一個實例,用于接收客戶端發送的投訴信息。
當客戶端發送一個POST請求到服務器的"/search"路徑時,請求體中的數據會被解析并轉換為Complaints類的實例。然后,這個實例會被傳遞給search方法作為參數keyword。
ResultUtils.success(list)
是一個方法調用,它是一個工具類中的一個靜態方法。這個方法的作用是創建一個成功的響應對象,并將傳入的參數list
作為數據返回給客戶端。
在上面的代碼中,list
是一個包含投訴信息的列表,而ResultUtils.success(list)
方法會創建一個BaseResponse
類型的對象,并設置狀態碼為成功(例如200),同時將list
作為響應的數據部分。這樣,當客戶端接收到這個響應時,它將能夠獲取到包含投訴信息的數據。
ResultUtils.success()`方法封裝一些常見的響應結構,如狀態碼、消息和數據等,以便在應用程序中統一處理響應結果。
4.2 findAll—接口
@GetMapping("/list")public BaseResponse<ListWithTotal<Complaints>> list() {List<Complaints> complaints = complaintsService.getList();int total = complaintsService.count1();log.info("查詢投訴信箱總數:{}", total);ListWithTotal<Complaints> list = new ListWithTotal<>(complaints, total);return ResultUtils.success(list);}
findAll:請求獲取所有的Complaints對象,用GET接口。
4.3 根據id查詢—接口
@GetMapping("/select2")public BaseResponse<Complaints> select2(Long id) {log.info("查詢投訴信息:{}", id);Complaints complaints = complaintsService.select2(id);log.info("查詢投訴信息:{}", complaints);return ResultUtils.success(complaints);}
4.4 增加—接口
@PostMapping("/insert2")public BaseResponse<Complaints> insert2(@RequestBody Complaints complaints) {Complaints result = complaintsService.insert2(complaints);return ResultUtils.success(result);}
增加:將一個Complaints 對象傳到controller層,用POST接口。
4.5 刪除—接口
@DeleteMapping("/delete2")public BaseResponse<Long> delete2(Long id) {complaintsService.delete2(id);return ResultUtils.success(id);}
刪除:傳id給controller層,請求刪除指定的Complaints對象,用DELETE接口。
4.5 更新—接口
@PutMapping("/update2")public BaseResponse<Complaints> update2(@RequestBody Complaints complaints) {complaintsService.update2(complaints);return ResultUtils.success(complaints);}
更新:將一個Complaints 對象傳到controller層取代原有Complaints ,從客戶端向服務器傳送的數據取代指定的文檔的內容,用PUT接口。
5 總結
文章首先講了controller層的重要性和優點,然后講了restful風格和傳統風格,接著對代碼進行分析:分析了依賴注入的三種方法,最后是對接口的解析,不可謂不深刻。看完這篇文章應該能知道什么是controller層。代碼誰都會寫,真正的區別在于對代碼底層的理解。