OncePerRequestFilter 是Spring框架提供的一個過濾器類別,用於實現一次請求僅執行一次過濾的功能。這個過濾器可以在需要保證過濾器只執行一次的情況下使用。
使用 OncePerRequestFilter 需要繼承這個類別,並實現其中的 doFilterInternal() 方法。在 doFilterInternal() 方法中可以定義需要執行的過濾邏輯。
以下是一個簡單的 OncePerRequestFilter 範例:
```java
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Order(1)
@Component
public class MyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 在這裡實現過濾邏輯
// 呼叫 filterChain.doFilter() 繼續執行過濾器鏈
filterChain.doFilter(request, response);
}
}
```
如果希望在某些條件下不執行過濾器,可以在 MyFilter 中重寫 shouldNotFilter() 方法,該方法返回 true 則表示不執行過濾器:
```java
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// 在這裡判斷是否不執行過濾器的條件
return true;
}
```
通過使用 OncePerRequestFilter,可以確保過濾器在一次請求中只執行一次,並且還可以根據不同的條件來控制過濾器的執行。
### [Spring 攔截處理器](https://www.tpisoftware.com/tpu/articleDetails/1929)
Filter
```java
@WebFilter(filterName = "RequestFilter", urlPatterns = "/book")
public class RequestFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(RequestFilter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
LOGGER.info("Request do filter ...");
chain.doFilter(request, response);
}
}
```
Interceptor
```java
@Component
public class DemoInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LOGGER.info("Interceptor do preHandle ...");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
LOGGER.info("Interceptor do postHandle ...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LOGGER.info("Interceptor do afterCompletion ...");
}
}
```
### AOP
```java
@Aspect
@Order(1)
@Component
public class ControllerAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAspect.class);
@Around(value = "within(@org.springframework.web.bind.annotation.RestController *)")
public Object controllerAround(ProceedingJoinPoint jp) throws Throwable {
LOGGER.info("Aop do controller around before method ...");
Object result = jp.proceed();
LOGGER.info("Aop do controller around after method ...");
return result;
}
@Pointcut("execution(* com.example.api.*.*(..))") // 替換成你的API包名和方法
public void apiMethods() {}
@Before("apiMethods()")
public void beforeApiMethod(JoinPoint joinPoint) {
}
}
```
@Around 注解用於定義環繞增強方法,即在目標方法執行前後都執行的方法。使用 @Around 可以完全控制目標方法的執行,可以在目標方法執行之前進行一些前置處理,並在執行之後進行後置處理。在 @Around 注解的方法內部,你可以通過調用 ProceedingJoinPoint.proceed() 方法來執行目標方法。
@Pointcut 注解應用於一個方法上,該方法的內容可以是一個切點表達式,也可以是一個命名的切點,以便在其他切面中引用。使用 @Pointcut 可以將切點的定義集中在一個地方,以便在不同的切面中重複使用。
@Before 注解用於定義在目標方法執行之前應該執行的增強方法。當目標方法被調用之前,@Before 指定的方法將被執行。這種增強方法通常用於進行一些前置處理操作。
* execution(* com.example.service.*.*(..)): 匹配 com.example.service 包中的所有類的任意方法。
* execution(public * com.example.service.UserService.*(..)): 匹配 com.example.service.UserService 類中的所有公開方法。
* execution(* save*(..)): 匹配方法名以 "save" 開頭的所有方法。
* within(com.example.repository.*): 匹配 com.example.repository 包中的所有方法。
* args(java.lang.String): 匹配接受一個 String 參數的方法。
* @annotation(org.springframework.web.bind.annotation.GetMapping): 匹配標註有 @GetMapping 註解的方法。
```java
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cachedBody;
// 1. 在建構子就先把串流讀取到記憶體(byte 陣列)
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
}
// 2. 重寫 getInputStream,每次呼叫都回傳全新的、從頭讀取的快取串流
@Override
public ServletInputStream getInputStream() {
return new CachedServletInputStream(this.cachedBody);
}
// ==========================================
// 3. 【關鍵】這就是你需要自己實作的自訂類別
// ==========================================
private static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream cachedBodyInputStream;
public CachedServletInputStream(byte[] cachedBody) {
// 將 byte 陣列傳入 ByteArrayInputStream
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
@Override
public int read() throws IOException {
// 實際讀取的是記憶體中的快取
return cachedBodyInputStream.read();
}
@Override
public boolean isFinished() {
return cachedBodyInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
// 通常快取讀取不需要異步監聽,保持空白或丟出 UnsupportedOperationException 即可
}
}
}
```
---
### 1. 核心生命週期總覽圖
當一個 HTTP 請求從瀏覽器發出,到最後回應,它在後端會依序走過這三個層級:
```text
[瀏覽器 HTTP 請求]
│
┌──────▼────────────────────────────────────────┐
│ Filter 層
│ ➔ 進入:doFilter()
│ ➔ 🔴 發生例外:由 Tomcat 容器或 /error 機制處理
└──────┬────────────────────────────────────────┘
│
┌──────▼────────────────────────────────────────┐
│ Interceptor 層
│ ➔ 執行:preHandle()
│ ➔ 🔴 發生例外:@ExceptionHandler 區塊
└──────┬────────────────────────────────────────┘
│ (若 preHandle 回傳 true)
┌──────▼────────────────────────────────────────┐
│ HandlerAdapter 層
│ ➔ 進入:handle()
│ ➔ 執行:ArgumentResolver 參數解析與綁定
│ ➔ 🔴 發生例外:@ExceptionHandler 區塊
└──────┬────────────────────────────────────────┘
│
┌──────▼────────────────────────────────────────┐
│ Controller 層
│ ➔ 執行:你的業務函式 (@GetMapping/@PostMapping)
│ ➔ 🔴 發生例外:@ExceptionHandler 區塊
└──────┬────────────────────────────────────────┘
│ (業務函式執行完畢,原路回傳)
┌──────▼────────────────────────────────────────┐
│ Interceptor 層
│ ➔ 執行:postHandle()
│ ➔ 🔴 發生例外:由容器處理
└──────┬────────────────────────────────────────┘
│
┌──────▼────────────────────────────────────────┐
│ Interceptor 層
│ ➔ 執行:afterCompletion()
│ ➔ 🔴 發生例外:由容器處理
└──────┬────────────────────────────────────────┘
│
┌──────▼────────────────────────────────────────┐
│ Filter 層
│ ➔ 離開:doFilter() 的 finally 區塊
│ ➔ 🔴 發生例外:由容器處理
└──────┬────────────────────────────────────────┘
▼
[HTTP 回應傳回前端]
```
---