Spring Boot ‐ Spring Web MVC - CCH0124/spring-sandbox GitHub Wiki
Spring Web model-view-controller (MVC) 框架是圍繞 DispatcherServlet
設計,它將請求分派給處理程序,具有可配置的處理程序映射、view 解析以及對上傳文件的支援等。可用於開發靈活且鬆散耦合的 Web 應用程式。 MVC 模式導致分離應用程式的不同方面(輸入邏輯、業務邏輯和 UI 邏輯),同時在這些元素之間提供鬆散耦合。
- Model 封裝了應用程式數據,通常它們由 POJO 組成
- View 負責呈現模型數據,通常它會產生客戶端瀏覽器可以解釋的 HTML。
- Controller 負責處理使用者請求並建立適當的模型並將其傳遞給 View 進行渲染
From https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/mvc.html
DispatcherServlet
Spring 的 DispatcherServlet
負責將 HttpRequest
正確協調到正確的處理程序。
基本上,DispatcherServlet
處理傳入的 HttpRequest
,將請求委派出去,並根據在 Spring 應用中實現的 HandlerAdapter
介面處理該請求。這些介面在 Spring 應用中已被實現,並伴隨著指定處理程序、控制器端點和響應對象的註釋。
- 在關鍵字 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 下,與 DispatcherServlet 關聯的 WebApplicationContext 被搜索並提供給處理過程中的所有元素。
- DispatcherServlet 使用 getHandler() 方法查找為其配置的所有 HandlerAdapter 接口實現——每個找到並配置的實現通過 handle() 方法在處理過程中處理請求。
- LocaleResolver 可選綁定到請求,以便在處理過程中的各個元素解析語言環境。
- ThemeResolver 可選綁定到請求,以便元素(例如視圖)確定使用哪個主題。
- 如果指定了 MultipartResolver,則會檢查請求中的 MultipartFiles 找到的所有文件都會包裝在 MultipartHttpServletRequest 中以進行進一步處理。
- 在 WebApplicationContext 中聲明的 HandlerExceptionResolver 實現會捕獲在請求處理過程中拋出的異常。
- 用戶端發送 HTTP 請求至 Spring MVC 應用程式
DispatcherServlet
(Front Controller)接收並捕獲請求。並將請求委託給Handler Mapping
。DispatcherServlet
的一項關鍵責任是查找HandlerMapping
。HandlerMapping
負責根據請求 URL、請求方法或其他參數來確定應該由哪個控制器(handler)來處理傳入的請求。當 HandlerMapping 將請求映射到特定的控制器之後,它會返回這個控制器(habdler)的訊息給DispatcherServlet
。接著,DispatcherServlet
會接收這個控制器的 Bean ID,然後獲取相應的控制器對象並調用該對象的方法,從而將控制權移交給控制器。- Handler class / Controller class 負責創建 command class 物件並執行請求封裝。即,它接收 form data 並將其儲存(也稱為請求封裝)到 command class 物件中。 command class 實際上是一個 Java Bean,其物件保存接收到的 form data。
- Handler class 執行自己的業務邏輯或與 Service 或 DAO 類別交互,透過處理請求取得輸出。
- Handler class 將結果傳回給
DispatcherServlet
並調用ViewResolver
。這檢視對於生成適當的回應格式,例如 HTML、JSON 或其他內容類型。以application/json
類型來說 Spring Boot 利用 HttpMessageConverters 中 MappingJackson2HttpMessageConverter 將返回的物件轉換成 JSON 格式。 - DispatcherServlet 會向客戶端發送回應。表示請求處理過程完成,客戶端將接收後端生成的內容,無論是渲染的網頁還是 JSON 數據,具體取決於控制器執行的操作。
HandlerAdapter
介面在 DispatcherServlet
請求處理工作流程的各個階段都扮演著重要角色。HandlerAdapter
被許多介面實作更簡化了控制器的使用。
以下是 HandlerAdapter
實作關係:
以下是 HandlerAdapter
的工作流程:
- 加入鏈中 HandlerExecutionChain:首先,每個 HandlerAdapter 的實作類別都會從
DispatcherServlet
的getHandler()
方法中被加入到HandlerExecutionChain
中。 - 逐一處理請求 handle(): 然後,隨著處理鏈的執行,每個
HandlerAdapter
實作類別的handle()
方法都會處理HttpServletRequest
物件。
SimpleControllerHandlerAdapter
允許在沒有 @Controller
註解的情況下明確實作控制器。
RequestMappingHandlerAdapter
支援使用 @RequestMapping
註解進行註解的方法。這裡重點關注 @Controller
註釋。@RequestMapping
註解設定處理程序在其關聯的 WebApplicationContext
中可用的特定端點。
@RestController
@RequestMapping("/api")
@Tag(name = "Tutorial", description = "Tutorial management APIs")
public class TutorialController {
@Autowired TutorialService tutorialService;
@Operation(
summary = "Retrieve all Tutorial by title.",
description = "Get all Tutorial object by specifying title.",
tags = {"tutorials", "get"})
@ApiResponses({
@ApiResponse(
responseCode = "200",
content = {
@Content(
schema = @Schema(implementation = TutorialResponsePagingDto.class),
mediaType = "application/json")
}),
@ApiResponse(
responseCode = "500",
description = "System Error.",
content = {@Content(schema = @Schema())})
})
@GetMapping("/tutorials")
public ResponseEntity<TutorialResponsePagingDto> getAllTutorials(
@Parameter(description = "Search by title.") @RequestParam(required = false, name = "title")
final String title,
@Parameter(description = "Page number, starting from 0", required = true)
@RequestParam(defaultValue = "0", name = "page")
int page,
@Parameter(description = "Number of items per page", required = true)
@RequestParam(defaultValue = "3", name = "size")
int size) {
var allTutorials = tutorialService.getAllTutorials(title, page, size);
return ResponseEntity.ok(allTutorials);
}
}
由 @RequestMapping 註解指定的路徑透過 HandlerMapping 介面在內部進行管理。URL 結構自然相對於 DispatcherServlet
本身,由 servlet 映射決定。
DispatcherServlet
的核心職責是將傳入的 HttpRequest
分派到使用 @Controller
或 @RestController
註解指定的正確處理程序。
常用註解
- @RequestMapping
基於 @Controller 類別中請求處理方法。用於應設於哪個 URL 路徑,其還包含以下設定
- method: HTTP methods
- params: 根據 HTTP 參數的存不存在或值過濾請求
- headers: 根據 HTTP 表頭參數的存不存在或值過濾請求
- consumes: 此 HTTP method 可以在 HTTP 請求載體中使用哪些 Media Type
- produces: 此 HTTP method 可以在 HTTP 回應載體中產生哪些 Media Type
@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
和 @PatchMapping
是 @RequestMapping
的不同變體,分別對應不同的 HTTP Method。
- @RequestBody
它將 HTTP 請求載體對應到一個物件。過程反序列化是自動的,並且取決於請求的內容類型。
- @PathVariable
會將參數綁定到 URI 模板變數,可以使用 @RequestMapping
註解設計 URI 模板,並使用 @PathVariable
將方法參數綁定到模板部分之一。
- @RequestParam
用來存取 HTTP 請求參數(HTTP Parameters)。當 Spring 在請求中發現該參數沒有值或空值時,可以使用 @RequestParam
來指定預設注入的值,可透果設定 defaultValue
參數設置。
對於存取其他 HTTP 請求部分,像是 cookie 和 header。可以分別使用註解 @CookieValue
和 @RequestHeader
來存取它們。
- @ResponseBody
如果使用 @ResponseBody
Spring 會將該方法的最後結果視為 HTTP 回應載體。會跳過 view resolve 過程。
- @ExceptionHandler
可以聲明自訂錯誤處理方法,當 Controller 下的請求處理過程拋出任何指定的異常時,Spring 呼叫此方法。
- @ResponseStatus
可以指定所需的回應 HTTP 狀態。也可以將它與 @ExceptionHandler
一起使用。
- @Controller
可以使用 @Controller
定義一個 Spring MVC 控制器。
- @RestController
@RestController
結合了 @Controller
和 @ResponseBody
。
- @CrossOrigin
請求處理程序啟用跨域通訊。標記在一個類,它將應用於其中的所有請求處理方法。
Interceptors 攔截器
HandlerMapping
的目的是將處理(Handle)方法對應到 URL。這樣,DispatcherServlet
才能夠在處理請求時呼叫它。攔截器攔截請求並處理它們,有助於避免重複的處理器流程,例如日誌和授權檢查。
Handler Interceptor
Spring 攔截器是一個擴展 HandlerInterceptorAdapter
或實作 HandlerInterceptor
介面的類別。
HandlerInterceptor 主要三個方法:
prehandle()
在處理器(Controller)方法執行之前被調用。通常用於進行請求預處理,例如身份驗證、授權、日誌記錄等。如果該方法返回 false,則請求將被攔截,不會繼續執行後續的處理器方法。postHandle()
在處理器方法執行之後,但渲染 view 之前被調用。通常用於處理模型數據、處理異常等。afterCompletion()
在整個請求完成後含渲染 VIEW。通常用於清理資源,例如關閉資料庫連接、釋放其他資源等。
preHandle()
實作方法,該方法傳回一個布林值,false 則不做後續處理。
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
postHandle()
實作方法,攔截器在處理請求後但在生成 VIEW 之前立即呼叫此方法。
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
afterCompletion()
,此方法允許在請求處理完成後執行自訂邏輯。
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
Custom Interceptor
@Component
public class LoggerInterceptor implements HandlerInterceptor {
private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("[preHandle][" + request + "]" + "[" + request.getMethod() + "]" + request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("[postHandle][" + request + "]");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex != null)
ex.printStackTrace();
log.info("[afterCompletion][" + request + "][exception: " + ex + "]");
log.info("Request and Response is completed");
}
}
實作完之後需要進行配置讓其生效。
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggerInterceptor());
}
}
啟用此配置後,該定義攔截器會處於活動狀態,並且應用程式中的所有請求都將被正確捕獲。如果配置了多個 Spring 攔截器,則 preHandle()
方法將按配置順序執行,而 postHandle()
和 afterCompletion()
方法將依相反順序呼叫。