URL Routing Pipeline: HandlerMapping Và PathPattern
Khám phá luồng định tuyến URL trong DispatcherServlet: cơ chế HandlerMapping, phân giải HandlerAdapter và công cụ PathPatternParser trong Spring 6.
TL;DR: Khi request đến, DispatcherServlet tra cứu HandlerMapping để ánh xạ đường dẫn URL và phương thức HTTP thành HandlerExecutionChain. Tiếp đó, HandlerAdapter tiếp nhận chuỗi thực thi để phân giải tham số và gọi controller method tương ứng. Việc phân tách hai tầng này tuân thủ nguyên lý Open-Closed, cho phép mở rộng thêm các dạng handler mới mà không cần can thiệp vào mã nguồn servlet cốt lõi. Từ Spring 6, công cụ PathPatternParser được kích hoạt mặc định, giúp tăng tốc độ định tuyến gấp 6 đến 8 lần so với AntPathMatcher.
4 bước cốt lõi trong pipeline định tuyến URL gồm những gì?
Khi yêu cầu HTTP đi vào phương thức doDispatch() của DispatcherServlet, quá trình định tuyến diễn ra tuần tự qua bốn chặng:
DispatcherServletgửiHttpServletRequesttới danh sáchHandlerMappingqua lệnhgetHandler(request).HandlerMappingso khớp thông tin URL, HTTP method, header và trả vềHandlerExecutionChainchứa method đích cùng các interceptor.DispatcherServletduyệt qua danh sáchHandlerAdapterđể chọn adapter phù hợp thông qua hàmsupports(handler).HandlerAdapterphân giải các tham số đầu vào từ request, thực thi phương thức Java của controller và chuyển giao kết quả trả về.
Nhờ sự phân định ranh giới rõ ràng, tầng định tuyến URL hoàn toàn độc lập với cách thức gọi thực thi mã nguồn Java bên dưới.
Vì sao Spring tách biệt HandlerMapping và HandlerAdapter theo Open-Closed?
Trong hệ sinh thái Spring Framework, một handler có thể tồn tại dưới nhiều hình thái khác nhau:
- Lớp cài đặt interface cổ điển
ControllerhoặcHttpRequestHandler. - Phương thức chú thích
@RequestMappinghoặc@GetMapping. - Hàm tạm dừng (suspend function) trong Kotlin Coroutines.
Nếu DispatcherServlet trực tiếp thực thi handler, nó sẽ phải chứa hàng loạt câu lệnh if/else instanceof để kiểm tra từng loại đối tượng. Bất cứ khi nào ngôn ngữ hay framework bổ sung kiểu thực thi mới, mã nguồn của servlet trung tâm sẽ buộc phải viết lại, vi phạm nguyên lý Open-Closed Principle.

Thay vào đó, Spring áp dụng mẫu thiết kế Adapter Pattern: DispatcherServlet chỉ cần ủy thác cho HandlerAdapter. Muốn hỗ trợ kiểu handler mới, lập trình viên chỉ cần đăng ký thêm một bean adapter tương ứng vào Spring context mà không làm xáo trộn servlet cốt lõi.
Quá trình tra cứu bản đồ HandlerMapping diễn ra như thế nào?
Lớp RequestMappingHandlerMapping quét toàn bộ các bean trong container khi ứng dụng khởi động để xây dựng bảng định tuyến cố định:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}")
public OrderDto getOrder(@PathVariable Long id) {
return orderService.findById(id);
}
}
Bảng định tuyến ánh xạ từng tiêu chí:
- Đường dẫn URL mẫu (URL Pattern).
- Phương thức HTTP (GET, POST, PUT, DELETE).
- Kiểu dữ liệu chấp nhận (
consumes) và kiểu trả về (produces).
Khi nhận request, mapper tiến hành đối soát theo nguyên tắc ưu tiên cấu hình cụ thể nhất (most specific match). Nếu xuất hiện hai handler có cùng mức độ khớp trên cùng một URL, Spring Boot sẽ ném ngoại lệ IllegalStateException ngay lúc khởi động (fail-fast) để ngăn chặn hành vi định tuyến sai lệch ngầm.
PathPatternParser vượt trội hơn AntPathMatcher ở những điểm nào?
Từ phiên bản Spring 6 (Spring Boot 3), công cụ PathPatternParser thay thế hoàn toàn AntPathMatcher làm bộ phân tích URL mặc định.
| Tiêu chí so sánh | AntPathMatcher (Legacy) | PathPatternParser (Hiện đại) |
|---|---|---|
| Thời điểm phân tích cú pháp | Phân tích lại chuỗi trên từng request | Biên dịch thành cây PathPattern một lần lúc startup |
| Độ phức tạp thuật toán | Quét chuỗi $O(N)$ lặp đi lặp lại | Tra cứu theo độ sâu đường dẫn $O(d)$ |
| Hiệu năng xử lý | Mức cơ sở | Nhanh hơn gấp 6 đến 8 lần |
| Quy tắc dấu gạch chéo cuối | Coi /orders/ tương đương /orders | Phân biệt nghiêm ngặt hai URL khác nhau |

Cú pháp PathPattern hỗ trợ các biểu thức linh hoạt:
/orders/{id:\\d+}: Bắt biến đường dẫn với điều kiện số nguyên dương. Nếu client truyền chuỗi ký tự, hệ thống trả về 404 (không tìm thấy handler phù hợp)./files/{*path}: Bắt toàn bộ các phân đoạn còn lại của URL vào một biến duy nhất.
HandlerAdapter thực thi giải nén tham số và gọi controller ra sao?
Khi tiếp nhận HandlerMethod, bean RequestMappingHandlerAdapter phối hợp cùng chuỗi các bộ phân giải tham số độc lập:

- Phân giải danh sách đối số: Adapter duyệt qua danh sách hơn 30
HandlerMethodArgumentResolvercó sẵn để trích xuất dữ liệu từ URL, query string, header hoặc HTTP body. - Thực thi phương thức: Gọi phương thức Java qua Reflection với mảng đối số đã được ép kiểu.
- Xử lý giá trị trả về: Sử dụng
HandlerMethodReturnValueHandlerđể đóng gói dữ liệu và gọiHttpMessageConverterxuất chuỗi JSON ra response.
Bạn có thể tạo bộ phân giải tùy biến (ví dụ: tự động inject thông tin người dùng từ JWT Token) bằng cách cài đặt HandlerMethodArgumentResolver:
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
String token = webRequest.getHeader("Authorization");
return jwtService.extractUser(token);
}
}
Mã nguồn doDispatch hé lộ điều gì về cơ chế thực thi?
Dưới đây là cấu trúc giản lược của phương thức doDispatch() trong DispatcherServlet:
// Simplified execution flow inside DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = getHandler(request);
if (mappedHandler == null) {
noHandlerFound(request, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(request, response)) {
return; // Short-circuited by interceptor
}
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
mappedHandler.applyPostHandle(request, response, mv);
processDispatchResult(request, response, mappedHandler, mv, null);
}
Mỗi chặng đều được thiết kế để ngắt luồng sớm: nếu getHandler() không tìm ra ánh xạ, mã 404 được trả về ngay; nếu một interceptor trả về false trong preHandle(), controller method sẽ không bao giờ được gọi.
Những lưu ý vận hành và lỗi thường gặp trong production là gì?
Dưới đây là các lưu ý quan trọng khi triển khai routing trên hệ thống thực tế:
- Lỗi 404 do trailing slash khi nâng cấp Spring Boot 3: Client gửi request tới
/api/orders/nhưng backend chỉ khai báo/api/orders. Cần cập nhật client gửi đúng URL hoặc định nghĩa mảng mapping{ "/orders", "/orders/" }. - Tác vụ nặng chặn luồng trong Interceptor: Thực hiện truy vấn cơ sở dữ liệu đồng bộ trong
preHandle()sẽ trở thành điểm nghẽn hiệu năng lớn nhất của toàn bộ hệ thống. - Nhầm lẫn giữa mã lỗi 400 và 404 khi dùng Regex Path: Đặt ràng buộc
/orders/{id:\\d+}sẽ khiến request chứa ký tự chữ nhận về mã 404 thay vì 400 Bad Request.
Tóm tắt
- Luồng định tuyến chia làm hai phần:
HandlerMappingtìm kiếm handler,HandlerAdaptergọi thực thi. - Thiết kế Adapter Pattern giúp Spring MVC tuân thủ chặt chẽ nguyên lý Open-Closed.
PathPatternParsertrong Spring 6 biên dịch đường dẫn một lần lúc startup, mang lại hiệu năng vượt trội.- Hệ thống hỗ trợ mở rộng tham số linh hoạt thông qua các
HandlerMethodArgumentResolver. - Bật log mức
TRACEcho packageorg.springframework.web.servletlà phương pháp nhanh nhất để điều tra nguyên nhân request không đến controller.
Tự kiểm tra
- Q1Vì sao DispatcherServlet lại ủy thác việc thực thi controller cho HandlerAdapter thay vì tự gọi phương thức trực tiếp?
- Q2Một controller khai báo @GetMapping('/api/orders/{id:\d+}'). Nếu client gửi request GET /api/orders/abc, hệ thống trả về mã trạng thái nào?
- Q3Điểm cải tiến vượt trội nhất của PathPatternParser trong Spring 6 so với AntPathMatcher cũ là gì?
- Q4Khi nâng cấp từ Spring Boot 2 lên Spring Boot 3, tại sao request có dấu gạch chéo cuối (/orders/) lại có thể nhận mã lỗi 404?
- Q5Muốn tạo một annotation tùy biến như @CurrentUser để tự động inject thông tin UserPrincipal vào tham số của controller, lập trình viên cần cài đặt interface nào?
Bài tiếp theo: @RestController & Request Mapping
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
DispatcherServlet: Kiến Trúc Front Controller Spring MVC
Bài tiếp@RestController & Request Mapping: Xây Dựng REST API
Lộ trình Junior Java Backend Developer
Chặng 15/21 · REST API với Spring MVC — bài 3/21 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này