Filter và interceptor
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Để đo mỗi request mất bao lâu, bạn không muốn thêm code đo vào từng method của controller. Việc chung cho mọi request như vậy nên làm ở một chỗ, trước khi request tới controller. Chỗ đó là filter chain.
Khái niệm
🚰 Filter chain: chuỗi các bước mà mọi request đi qua trước khi tới controller, rồi response đi ngược lại qua đúng các bước đó.
🧱 Filter: một bước trong filter chain, xử lý request, gọi bước tiếp theo, rồi xử lý response.
🪝 Interceptor: bước do Spring MVC chạy ngay trước và sau method của controller, nằm bên trong filter chain.
Ví dụ
// TimingFilter.java
package com.shop.api;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import org.springframework.stereotype.*;
import org.springframework.web.filter.*;
@Component
class TimingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long ms = System.currentTimeMillis() - start;
System.out.println(request.getRequestURI()
+ " " + response.getStatus()
+ " " + ms + "ms");
}
}@Componentbiến filter thành bean, Spring Boot tự thêm nó vào filter chain.OncePerRequestFilterlà class có sẵn của Spring, đảm bảo filter chạy đúng một lần cho mỗi request. Class con viết lại methoddoFilterInternal.- Code trước
chain.doFilter(...)chạy khi request đi vào. Code sau nó chạy khi response đi ra. chain.doFiltercó thể ném hai checked exception, nên method khai báothrows ServletException, IOException.- Gọi
GET /api/productsthì cửa sổ server in ra dòng như/api/products 200 2ms. - Nhiều filter thì đặt thứ tự bằng
@Order(số)phía trên class, số nhỏ chạy trước. Không ghi@Orderthì thứ tự giữa các filter không rõ ràng.
Thử ngay
Xoá TimingFilter.java, thêm hai filter sau:
// AFilter.java
package com.shop.api;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import org.springframework.core.annotation.*;
import org.springframework.stereotype.*;
import org.springframework.web.filter.*;
@Component
@Order(1)
class AFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
System.out.println("A vào");
chain.doFilter(request, response);
System.out.println("A ra");
}
}
// BFilter.java
package com.shop.api;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import org.springframework.core.annotation.*;
import org.springframework.stereotype.*;
import org.springframework.web.filter.*;
@Component
@Order(2)
class BFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
System.out.println("B vào");
chain.doFilter(request, response);
System.out.println("B ra");
}
}Chạy lại server, gọi curl -i http://localhost:5000/api/products rồi xem
cửa sổ đang chạy server.
Đoán trước khi chạy: bốn dòng in ra theo thứ tự nào?
Xem kết quả
A vào
B vào
B ra
A raRequest đi xuôi A rồi B, tới controller, rồi response đi ngược B rồi A.
Filter có @Order nhỏ hơn thì bọc ngoài filter có @Order lớn hơn.
Lỗi hay gặp
Quên chain.doFilter(...). Request dừng ở filter này, controller không
bao giờ chạy. Client nhận status 200 với body rỗng.
// SAI — không gọi doFilter, request không đi tiếp
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
System.out.println("Có request");
}Cách đúng là luôn gọi chain.doFilter(request, response), như
TimingFilter ở phần Ví dụ.
Sửa response sau khi đã gửi. Sau chain.doFilter(...), response thường
đã được gửi về client: Spring ghi body xong là đẩy luôn phần đầu đi. Header
thêm lúc đó bị bỏ qua mà không báo lỗi. Có loại response vẫn giữ được
header (tuỳ kiểu dữ liệu trả về, không phải độ dài body), nên lỗi dễ lọt qua.
// SAI — response đã gửi thì header thêm sau bị bỏ qua
chain.doFilter(request, response);
response.setHeader("X-Shop", "An");Muốn thêm header thì đặt code đó trước chain.doFilter(...).
// ĐÚNG — đổi header khi response chưa gửi
// ShopHeaderFilter.java
package com.shop.api;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import org.springframework.stereotype.*;
import org.springframework.web.filter.*;
@Component
class ShopHeaderFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
response.setHeader("X-Shop", "An");
chain.doFilter(request, response);
}
}Tóm tắt
- Filter chain là chuỗi filter mà mọi request đi qua.
- Filter xử lý request trước
chain.doFilter(...), xử lý response sau đó. @Orderquyết định thứ tự chạy: số nhỏ bọc ngoài.- Việc chung cho mọi request như đo thời gian, ghi log nên làm ở filter.
- Interceptor (
HandlerInterceptor) chạy sát controller hơn, dùng khi cần biết method nào của controller sẽ xử lý request.
Bình luận
Đang tải bình luận…