这是一个非常好的问题!@ValidatedController 层、Service 层和 ServiceImpl 层 都使用,是基于 分层校验原则防御性编程 的最佳实践。让我详细解释原因:

1. 为什么要在 Controller 层使用 @Validated

Controller 层是第一道防线,负责:

  • 校验 HTTP 请求参数的格式(格式、长度、非空等)
  • 防止恶意输入直接进入系统
  • 返回友好的错误信息给前端
@RestController
@RequestMapping("/user")
@Validated  // ✅ Controller 层校验
public class UserController {
    
    @PostMapping("/register")
    public Result<User> register(@Valid @RequestBody User user, HttpServletRequest request) {
        // 这里校验的是 HTTP 请求体的格式
        // 例如:用户名不能为空、邮箱格式是否正确等
        return userService.register(user, ipAddress);
    }
}

2. 为什么还要在 Service 层使用 @Validated

Service 层是业务逻辑的守门员,负责:

  • 校验业务规则的合法性(不仅仅是格式)
  • 防止非法业务操作(如负数金额、过去的时间等)
  • 确保即使 Controller 层被绕过,业务规则仍然有效
@Service
@Validated  // ✅ Service 层校验
public class UserServiceImpl implements UserService {
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public User register(@Valid User user, String ipAddress) {
        // 这里校验的是业务规则
        // 例如:用户名是否已存在、邮箱是否已被注册等
        // 即使有人直接调用 Service 方法,也能保证参数合法
        return registeredUser;
    }
}

3. 为什么 ServiceImpl 层也要使用 @Validated

ServiceImpl 层是具体实现的保障,负责:

  • 实现具体的校验逻辑
  • 处理复杂的业务校验规则
  • 作为最后的防线,防止任何非法数据进入数据库
@Service
@Validated  // ✅ ServiceImpl 层校验
public class UserServiceImpl implements UserService {
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public User register(@Valid User user, String ipAddress) {
        // 具体的业务校验逻辑
        if (checkUsernameExists(user.getUsername())) {
            throw new IllegalArgumentException("用户名已存在");
        }
        // ... 其他业务校验
    }
}

4. 三层校验的完整示例

// ==================== Controller 层 ====================
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
    
    @PostMapping("/register")
    public Result<User> register(@Valid @RequestBody User user) {
        // 1. 校验 HTTP 请求格式(@Valid 注解)
        // 2. 调用 Service 层
        return userService.register(user);
    }
}

// ==================== Service 层 ====================
public interface UserService {
    
    @Validated  // 接口层也可以定义校验规则
    User register(@Valid User user);
}

// ==================== ServiceImpl 层 ====================
@Service
@Validated
public class UserServiceImpl implements UserService {
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public User register(@Valid User user) {
        // 1. 再次校验业务规则(即使 Controller 层被绕过)
        // 2. 执行具体的业务逻辑
        // 3. 保存到数据库
        return savedUser;
    }
}

5. 实际场景中的必要性

场景 1:内部调用

// 定时任务直接调用 Service
@Component
public class UserSyncTask {
    
    @Autowired
    private UserService userService;
    
    @Scheduled(cron = "0 0 2 * * ?")
    public void syncUsers() {
        User user = new User();
        user.setUsername("test");
        // 如果没有 Service 层校验,这里可能传入非法数据
        userService.register(user);  // ✅ Service 层会拦截
    }
}

场景 2:微服务调用

// 其他服务通过 Feign 调用
@FeignClient(name = "user-service")
public interface UserFeignClient {
    
    @PostMapping("/user/register")
    Result<User> register(@RequestBody User user);
}

// 调用方可能没有做参数校验
User user = new User();
user.setUsername("");  // 空用户名
userFeignClient.register(user);  // ✅ Service 层会拦截

场景 3:测试代码

@Test
public void testRegister() {
    User user = new User();
    user.setUsername(null);  // 空用户名
    userService.register(user);  // ✅ Service 层会拦截
}

6. 校验的分工

层级校验内容示例
Controller 层格式校验用户名不能为空、邮箱格式、长度限制
Service 层业务规则校验用户名是否已存在、邮箱是否已被注册
ServiceImpl 层具体实现校验复杂的业务校验、状态检查

7. 避免过度校验

虽然三层都使用 @Validated,但要注意:

  • 不要在每层都校验相同的规则(避免重复)
  • Controller 层校验格式,Service 层校验业务
  • 使用分组校验 来区分不同场景
// 使用分组校验
public interface CreateGroup {}
public interface UpdateGroup {}

public class User {
    @NotNull(groups = {CreateGroup.class, UpdateGroup.class})
    private Long id;
    
    @NotBlank(groups = CreateGroup.class)
    private String username;
}

// Controller 使用 CreateGroup
@PostMapping("/register")
public Result<User> register(@Validated(CreateGroup.class) @RequestBody User user) {
    return userService.register(user);
}

// Service 使用 UpdateGroup
@PutMapping("/update")
public Result<User> update(@Validated(UpdateGroup.class) @RequestBody User user) {
    return userService.updateUser(user);
}

8. 最佳实践建议

  1. Controller 层:使用 @Validated + @Valid 校验请求参数
  2. Service 层:使用 @Validated 定义业务校验规则
  3. ServiceImpl 层:实现具体的校验逻辑
  4. 使用分组校验:区分不同场景的校验规则
  5. 统一异常处理:使用 @ControllerAdvice 统一处理校验异常

总结

三层都使用 @Validated 的原因是:

  • Controller 层:保护系统入口,防止非法请求
  • Service 层:保护业务逻辑,防止非法操作
  • ServiceImpl 层:作为最后防线,确保数据完整性

这是一种 防御性编程 的最佳实践,确保系统在任何一个环节都不会因为非法数据而出错。虽然看起来有些冗余,但在大型系统中,这种分层校验是非常必要的。

更多推荐