SpringBoot中請求處理組件全面對比與選型指南
Spring Boot 提供了多種組件來處理請求的不同階段:過濾器、攔截器、AOP、監(jiān)聽器、ControllerAdvice、參數(shù)/返回值處理器。它們功能上有重疊但各有定位,初學(xué)者容易混淆。本文系統(tǒng)講解每種組件的原理、用法,并從執(zhí)行順序、適用場景、功能交叉點等維度進(jìn)行橫向?qū)Ρ龋瑤椭诓煌瑘鼍跋伦龀稣_選型。
一、整體架構(gòu)與執(zhí)行流程
1.1 請求處理全鏈路
一個 HTTP 請求到達(dá) Spring Boot 應(yīng)用后,會依次經(jīng)過以下組件:
HTTP 請求
|
v
┌─────────────────────────────────────────────────────────┐
│ Servlet 容器(Tomcat) │
│ ↓ │
│ Filter(過濾器)─── 可攔截請求和響應(yīng) │
│ ↓ │
│ DispatcherServlet │
│ ├──→ HandlerInterceptor.preHandle()(攔截器-前置) │
│ ├──→ HandlerMethodArgumentResolver(參數(shù)處理器) │
│ ├──→ Controller 方法(被 AOP 環(huán)繞) │
│ │ └──→ AOP @Before / @Around │
│ ├──→ HandlerMethodReturnValueHandler(返回值處理器) │
│ ├──→ HandlerInterceptor.postHandle()(攔截器-后置) │
│ └──→ HandlerInterceptor.afterCompletion()(攔截器-完成)│
│ │
│ 異常分支: │
│ 任何階段拋出異常 → @RestControllerAdvice 捕獲 │
│ │
│ 事件分支: │
│ ServletRequestListener / ApplicationEvent 貫穿全程 │
└─────────────────────────────────────────────────────────┘
|
v
HTTP 響應(yīng)
1.2 六大組件定位
| 組件 | 所屬層級 | 核心職責(zé) | 一句話定位 |
|---|---|---|---|
| Filter | Servlet 容器 | 請求/響應(yīng)的過濾與包裝 | 最外層的守門員 |
| Interceptor | Spring MVC | Controller 前后攔截 | Controller 的貼身保鏢 |
| AOP | Spring 容器 | 方法級別的橫切邏輯 | 任意 Bean 方法的攔截器 |
| Listener | Servlet / Spring | 事件監(jiān)聽與回調(diào) | 全局事件的觀察者 |
| ControllerAdvice | Spring MVC | 全局異常處理與數(shù)據(jù)綁定 | Controller 的后勤保障 |
| ArgumentResolver | Spring MVC | 自定義參數(shù)解析 | 請求參數(shù) → Controller 入?yún)?/td> |
| ReturnValueHandler | Spring MVC | 自定義返回值處理 | Controller 返回值 → 響應(yīng) |
二、Filter(過濾器)
2.1 原理
Filter 是 Servlet 規(guī)范的一部分,運(yùn)行在 DispatcherServlet 之前和之后,可以攔截所有 HTTP 請求(包括靜態(tài)資源)。
請求 → FilterChain
Filter1.doFilter()
Filter2.doFilter()
DispatcherServlet
Filter2(響應(yīng))
Filter1(響應(yīng))
響應(yīng)
2.2 用法
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 請求日志過濾器
*/
@Component
@Order(1)
public class RequestLogFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
long start = System.currentTimeMillis();
System.out.println("[Filter] 請求進(jìn)入: " + req.getMethod() + " " + req.getRequestURI());
// 放行到下一個 Filter 或 DispatcherServlet
chain.doFilter(request, response);
long cost = System.currentTimeMillis() - start;
System.out.println("[Filter] 請求完成: " + req.getRequestURI() + " 耗時 " + cost + "ms 狀態(tài)碼 " + resp.getStatus());
}
}
也可以通過 FilterRegistrationBean 注冊:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<RequestLogFilter> requestLogFilter() {
FilterRegistrationBean<RequestLogFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new RequestLogFilter());
bean.addUrlPatterns("/*");
bean.setOrder(1);
bean.setName("requestLogFilter");
return bean;
}
}
2.3 特點
| 特點 | 說明 |
|---|---|
| 攔截范圍 | 所有請求(包括靜態(tài)資源、非 Controller 請求) |
| 能否訪問 Spring Bean | 可以(通過注入) |
| 能否獲取 Controller 信息 | 不能(不知道哪個 Controller 處理) |
| 能否修改請求/響應(yīng)體 | 可以(通過包裝 HttpServletRequestWrapper) |
| 執(zhí)行時機(jī) | DispatcherServlet 之前和之后 |
三、Interceptor(攔截器)
3.1 原理
Interceptor 是 Spring MVC 提供的機(jī)制,運(yùn)行在 DispatcherServlet 內(nèi)部,可以感知到 HandlerExecutionChain(即哪個 Controller 方法將要執(zhí)行)。
DispatcherServlet
├── preHandle() → Controller 執(zhí)行前
├── postHandle() → Controller 返回后,視圖渲染前
└── afterCompletion() → 視圖渲染完成后(即使有異常也會執(zhí)行)
3.2 用法
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 登錄校驗攔截器
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || token.isEmpty()) {
response.setStatus(401);
response.getWriter().write("未登錄");
return false; // 中斷請求
}
System.out.println("[Interceptor] preHandle: token=" + token);
return true; // 放行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("[Interceptor] postHandle: Controller 執(zhí)行完畢");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
System.out.println("[Interceptor] afterCompletion: 請求結(jié)束, ex=" + ex);
}
}
注冊攔截器并配置攔截路徑:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**") // 攔截路徑
.excludePathPatterns("/api/login"); // 排除路徑
}
}
3.3 特點
| 特點 | 說明 |
|---|---|
| 攔截范圍 | Controller 請求(不攔截靜態(tài)資源) |
| 能否獲取 Controller 信息 | 可以(handler 參數(shù)包含 HandlerMethod) |
| 能否中斷請求 | 可以(preHandle 返回 false) |
preHandle | Controller 執(zhí)行前,返回 false 中斷 |
postHandle | Controller 正常返回后執(zhí)行(拋異常則不執(zhí)行) |
afterCompletion | 最終執(zhí)行(無論是否異常) |
注意:postHandle 在 Controller 拋出異常時不會執(zhí)行,但 afterCompletion 一定會執(zhí)行。
四、AOP(面向切面編程)
4.1 原理
AOP 是 Spring 容器級別的機(jī)制,基于動態(tài)代理(JDK 或 CGLIB),可以攔截 Spring Bean 的方法調(diào)用。它不局限于 Controller,可以作用于任何 Bean。
調(diào)用 target.method()
↓
代理對象
├── @Before → 方法執(zhí)行前
├── @AfterReturning → 方法正常返回后
├── @AfterThrowing → 方法拋出異常后
├── @After → 方法結(jié)束后(無論成功失?。?br /> └── @Around → 環(huán)繞(最強(qiáng)大,可控制是否執(zhí)行)
4.2 用法
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 接口耗時統(tǒng)計切面
*/
@Slf4j
@Aspect
@Component
public class TimeLogAspect {
/**
* 定義切入點:controller 包下所有 public 方法
*/
@Pointcut("execution(public * com.example.demo.controller.*.*(..))")
public void controllerPointcut() {
}
/**
* 環(huán)繞通知:統(tǒng)計方法執(zhí)行耗時
*/
@Around("controllerPointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
String methodName = pjp.getSignature().toShortString();
log.info("[AOP] 方法開始: {}", methodName);
try {
Object result = pjp.proceed(); // 執(zhí)行目標(biāo)方法
long cost = System.currentTimeMillis() - start;
log.info("[AOP] 方法結(jié)束: {}, 耗時 {}ms", methodName, cost);
return result;
} catch (Throwable e) {
long cost = System.currentTimeMillis() - start;
log.error("[AOP] 方法異常: {}, 耗時 {}ms", methodName, cost, e);
throw e;
}
}
/**
* 前置通知
*/
@Before("controllerPointcut()")
public void before() {
// 在 @Around 的 proceed() 之前執(zhí)行
}
}
4.3 特點
| 特點 | 說明 |
|---|---|
| 攔截范圍 | 任何 Spring Bean 的方法(不限于 Controller) |
| 能否獲取方法參數(shù) | 可以(JoinPoint.getArgs()) |
| 能否修改參數(shù) | 可以(@Around 中修改 args 后 proceed) |
| 能否修改返回值 | 可以(@Around 中替換返回值) |
| 能否中斷執(zhí)行 | 可以(@Around 中不調(diào)用 proceed) |
五、Listener(監(jiān)聽器)
5.1 原理
監(jiān)聽器是觀察者模式的實現(xiàn),用于監(jiān)聽容器或應(yīng)用中發(fā)生的事件。Spring Boot 中有兩種監(jiān)聽器:
| 類型 | 接口 | 事件 |
|---|---|---|
| Servlet 監(jiān)聽器 | ServletRequestListener 等 | 請求創(chuàng)建/銷毀 |
| Spring 事件監(jiān)聽器 | ApplicationListener | 應(yīng)用啟動、刷新、自定義事件 |
5.2 Servlet 請求監(jiān)聽器
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
import org.springframework.stereotype.Component;
/**
* Servlet 請求監(jiān)聽器
*/
@Component
public class RequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("[Listener] 請求創(chuàng)建: " + sre.getServletRequest());
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("[Listener] 請求銷毀");
}
}
5.3 Spring 事件監(jiān)聽器
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* Spring 事件監(jiān)聽器
*/
@Component
public class SpringEventListener {
/**
* 監(jiān)聽?wèi)?yīng)用啟動完成事件
*/
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
System.out.println("[Listener] 應(yīng)用啟動完成");
}
/**
* 監(jiān)聽自定義事件
*/
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
System.out.println("[Listener] 收到訂單創(chuàng)建事件: " + event.getOrderId());
}
}
自定義事件:
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* 訂單創(chuàng)建事件
*/
@Getter
public class OrderCreatedEvent extends ApplicationEvent {
private final String orderId;
public OrderCreatedEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
}
發(fā)布事件:
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public OrderService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void createOrder(String orderId) {
// 業(yè)務(wù)邏輯...
System.out.println("訂單創(chuàng)建: " + orderId);
// 發(fā)布事件
eventPublisher.publishEvent(new OrderCreatedEvent(this, orderId));
}
}
與 Filter/Interceptor 的區(qū)別:Listener 是被動的事件回調(diào),不能攔截或中斷請求流程。
六、ControllerAdvice(全局異常與數(shù)據(jù)綁定)
6.1 原理
@RestControllerAdvice / @ControllerAdvice 是 Spring MVC 提供的全局增強(qiáng)機(jī)制,可以處理 Controller 層的異常、全局?jǐn)?shù)據(jù)綁定、全局?jǐn)?shù)據(jù)預(yù)處理。
6.2 全局異常處理
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 全局異常處理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理業(yè)務(wù)異常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public Map<String, Object> handleBusiness(BusinessException e) {
log.warn("業(yè)務(wù)異常: {}", e.getMessage());
Map<String, Object> result = new HashMap<>();
result.put("code", e.getCode());
result.put("message", e.getMessage());
return result;
}
/**
* 處理參數(shù)校驗異常
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleIllegalArg(IllegalArgumentException e) {
log.warn("參數(shù)異常: {}", e.getMessage());
Map<String, Object> result = new HashMap<>();
result.put("code", "400");
result.put("message", e.getMessage());
return result;
}
/**
* 兜底:處理所有未捕獲異常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, Object> handleException(Exception e) {
log.error("系統(tǒng)異常", e);
Map<String, Object> result = new HashMap<>();
result.put("code", "500");
result.put("message", "系統(tǒng)繁忙");
return result;
}
}
6.3 全局?jǐn)?shù)據(jù)綁定
@RestControllerAdvice
public class GlobalDataBinder {
@InitBinder
public void initBinder(WebDataBinder binder) {
// 注冊自定義編輯器
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}
6.4 全局?jǐn)?shù)據(jù)預(yù)處理
@RestControllerAdvice
public class GlobalRequestBodyAdvice {
@ModelAttribute
public void addCommonAttributes(Model model) {
model.addAttribute("serverTime", System.currentTimeMillis());
}
}
關(guān)鍵:@ExceptionHandler 只能捕獲 Controller 層拋出的異常,不能捕獲 Filter 層的異常。
七、HandlerMethodArgumentResolver(參數(shù)處理器)
7.1 原理
參數(shù)處理器負(fù)責(zé)將 HTTP 請求中的數(shù)據(jù)解析為 Controller 方法的入?yún)?。Spring MVC 內(nèi)置了多個處理器:
| 內(nèi)置處理器 | 處理的注解 |
|---|---|
RequestParamMethodArgumentResolver | @RequestParam |
PathVariableMethodArgumentResolver | @PathVariable |
RequestBodyMethodArgumentResolver | @RequestBody |
RequestHeaderMethodArgumentResolver | @RequestHeader |
ModelAttributeMethodProcessor | @ModelAttribute |
當(dāng)內(nèi)置處理器不滿足需求時,可以自定義。
7.2 自定義參數(shù)處理器示例
場景:從 Token 中解析當(dāng)前登錄用戶,直接注入 Controller 方法。
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import jakarta.servlet.http.HttpServletRequest;
/**
* 自定義參數(shù)處理器:解析 @CurrentUser 注解
*/
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CurrentUser.class)
&& parameter.getParameterType().equals(UserInfo.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String token = request.getHeader("Authorization");
// 從 token 解析用戶信息(實際項目中通常從 JWT 或緩存中獲?。?
UserInfo user = parseToken(token);
return user;
}
private UserInfo parseToken(String token) {
UserInfo user = new UserInfo();
user.setId(1001L);
user.setName("張三");
return user;
}
}
自定義注解:
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentUser {
}
用戶信息類:
import lombok.Data;
@Data
public class UserInfo {
private Long id;
private String name;
}
注冊參數(shù)處理器:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final CurrentUserArgumentResolver currentUserResolver;
public WebMvcConfig(CurrentUserArgumentResolver currentUserResolver) {
this.currentUserResolver = currentUserResolver;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserResolver);
}
}
在 Controller 中使用:
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/profile")
public Map<String, Object> getProfile(@CurrentUser UserInfo user) {
Map<String, Object> result = new HashMap<>();
result.put("id", user.getId());
result.put("name", user.getName());
return result;
}
}
八、HandlerMethodReturnValueHandler(返回值處理器)
8.1 原理
返回值處理器負(fù)責(zé)將 Controller 方法的返回值轉(zhuǎn)換為 HTTP 響應(yīng)。內(nèi)置處理器包括:
| 內(nèi)置處理器 | 處理場景 |
|---|---|
RequestResponseBodyMethodProcessor | @ResponseBody / @RestController |
ModelAndViewMethodReturnValueHandler | 返回 ModelAndView |
ViewMethodReturnValueHandler | 返回 String(視圖名) |
HttpEntityMethodProcessor | 返回 ResponseEntity |
8.2 自定義返回值處理器示例
場景:Controller 方法返回實體對象,自動包裝為統(tǒng)一響應(yīng)格式 Result<T>。
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* 統(tǒng)一返回值包裝
* <p>
* 通過 ResponseBodyAdvice 實現(xiàn),它本質(zhì)上是返回值處理器的增強(qiáng)鉤子。
*/
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
// 如果已經(jīng)是 Result 類型,則不再包裝
return !returnType.getParameterType().equals(Result.class);
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("message", "success");
result.put("data", body);
return result;
}
}
統(tǒng)一響應(yīng)類:
import lombok.Data;
@Data
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> r = new Result<>();
r.setCode(0);
r.setMessage("success");
r.setData(data);
return r;
}
public static <T> Result<T> fail(String message) {
Result<T> r = new Result<>();
r.setCode(500);
r.setMessage(message);
return r;
}
}
注意:ResponseBodyAdvice 不是完整的 ReturnValueHandler,而是在 RequestResponseBodyMethodProcessor 內(nèi)部的增強(qiáng)鉤子。完整自定義需要實現(xiàn) HandlerMethodReturnValueHandler 接口并注冊,但實際項目中 ResponseBodyAdvice 已足夠。
九、橫向?qū)Ρ?/h2>
9.1 執(zhí)行順序
HTTP 請求到達(dá)
│
├── 1. Listener.requestInitialized() ← Servlet 請求監(jiān)聽器
│
├── 2. Filter.doFilter() [請求階段] ← 過濾器
│ (Filter1 → Filter2 → ...)
│
├── 3. DispatcherServlet
│ │
│ ├── 3.1 Interceptor.preHandle() ← 攔截器-前置
│ │
│ ├── 3.2 ArgumentResolver ← 參數(shù)處理器
│ │
│ ├── 3.3 AOP @Around [前半段] ← AOP(如果有)
│ │ ├── AOP @Before
│ │ ├── Controller 方法執(zhí)行
│ │ └── AOP @AfterReturning / @AfterThrowing
│ │ └── AOP @After
│ │ └── AOP @Around [后半段]
│ │
│ ├── 3.4 ReturnValueHandler ← 返回值處理器
│ │ └── ResponseBodyAdvice
│ │
│ ├── 3.5 Interceptor.postHandle() ← 攔截器-后置
│ │
│ └── 3.6 Interceptor.afterCompletion() ← 攔截器-完成
│
├── 4. Filter.doFilter() [響應(yīng)階段]
│ (... → Filter2 → Filter1)
│
└── 5. Listener.requestDestroyed() ← Servlet 請求監(jiān)聽器
異常分支:
Controller 拋出異常
→ AOP @AfterThrowing
→ Interceptor.afterCompletion()
→ @RestControllerAdvice @ExceptionHandler
Filter 拋出異常
→ 不會進(jìn)入 ControllerAdvice(需在 Filter 中自行處理)
核心記憶:Filter → Interceptor.preHandle → ArgumentResolver → AOP → Controller → ReturnValueHandler → Interceptor.postHandle → Interceptor.afterCompletion → Filter
9.2 功能對比矩陣
| 能力 | Filter | Interceptor | AOP | Listener | ControllerAdvice | ArgumentResolver | ReturnValueHandler |
|---|---|---|---|---|---|---|---|
| 攔截所有請求 | ? | ? 僅 Controller | ? 僅 Bean 方法 | ? | ? | ? | ? |
| 攔截靜態(tài)資源 | ? | ? | ? | ? | ? | ? | ? |
| 獲取 Controller 方法信息 | ? | ? | ? | ? | ? | ? | ? |
| 獲取方法參數(shù)值 | ? | ? | ? | ? | ? | ?(可修改) | ? |
| 修改方法參數(shù) | ? | ? | ? | ? | ? | ? | ? |
| 修改返回值 | ? | ? | ? | ? | ? | ? | ? |
| 中斷請求 | ? | ? | ? | ? | ? | ? | ? |
| 處理異常 | ?(自行處理) | ? | ? | ? | ? | ? | ? |
| 感知事務(wù) | ? | ? | ? | ? | ? | ? | ? |
| 作用范圍 | Servlet 容器 | Spring MVC | Spring 容器 | Servlet/Spring | Spring MVC | Spring MVC | Spring MVC |
9.3 適用場景對比
| 場景 | 推薦組件 | 理由 |
|---|---|---|
| 請求日志記錄 | Filter | 能記錄所有請求(含靜態(tài)資源),且在最早階段攔截 |
| 登錄鑒權(quán) | Interceptor | 只需攔截 Controller 請求,可獲取 Handler 信息,可排除特定路徑 |
| 權(quán)限校驗 | Interceptor | 同上,且可獲取 Controller 注解 |
| 接口耗時統(tǒng)計 | AOP | 可精確到方法級別,可統(tǒng)計 Service 層耗時 |
| 參數(shù)校驗/簽名 | AOP 或 Interceptor | AOP 更靈活(可作用于 Service 層),Interceptor 更簡單 |
| 統(tǒng)一返回值包裝 | ResponseBodyAdvice | 不侵入 Controller 代碼,自動包裝所有返回值 |
| 自定義參數(shù)注入 | ArgumentResolver | 如 @CurrentUser 注入登錄用戶 |
| 全局異常處理 | ControllerAdvice | 統(tǒng)一處理 Controller 異常,避免 try-catch 散落各處 |
| 跨域處理 | Filter 或 CorsFilter | 在最早階段處理 CORS 預(yù)檢請求 |
| 請求體加密/解密 | Filter + Wrapper | 需要在請求到達(dá) Controller 前修改請求體 |
| 事件通知 | Listener | 異步解耦,如訂單創(chuàng)建后發(fā)通知 |
| 接口限流 | Filter 或 Interceptor | Filter 更早攔截減少資源消耗 |
9.4 功能交叉點分析
Filter vs Interceptor — 都能攔截請求
相同點:
- 都能在請求前后執(zhí)行邏輯
- 都能中斷請求
- 都能獲取 HttpServletRequest / HttpServletResponse
不同點:
- Filter 攔截所有請求(含靜態(tài)資源),Interceptor 只攔截 Controller
- Filter 不感知 Controller 信息,Interceptor 可以獲取 HandlerMethod
- Filter 在 DispatcherServlet 外,Interceptor 在內(nèi)
Interceptor 的 excludePathPatterns 配置更靈活
選型建議:
- 需要攔截所有請求 → Filter(如跨域、字符編碼)
- 只需攔截 Controller → Interceptor(如登錄校驗、權(quán)限控制)
Interceptor vs AOP — 都能攔截 Controller 方法
相同點:
- 都能在 Controller 方法前后執(zhí)行邏輯
- 都能獲取方法簽名和參數(shù)
- 都能中斷執(zhí)行
不同點:
- Interceptor 只能攔截 Controller,AOP 能攔截任何 Bean
- Interceptor 有 postHandle(視圖渲染前),AOP 沒有
- AOP 更靈活(@Around 可控制是否執(zhí)行、可修改參數(shù)和返回值)
- Interceptor 更簡單(實現(xiàn)接口即可,無需切點表達(dá)式)
- AOP 基于動態(tài)代理,同類內(nèi)部方法調(diào)用不走代理(自調(diào)用問題)
選型建議:
- 只需攔截 Controller、邏輯簡單 → Interceptor
- 需要攔截 Service 層、需要修改參數(shù)或返回值 → AOP
ControllerAdvice vs Interceptor — 都能處理異常
相同點:都在 Spring MVC 層面工作
不同點:
- ControllerAdvice 專門處理異常,Interceptor 的 afterCompletion 只能感知異常但不能處理
- ControllerAdvice 可以針對不同異常類型返回不同響應(yīng)
- Interceptor 無法修改異常的走向,異常會繼續(xù)傳播
選型建議:異常處理 → 始終用 ControllerAdvice
Filter vs Interceptor — 都能做日志記錄
Filter 做日志:
- 能記錄所有請求(含 404、靜態(tài)資源)
- 能獲取最終響應(yīng)狀態(tài)碼
- 不能獲取 Controller 類名和方法名
Interceptor 做日志:
- 能獲取 Controller 類名和方法名
- 可以排除不需要記錄的路徑
- 不能記錄非 Controller 請求(如 404)
選型建議:
- 訪問日志(access log)→ Filter
- 接口調(diào)用日志(需要方法名)→ Interceptor 或 AOP
十、實戰(zhàn):組合使用示例
一個典型的 Spring Boot 項目通常同時使用多種組件:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* Web MVC 統(tǒng)一配置
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final LoginInterceptor loginInterceptor;
private final CurrentUserArgumentResolver currentUserResolver;
public WebMvcConfig(LoginInterceptor loginInterceptor,
CurrentUserArgumentResolver currentUserResolver) {
this.loginInterceptor = loginInterceptor;
this.currentUserResolver = currentUserResolver;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login", "/api/register");
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(currentUserResolver);
}
}
各組件分工:
| 組件 | 職責(zé) |
|---|---|
| Filter | 請求日志、跨域處理、字符編碼 |
| Interceptor | 登錄校驗、權(quán)限控制 |
| AOP | 接口耗時統(tǒng)計、操作日志 |
| ControllerAdvice | 全局異常處理 |
| ArgumentResolver | @CurrentUser 注入登錄用戶 |
| ResponseBodyAdvice | 統(tǒng)一返回值包裝 |
| Listener | 應(yīng)用啟動初始化、訂單事件通知 |
Controller 代碼保持極簡:
@RestController
@RequestMapping("/api/order")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping("/create")
public OrderVO createOrder(@CurrentUser UserInfo user,
@RequestBody @Valid OrderCreateRequest request) {
return orderService.createOrder(user.getId(), request);
}
}
執(zhí)行流程:
- Filter → 記錄請求日志
- Interceptor → 校驗登錄 token
- ArgumentResolver → 解析 token,注入 UserInfo
- AOP @Around → 開始計時
- Controller → 執(zhí)行業(yè)務(wù)邏輯
- AOP @Around → 結(jié)束計時,記錄日志
- ResponseBodyAdvice → 包裝返回值為 {code, message, data}
- Interceptor.afterCompletion → 清理資源
- Filter → 記錄響應(yīng)日志
十一、常見誤區(qū)與最佳實踐
11.1 常見誤區(qū)
| 誤區(qū) | 正解 |
|---|---|
| Filter 中拋異常會被 ControllerAdvice 捕獲 | ? 不會。Filter 在 DispatcherServlet 之外,需要自行 try-catch |
| Interceptor 的 postHandle 在異常時也會執(zhí)行 | ? Controller 拋異常時 postHandle 不執(zhí)行,只有 afterCompletion 執(zhí)行 |
| AOP 可以攔截同類內(nèi)部方法調(diào)用 | ? this.method() 不走代理,只有外部調(diào)用才走代理 |
| Listener 可以中斷請求 | ? Listener 是事件回調(diào),不能中斷流程 |
| ResponseBodyAdvice 對所有返回值生效 | ? 只對 @ResponseBody / @RestController 的方法生效 |
| 多個 Interceptor 的 postHandle 按注冊順序執(zhí)行 | ? postHandle 和 afterCompletion 按注冊的逆序執(zhí)行 |
11.2 多個攔截器的執(zhí)行順序
攔截器注冊順序:A → B → C
preHandle 執(zhí)行順序:A → B → C(正序)
postHandle 執(zhí)行順序:C → B → A(逆序)
afterCompletion 執(zhí)行順序:C → B → A(逆序)
如果 B 的 preHandle 返回 false:
A.preHandle ?
B.preHandle ?(返回 false)
C.preHandle 不執(zhí)行
Controller 不執(zhí)行
postHandle 都不執(zhí)行
afterCompletion:A.afterCompletion ?(已 preHandle 成功的會執(zhí)行)
C.afterCompletion 不執(zhí)行
11.3 最佳實踐
- 職責(zé)單一:每個組件只做一件事,不要在 Filter 中做業(yè)務(wù)邏輯
- 異常處理分層:Filter 異常自行處理,Controller 異常交給 ControllerAdvice
- 避免循環(huán)依賴:Filter 中注入 Bean 時注意 Bean 的初始化時機(jī)
- 攔截器路徑配置:用
excludePathPatterns排除登錄、注冊等公開接口 - AOP 自調(diào)用問題:同類方法內(nèi)部調(diào)用不走代理,需要通過
AopContext.currentProxy()或拆分到不同類 - 參數(shù)處理器優(yōu)先級:自定義 ArgumentResolver 添加到列表末尾,避免覆蓋內(nèi)置處理器
- ResponseBodyAdvice 判斷類型:在
supports()中排除已是 Result 類型的返回值,避免重復(fù)包裝
十二、總結(jié)
12.1 選型決策樹
需要在請求最外層攔截?
├── 是 → Filter
│ (跨域、編碼、訪問日志)
└── 否 → 需要攔截 Controller 方法?
├── 是 → 需要修改參數(shù)或返回值?
│ ├── 是 → AOP
│ │ (耗時統(tǒng)計、參數(shù)處理)
│ └── 否 → 需要獲取 Handler 信息?
│ ├── 是 → Interceptor
│ │ (登錄校驗、權(quán)限控制)
│ └── 否 → AOP 或 Interceptor 均可
└── 否 → 需要處理異常?
├── 是 → ControllerAdvice
│ (全局異常處理)
└── 否 → 需要自定義參數(shù)注入?
├── 是 → ArgumentResolver
│ (@CurrentUser)
└── 否 → 需要統(tǒng)一包裝返回值?
├── 是 → ResponseBodyAdvice
└── 否 → 需要監(jiān)聽事件?
└── Listener
12.2 核心要點速查
- Filter 在 DispatcherServlet 外,攔截所有請求,適合跨域、編碼、訪問日志
- Interceptor 在 DispatcherServlet 內(nèi),只攔截 Controller,適合登錄鑒權(quán)、權(quán)限控制
- AOP 基于 Spring 動態(tài)代理,能攔截任何 Bean 方法,適合耗時統(tǒng)計、操作日志
- Listener 是事件回調(diào),不能中斷請求,適合應(yīng)用初始化、事件通知
- ControllerAdvice 專門處理 Controller 層異常,不能捕獲 Filter 層異常
- ArgumentResolver 自定義參數(shù)解析,如
@CurrentUser注入登錄用戶 - ResponseBodyAdvice 統(tǒng)一包裝返回值,不侵入 Controller 代碼
- 執(zhí)行順序:Filter → Interceptor.preHandle → ArgumentResolver → AOP → Controller → ReturnValueHandler → Interceptor.postHandle → afterCompletion → Filter
- Interceptor 的 postHandle 和 afterCompletion 按注冊逆序執(zhí)行
- AOP 自調(diào)用不走代理,需通過
AopContext.currentProxy()解決
以上就是SpringBoot中請求處理組件全面對比與選型指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot請求處理組件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用Filter攔截器如何實現(xiàn)請求跨域轉(zhuǎn)發(fā)
這篇文章主要介紹了使用Filter攔截器如何實現(xiàn)請求跨域轉(zhuǎn)發(fā),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot自定義starter啟動器的實現(xiàn)思路
這篇文章主要介紹了SpringBoot如何自定義starter啟動器,通過starter的自定義過程,能夠加深大家對SpringBoot自動配置原理的理解,需要的朋友可以參考下2022-10-10
MyBatis動態(tài)SQL之<choose><when><o(jì)therwise>標(biāo)簽的使用
MyBatis中動態(tài)語句choose-when-otherwise 類似于Java中的switch-case-default語句,本文就來介紹一下MyBatis動態(tài)SQL之<choose><when><o(jì)therwise>標(biāo)簽的使用,感興趣的可以了解一下2023-09-09
Java利用Spire.XLS for Java實現(xiàn)自動化生成PDF文檔
在 Java 后端高效靈活地實現(xiàn) PDF 文檔的生成,常常是困擾開發(fā)者的一個痛點,本文將為您介紹如何利用 Spire.XLS for Java 輕松駕馭 Java 中的 PDF 文檔生成,感興趣的小伙伴可以了解下2026-02-02
JavaSpring?boot配置JDK和MAVEN開發(fā)環(huán)境詳解
本文介紹了在Windows系統(tǒng)上配置JDK、Maven、IntelliJ?IDEA的詳細(xì)步驟,首先安裝JDK并配置環(huán)境變量,然后下載并解壓Maven,配置環(huán)境變量和Maven倉庫,最后安裝并配置IntelliJ?IDEA,設(shè)置項目SDK和Maven配置2026-04-04
MyBatis-Plus聯(lián)表查詢以及分頁代碼實例
在開發(fā)中遇到了一個問題,需要進(jìn)行聯(lián)表查詢并進(jìn)行分頁,因為不想自己來寫分頁,所以還是依靠MybatisPlus來實現(xiàn)想要的功能,下面這篇文章主要給大家介紹了關(guān)于MyBatis-Plus聯(lián)表查詢以及分頁的相關(guān)資料,需要的朋友可以參考下2023-06-06
PowerJob的OmsLogHandler工作流程源碼解析
這篇文章主要為大家介紹了PowerJob的OmsLogHandler工作流程源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12

