Spring MVC 扩展机制对比总结:@Configuration + WebMvcConfigurer vs @ControllerAdvice

发布于:2025-09-06 ⋅ 阅读:(16) ⋅ 点赞:(0)

1. 核心对比

对比项 @Configuration + WebMvcConfigurer @ControllerAdvice + ResponseBodyAdvice
用途 配置 Spring MVC 全局行为(如拦截器、消息转换器等) 全局拦截控制器方法的请求或响应体
扩展方式 继承 WebMvcConfigurer(旧版用 WebMvcConfigurerAdapter 实现 RequestBodyAdvice / ResponseBodyAdvice
执行时机 用于注册拦截器、视图解析等,在请求处理前配置 在请求处理前后修改请求/响应体内容
典型用途 注册自定义拦截器、跨域配置、静态资源映射 统一加签、解密请求、包装响应体
是否可替代 ❌ 不能替代,它是“配置入口” ❌ 不能替代,它是“数据处理”

2. HTTP 请求流程

1. HTTP 请求到达
   ↓
2. Filter(过滤器)           ← 最早,可终止请求(如 CORS、登录检查)
   ↓
3. HandlerInterceptor.preHandle()  ← 推荐鉴权位置
   ↓
4. RequestBodyAdvice.beforeBodyRead() ← 只处理 @RequestBody 的请求体
   ↓
5. Controller 方法执行
   ↓
6. ResponseBodyAdvice.beforeBodyWrite()
   ↓
7. 响应返回
   ↓
8. HandlerInterceptor.afterCompletion()
   ↓
9. Filter 最终处理

关键说明

  • RequestBodyAdvice 是在 preHandle() 之后才执行的
  • 它只对带有 @RequestBody 的接口有效
  • 它的目的是处理请求体内容(如解密、记录原始 JSON),不是做权限判断

3. @Configuration + WebMvcConfigurer 适用场景

3.1 适用场景

用于配置 Spring MVC 的全局行为,属于"框架配置层",适合在请求处理流程中进行前置控制或流程定制。

典型用途

  • 注册拦截器(如鉴权、日志、限流)
  • 配置跨域(CORS)
  • 静态资源映射
  • 消息转换器(如自定义 JSON 处理)
  • 添加视图控制器

3.2 Demo:使用 WebMvcConfigurer 注册鉴权拦截器

1. 自定义注解:@RequireAuth
// com.example.annotation.RequireAuth
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequireAuth {
}
2. 拦截器:AuthInterceptor
// com.example.interceptor.AuthInterceptor
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;

            // 判断方法是否标记了 @RequireAuth
            if (handlerMethod.hasMethodAnnotation(RequireAuth.class)) {
                String token = request.getHeader("Authorization");
                if (token == null || !token.equals("Bearer admin123")) {
                    response.setStatus(401);
                    response.setContentType("application/json");
                    response.getWriter().write("{\"error\": \"Unauthorized\"}");
                    return false;
                }
            }
        }
        return true;
    }
}
3. 配置类:注册拦截器
// com.example.config.WebConfig
import com.example.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 WebConfig implements WebMvcConfigurer {

    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**"); // 只拦截 /api 路径
    }
}
4. 控制器测试
@RestController
public class TestController {

    @GetMapping("/public")
    public String publicApi() {
        return "This is public";
    }

    @GetMapping("/private")
    @RequireAuth
    public String privateApi() {
        return "This is private";
    }
}

测试结果

  • /public:无需 token,直接访问
  • /private:必须携带 Authorization: Bearer admin123,否则返回 401

4. ControllerAdvice 适用场景

4.1 适用场景

用于全局增强控制器行为,属于"业务处理层",适合对所有控制器方法进行统一处理。

典型用途

  • 全局异常处理(@ExceptionHandler
  • 统一响应体包装(ResponseBodyAdvice
  • 请求体预处理(RequestBodyAdvice
  • 全局数据绑定(@ModelAttribute

4.2 Demo:使用 @ControllerAdvice 统一响应体格式

1. 自定义注解:@WrapResponse
// com.example.annotation.WrapResponse
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WrapResponse {
}
2. 响应体统一包装:ResponseWrapperAdvice
// com.example.advice.ResponseWrapperAdvice
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<?> converterType) {
        // 仅对标注 @WrapResponse 的方法生效
        return returnType.getMethodAnnotation(WrapResponse.class) != null;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<?> selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("message", "success");
        result.put("data", body);
        result.put("timestamp", System.currentTimeMillis());

        return result;
    }
}
3. 控制器测试
@RestController
public class TestController {

    @GetMapping("/raw")
    public String rawResponse() {
        return "原始响应,不包装";
    }

    @GetMapping("/wrapped")
    @WrapResponse
    public User wrappedResponse() {
        return new User("李四", 30);
    }

    static class User {
        String name;
        int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        // getter/setter 省略
        public String getName() { return name; }
        public int getAge() { return age; }
    }
}

测试结果

  • /raw:返回 "原始响应,不包装"
  • /wrapped:返回 JSON:
{
  "code": 200,
  "message": "success",
  "data": {
    "name": "李四",
    "age": 30
  },
  "timestamp": 1725432100123
}

5. 总结对比

维度 @Configuration + WebMvcConfigurer @ControllerAdvice
定位 框架配置入口 控制器行为增强
主要用途 注册拦截器、跨域、静态资源等 全局异常处理、响应包装、请求预处理
执行阶段 请求流程的配置阶段 控制器方法执行的前后
是否影响流程 ✅ 可拦截、终止请求 ❌ 一般不终止,只修改数据
推荐场景 鉴权、日志、限流、CORS 统一响应格式、加签、异常处理

选择建议

  • 要"控制流程" → 用 WebMvcConfigurer + 拦截器
  • 要"处理数据" → 用 @ControllerAdvice + ResponseBodyAdvice/@ExceptionHandler

网站公告

今日签到

点亮在社区的每一天
去签到