spring-mvc

25 阅读6分钟

接收参数的方式

参数格式是 k=v [常见于get请求 或者 文件上传等请求], 直接接收或者 pojo实体接收
  • 直接接收或者@RequestParam重命名接收
// 前端请求格式为
/user?id=1&name=bwf&age=88

// 当k与变量名一致时,可以不用@RequestParam,同名变量接收
@GetMapping("/user")
public String getUser(
    @RequestParam("id") Long userId,           // 必须参数 取出id赋值给变量 userId
    @RequestParam(value = "name", required = false) String name, // 可选参数
    @RequestParam(value = "age", defaultValue = "18") int age    // 默认值
) {
    return "user";
}
  • 封装为POJO对象接收
// 前端请求格式为
/user?username=bwf&email=163@qq.com&age=88

@GetMapping("/register")
public String register(UserForm form) {
    // Spring 自动将参数绑定到对象的同名属性
    return "success";
}

// 表单对象
@Data
public class UserForm {
    private String username;
    private String email;
    private Integer age;
}
参数格式请求体json格式 [常见于post等请求]
  • @RequestBody
@PostMapping("/order")
public void createOrder(@RequestBody OrderRequest request) {
   
}

@Data
public class OrderRequest {
    private Long userId;
    private List<OrderItemDTO> items;
    private AddressDTO shippingAddress;
    private String paymentMethod;
}

文件上传 MultipartFile类接收

/**
 * 前端上传图片
 * h5请求方式
 *     post
 * h5 入参
 *    let formData = new FormData();
 *    formData.append("file", file.raw);
 *    formData.append("file", file.raw);
 *    formData.append("attach", file.raw);
 *    formData.append(其他字段名, 字段对应的值)
 * h5 请求头
 *    {headers:{"Content-Type":"multipart/form-data"}}
 * h5发送
 *    {headers:{"Content-Type":"multipart/form-data"}}
 *    data:formData
 */

@PostMapping("/upload")
public void upload(User user, @RequestParam("file")MultipartFile[] files, @RequestParam("attach") MultipartFile attach){
    log.info("upload---user:{}", user); // 其他字段名映射到User实体类中
}

文件下载

  • 图片/excel下载 (前端get/post请求均可)
/**
 * 下载图片等文件
 * @param httpServletResponse
 */
public void downLoadFile(HttpServletResponse httpServletResponse) {
    //模拟读取 resources目录下的资源
    String filePath = this.getClass().getResource("/static/pic/1.jpg").getPath();
    File file = new File(filePath);
    String fileName = filePath.substring(filePath.lastIndexOf("/")+1);
    try {
        InputStream inputStream = Files.newInputStream(file.toPath());
        //响应头需要设置内容的处理方式:下载文件需要指定为文件
        httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        httpServletResponse.setHeader("Content-type","blob");
        cn.hutool.core.io.IoUtil.copy(inputStream, httpServletResponse.getOutputStream());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

//excel下载
public void exportExcel(HttpServletResponse httpServletResponse){
    List<User> list = "下载的集合"
    httpServletResponse.setContentType("application/vnd.ms-excel");
        httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
    httpServletResponse.setCharacterEncoding("UTF-8");
    com.alibaba.excel.EasyExcel.write(httpServletResponse.getOutputStream, User.class).sheet("sheet1").doWrite(list)
}

@Data
@AllArgsConstructor
public class User{
    /**
     * value={"一级表头","二级表头","三级表头"}
     * index=0 排序从左到右
     */
    @ExcelProperty(value={"用户信息","姓名"},index=0)
    private String userName;

    @ExcelProperty(value={"用户信息","性别"},index=1)
    private String sex;

    /**
     * 不要在excel中展示
     */
    @ExcelIgnore
    private String score

}

封装响应统一结果类

import lombok.AllArgsConstructor;
import lombok.Data;

@AllArgsConstructor
@Data
public class Result<T>{
    private String code;
    private String msg;
    private T data;

    public Result(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public static Result ok(){
        return new Result("200", "OK");
    }

    public static<T> Result<T> ok(T data){
        return new Result<>("200", "ok", data);
    }

}

取前端路径参数

  • @PathVariable
@GetMapping("/users/{id}")
public void getUserById(@PathVariable Long id) {
    // 访问: GET /users/123
    // id = 123

}


@GetMapping("/products/{productId}")
public void getProduct(
    @PathVariable("productId") Long id  // 参数名与路径变量名不同
) {
    // 访问: GET /products/789
    // id = 789

}

springmvc 拦截器

    1. 实现HandlerInterceptor接口生成一个拦截器
    1. 实现WebMvcConfigurer接口addInterceptors方法添加上述拦截器
@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {

    /**
     * @return boolean true-放行请求  false-拦截请求
     */
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        log.info("AuthInterceptor.preHandle---start");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler,
                                Exception ex) throws Exception {
    }
}


@Configuration
public class MySpringConfig implements WebMvcConfigurer {

    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor).addPathPatterns("/**");
    }
}
拦截器执行顺序
  • 正常执行顺序

image.png

请求进入 →
    拦截器1.preHandle() ✅ (放行)
    拦截器2.preHandle() ✅ (放行)
    拦截器3.preHandle() ✅ (放行)
    ↓
    Controller执行
    ↓
    拦截器3.postHandle() ← (逆序)
    拦截器2.postHandle() ← (逆序)
    拦截器1.postHandle() ← (逆序)
    ↓
    视图渲染
    ↓
    拦截器3.afterCompletion() ← (逆序)
    拦截器2.afterCompletion() ← (逆序)
    拦截器1.afterCompletion() ← (逆序)
    ↓
响应返回
  • 中断执行顺序(拦截器2不放行)

deepseek_mermaid_20260123_c53150.png

请求进入 →
    拦截器1.preHandle() ✅ (放行)
    拦截器2.preHandle() ❌ (返回false,拦截!)
    ↓
    【中断!】后续拦截器3和Controller不会执行
    ↓
    拦截器1.afterCompletion() ← (只有放行的才执行)
    ↓
    直接返回响应给客户端
Spring MVC 拦截器 vs 过滤器(Filter)

deepseek_mermaid_20260123_8f19bc.png

对比维度Filter(过滤器)Interceptor(拦截器)
所属规范Servlet 规范(J2EE)Spring MVC 框架
依赖依赖 Servlet 容器(Tomcat)依赖 Spring 框架
位置在 DispatcherServlet 之前在 DispatcherServlet 之后,Controller 前后
作用范围对所有请求有效只对 Spring MVC 处理的请求有效
放行chain.doFilter() 放行请求preHandle返回true 放行请求
使用场景字符编码、CORS、安全过滤、日志记录权限验证、日志记录、性能监控、数据预处理
配置方式web.xml 或 @WebFilter 注解实现 WebMvcConfigurer 接口
执行顺序通过 @Order 或 web.xml 配置顺序通过 order() 方法配置顺序

异常拦截器@RestControllerAdvice + @ExceptionHandler

@Slf4j
@RestControllerAdvice
public class GloabalAdvice {

    @ExceptionHandler(Exception.class)
    public Result exceptionHandler(Exception e){
        log.info("系统异常:{}", e);
        return new Result("500", "系统发生错误");
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){
        log.info("入参校验不通过---e:{}", e);
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();

        HashMap<String, String> fieldErrorMap = new HashMap<>();
        for (FieldError fieldError : fieldErrors) {
            String field = fieldError.getField();
            String defaultMessage = fieldError.getDefaultMessage();
            fieldErrorMap.put(field, defaultMessage);
        }
        return new Result<>("500", "入参校验不通过", fieldErrorMap);
    }

    @ExceptionHandler(BizException.class)
    public Result bizExceptionHandler(BizException e){
        log.info("业务异常:{}", e);
        return new Result(e.getCode(), e.getMsg());
    }
}
校验入参
  • 引入包 spring-boot-starter-validation
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  • Controller层开启校验 @Valid
@PostMapping("/user/update")
public Result<User> updateUser(@RequestBody @Valid User user){
    log.info("updateUser---user:{}", user);
    if(!StringUtils.hasText(user.getId())){
        throw new BizException(ExceptionEnum.USER_NOT_FOUND);
    }
    return Result.ok(user);
}
  • 添加校验规则
@Data
public class User {
    @NotNull(message = "用户id不能为空")
    private String id;

    @NotNull(message = "年龄不能为空")
    @Min(value = 0, message = "最小年龄为0")
    @Max(value = 150, message = "最大年龄为150")
    private Integer age;

    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
    private String username;

    @NotNull(message = "身份证号码不能为空")
    @IdCard(message = "身份证号码格式不正确")
    private String idCard;

    @Valid
    @NotNull(message = "地址不能为空")
    private Address address;
}
  • 校验不通过,默认不会进入Controller。可以被全局异常拦截器捕获,错误类型MethodArgumentNotValidException
常用校验注解大全
  • 空值检查
// 1. @NotNull - 不能为null(但可以为空字符串)
@NotNull(message = "ID不能为空")
private Long id;

// 2. @NotEmpty - 集合/数组/Map/字符串不能为空
@NotEmpty(message = "角色列表不能为空")
private List<String> roles;

@NotEmpty(message = "用户名不能为空")
private String username;  // 不能是null或空字符串""

// 3. @NotBlank - 字符串不能为null且必须包含非空白字符
@NotBlank(message = "密码不能为空")
private String password;  // 不能是null、""、"   "
  • 数值范围检查
// 1. @Min - 最小值限制
@Min(value = 0, message = "年龄不能小于0")
private Integer age;

@Min(value = 1, message = "数量必须大于0")
private BigDecimal quantity;

// 2. @Max - 最大值限制
@Max(value = 150, message = "年龄不能大于150")
private Integer age;

// 3. @Range - 数值范围(Spring扩展)
@Range(min = 0, max = 100, message = "分数必须在0-100之间")
private Integer score;
  • 字符串长度检查
// 1. @Size - 集合/数组/字符串长度
@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
private String password;

@Size(min = 1, max = 10, message = "最多选择10个标签")
private List<String> tags;

// 2. @Length - 字符串长度(Spring扩展)
@Length(min = 2, max = 10, message = "名称长度必须在2-10之间")
private String name;
  • 模式匹配检查
// 1. @Pattern - 正则表达式匹配
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;

@Pattern(regexp = "^1[3-9]\d{9}$", message = "手机号格式不正确")
private String phone;
  • 自定义校验器 + 注解

    注解 @IdCard validatedBy的值为自定义校验器

@Documented
@Constraint(
        validatedBy = {IdCardValidator.class}
)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IdCard {

    String message() default "{jakarta.validation.constraints.NotBlank.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

校验器 IdCardValidator 实现 ConstraintValidator接口的isValid方法

/**
* ConstraintValidator 泛型1:注解名称  泛型2:注解所作用的属性类型
* 
*/
@Slf4j
public class IdCardValidator implements ConstraintValidator<IdCard, String> {

   /**
    * 是否有效
    * @param idCard 校验目标的属性值
    * @param constraintValidatorContext
    * @return boolean true-校验通过  false-校验失败
    */
   @Override
   public boolean isValid(String idCard, ConstraintValidatorContext constraintValidatorContext) {
       log.info("IdCardValidator---start:{}", idCard);
       if (idCard == null) {
           return true;
       }

       // 简单的身份证格式校验
       String regex = "(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)";
       if (!idCard.matches(regex)) {
           return false;
       }
       return true;
   }
}
自定义业务异常类
  • BizException
@NoArgsConstructor
@AllArgsConstructor
@Data
public class BizException extends RuntimeException{

    private String code;
    private String msg;

    public BizException(ExceptionEnum exceptionEnum){
        this.code = exceptionEnum.getCode();
        this.msg = exceptionEnum.getMsg();
    }
}
  • 业务错误常见枚举ExceptionEnum
import lombok.Getter;

public enum ExceptionEnum {

    USER_NOT_FOUND("1000", "用户不存在");

    @Getter
    private String code;
    @Getter
    private String msg;

    ExceptionEnum(String code, String msg){
        this.code = code;
        this.msg = msg;
    }
}
  • 使用
throw new BizException(ExceptionEnum.USER_NOT_FOUND);

VO / DTO / BO / Entity

┌─────────────────────────────────────────────────────────┐
│                  Presentation Layer                     │
│  (Controller / Resource / Endpoint)                     │
│                                                         │
│  ┌─────────────────┐  ┌─────────────────┐             │
│  │     Request     │  │    Response     │             │
│  │      VO         │  │       VO        │             │
│  └─────────────────┘  └─────────────────┘             │
└───────────────┬─────────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────────────────────┐
│                  Business Layer                         │
│  (Service / Manager / Facade)                           │
│                                                         │
│  ┌─────────────────┐  ┌─────────────────┐             │
│  │       BO        │  │       BO        │             │
│  │  (Business      │  │  (Business      │             │
│  │   Object)       │  │   Object)       │             │
│  └─────────────────┘  └─────────────────┘             │
└───────────────┬─────────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────────────────────┐
│                  Persistence Layer                      │
│  (Repository / DAO / Mapper)                            │
│                                                         │
│  ┌─────────────────┐  ┌─────────────────┐             │
│  │     Entity      │  │    Entity       │             │
│  │    (DO/PO)      │  │   (DO/PO)       │             │
│  └─────────────────┘  └─────────────────┘             │
└───────────────┬─────────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────────────────────────┐
│                    Database Layer                       │
│                                                         │
│  ┌─────────────────┐  ┌─────────────────┐             │
│  │    Table        │  │    Table        │             │
│  │   Schema        │  │   Schema        │             │
│  └─────────────────┘  └─────────────────┘             │
└─────────────────────────────────────────────────────────┘
  • VO (View Object 视图对象) 用于Controller 接收前端入参 + 返回给前端
  • DTO (Data Transfer Object 数据传输对象) 用于Controller-Service或Service-Service之间的数据传输
  • BO (Business Object 业务对象) 核心业务逻辑载体
  • Entity 与数据库表一一对应

knife4j Api文档

doc.xiaominfo.com/v2/document…

  • 加入依赖
<dependency>
    <groupId>com.github.xingfudeshi</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.6.0</version>
</dependency>
  • application.yml配置 需要将packages-to-scan值改为controller所在位置
# springdoc-openapi项目配置
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'default'
      paths-to-match: '/**'
      packages-to-scan: org.example.springmvc.controller
# knife4j的增强配置,不需要增强可以不配
knife4j:
  enable: true
  setting:
    language: zh_cn
  • 加接口文档注解 @Tag 标注于Controller类 @Operation Controller类中方法 @Schema 入参bean属性
  • 访问接口文档 http://localhost:8080/doc.html#/