【结合JSR380自定义校验】
根据你提供的代码,NEbServiceAreaDto
类继承自 EbServiceArea
并包含了一些额外的字段和嵌套对象列表(ebServiceAreaRegionList
和 ebServiceAreaSupplierList
)。这些嵌套对象也有各自的校验注解。接下来,我会基于你的需求提供一些建议,确保在不同场景下都能正确地进行参数校验。
✅ 一、你的理解完全正确!
- Validator.validate() 的前提条件:
是的!只有在实体类字段上使用了校验注解(如 @NotBlank, @NotNull 等)时,Validator.validate(dto) 才能生效。
比如你在 NEbServiceAreaDto 上写了:
@NotBlank(message = "服务范围代码不能为空")
private String serviceAreaCode;
那么当你调用:
validator.validate(nEbServiceAreaDto);
这个字段为空时,就会触发错误信息。
1. 本地调用时的手动校验
既然你提到希望在 Service 层中直接调用而不是通过 Controller 来触发校验逻辑,那么你需要手动处理校验逻辑。以下是几种实现方法:
方法一:使用 Validator
进行校验
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.ConstraintViolation;
import java.util.Set;@Service
public class EbServiceAreaService {private final Validator validator;public EbServiceAreaService() {ValidatorFactory factory = Validation.buildDefaultValidatorFactory();validator = factory.getValidator();}public JsonResponse saveServiceArea(NEbServiceAreaDto nEbServiceAreaDto) {// 手动校验Set<ConstraintViolation<NEbServiceAreaDto>> violations = validator.validate(nEbServiceAreaDto);if (!violations.isEmpty()) {String errorMsg = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("; "));return JsonResponse.fail(errorMsg);}// 校验嵌套对象列表for (EbServiceAreaRegion region : nEbServiceAreaDto.getEbServiceAreaRegionList()) {Set<ConstraintViolation<EbServiceAreaRegion>> regionViolations = validator.validate(region);if (!regionViolations.isEmpty()) {String errorMsg = regionViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("; "));return JsonResponse.fail(errorMsg);}}for (EbServiceAreaSupplier supplier : nEbServiceAreaDto.getEbServiceAreaSupplierList()) {Set<ConstraintViolation<EbServiceAreaSupplier>> supplierViolations = validator.validate(supplier);if (!supplierViolations.isEmpty()) {String errorMsg = supplierViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("; "));return JsonResponse.fail(errorMsg);}}// 后续业务逻辑...return JsonResponse.success("成功");}
}
方法二:封装校验工具类
为了简化校验逻辑,可以将校验逻辑封装到一个工具类中:
import javax.validation.Validator;
import javax.validation.Validation;
import javax.validation.ConstraintViolation;
import java.util.Set;
import java.util.stream.Collectors;@Component
public class DtoValidator {private final Validator validator;public DtoValidator() {ValidatorFactory factory = Validation.buildDefaultValidatorFactory();validator = factory.getValidator();}public <T> void validate(T dto) throws IllegalArgumentException {Set<ConstraintViolation<T>> violations = validator.validate(dto);if (!violations.isEmpty()) {String errorMsg = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("; "));throw new IllegalArgumentException(errorMsg);}}
}
然后在 Service 中使用这个工具类:
@Service
public class EbServiceAreaService {@Autowiredprivate DtoValidator dtoValidator;public JsonResponse saveServiceArea(NEbServiceAreaDto nEbServiceAreaDto) {try {dtoValidator.validate(nEbServiceAreaDto);for (EbServiceAreaRegion region : nEbServiceAreaDto.getEbServiceAreaRegionList()) {dtoValidator.validate(region);}for (EbServiceAreaSupplier supplier : nEbServiceAreaDto.getEbServiceAreaSupplierList()) {dtoValidator.validate(supplier);}} catch (IllegalArgumentException e) {return JsonResponse.fail(e.getMessage());}// 后续业务逻辑...return JsonResponse.success("成功");}
}
2. Feign 调用时的注意事项
如果你确实需要通过 Feign 调用该接口,那么请注意以下几点:
- 确保接口暴露了 HTTP 接口:只有当
/v1/save-service-area
是一个独立的服务并且可以通过 HTTP 访问时,才能使用 Feign 调用。 - Feign Client 配置:定义 Feign Client 并配置好服务名和路径。
@FeignClient(name = "service-b", path = "/api")
public interface ServiceBClient {@PostMapping("/v1/save-service-area")JsonResponse saveServiceArea(@RequestBody NEbServiceAreaDto dto);
}
- Controller 层的校验依然有效:当你通过 Feign 调用该接口时,
@Valid
注解依然会生效,因为它是 Spring MVC 的一部分。
3. 总结与建议
- 如果是在同一个服务内部,建议直接调用 Service 方法,并手动添加校验逻辑。这样可以避免不必要的网络开销和延迟。
- 如果确实需要跨服务调用,那么使用 Feign 是合适的,但请确保你已经正确配置了 Feign Client,并且目标服务的接口是公开可访问的。
通过上述方法,你可以确保无论是在本地调用还是通过 Feign 调用时,参数校验都能正常工作。如果你有更多具体的需求或问题,请随时告诉我!
=======================================================
好的!我们来结合你之前发给我的代码和问题,一步一步地解释清楚:
🧾 你的原始问题背景
你有一组 DTO 和实体类,结构如下:
✅ NEbServiceAreaDto
(主 DTO)
public class NEbServiceAreaDto extends EbServiceArea {@NotBlank(message = "服务范围代码不能为空")private String serviceAreaCode;@NotBlank(message = "服务范围名称不能为空")private String serviceAreaName;@NotNull(message = "生效日期不能为空")private Date effectDate;@NotNull(message = "失效日期不能为空")private Date failureDate;@Validprivate List<EbServiceAreaRegion> ebServiceAreaRegionList;@Validprivate List<EbServiceAreaSupplier> ebServiceAreaSupplierList;
}
✅ EbServiceAreaRegion
(子对象)
public class EbServiceAreaRegion extends BaseDomain {@NotBlank(message = "区域地点类型不能为空")private String cterType;// 其他字段...
}
✅ EbServiceAreaSupplier
(子对象)
public class EbServiceAreaSupplier extends BaseDomain {@NotBlank(message = "承运商代码不能为空")private String supplierCode;@NotBlank(message = "承运商名称不能为空")private String supplierName;// 其他字段...
}
🚩 你遇到的问题
你说你在 Service 中调用了:
ebServiceAreaService.saveServiceArea(nEbServiceAreaDto);
而不是通过 Controller 的接口 /v1/save-service-area
来触发校验。
于是你担心:
Controller 上的
@Valid
校验是不是没生效?
是的,确实不会生效。因为 @Valid
是 Spring MVC 的功能,只在 HTTP 请求时起作用,不能跨方法、跨类自动生效。
✅ 解决方案:手动添加校验逻辑
我们要做的就是:在 Service 层手动对 DTO 及其嵌套对象进行参数校验。
下面我用最清晰的方式,一步步写出完整代码并解释每一行的作用。
✅ 第一步:注入 Validator
Spring Boot 默认已经集成了 Hibernate Validator,所以你可以直接注入使用:
@Service
public class EbServiceAreaService {@Autowiredprivate Validator validator; // 自动注入 Validator
}
✅ 第二步:定义保存方法
@Transactional(rollbackFor = Exception.class)
public JsonResponse saveServiceArea(NEbServiceAreaDto nEbServiceAreaDto) {// Step 1: 校验主 DTOSet<ConstraintViolation<NEbServiceAreaDto>> mainViolations = validator.validate(nEbServiceAreaDto);if (!mainViolations.isEmpty()) {String errorMsg = mainViolations.stream().map(v -> v.getMessage()).collect(Collectors.joining("; "));return JsonResponse.fail(errorMsg);}// Step 2: 校验嵌套列表 - ebServiceAreaRegionListfor (EbServiceAreaRegion region : CollectionUtil.emptyIfNull(nEbServiceAreaDto.getEbServiceAreaRegionList())) {Set<ConstraintViolation<EbServiceAreaRegion>> regionViolations = validator.validate(region);if (!regionViolations.isEmpty()) {String errorMsg = regionViolations.stream().map(v -> v.getMessage()).collect(Collectors.joining("; "));return JsonResponse.fail("区域信息校验失败:" + errorMsg);}}// Step 3: 校验嵌套列表 - ebServiceAreaSupplierListfor (EbServiceAreaSupplier supplier : CollectionUtil.emptyIfNull(nEbServiceAreaDto.getEbServiceAreaSupplierList())) {Set<ConstraintViolation<EbServiceAreaSupplier>> supplierViolations = validator.validate(supplier);if (!supplierViolations.isEmpty()) {String errorMsg = supplierViolations.stream().map(v -> v.getMessage()).collect(Collectors.joining("; "));return JsonResponse.fail("承运商信息校验失败:" + errorMsg);}}// Step 4: 执行业务逻辑(插入或更新)if (ObjectUtil.isEmpty(nEbServiceAreaDto.getId())) {Long count = this.insert(nEbServiceAreaDto);return JsonResponse.success(count);} else {int count = this.updateSelective(nEbServiceAreaDto);return JsonResponse.success(count);}
}
🔍 逐行解释说明
代码片段 | 解释 |
---|---|
Set<ConstraintViolation<NEbServiceAreaDto>> mainViolations = validator.validate(nEbServiceAreaDto); | 对主 DTO 进行校验,返回所有违反约束的错误 |
if (!mainViolations.isEmpty()) { ... } | 如果有错误,就收集提示信息并返回失败 |
for (EbServiceAreaRegion region : ...) | 遍历区域列表 |
validator.validate(region) | 对每个区域对象进行校验 |
return JsonResponse.fail(...) | 如果发现错误,立即中断流程并返回错误信息 |
this.insert(...) / this.updateSelective(...) | 没有错误的话,继续执行业务逻辑 |
✅ 工具类说明(可选)
CollectionUtil.emptyIfNull(...)
:防止空指针异常,如果 list 为 null 返回一个空集合。ObjectUtil.isEmpty(...)
:Apache Commons 或 Hutool 提供的工具类,用于判断对象是否为空。
如果你没有这些工具类,可以换成:
if (nEbServiceAreaDto.getEbServiceAreaRegionList() == null || nEbServiceAreaDto.getEbServiceAreaRegionList().isEmpty()) {// 处理空的情况
}
✅ 最后总结一句话:
当你在 Service 中直接调用
saveServiceArea(dto)
而不是走 HTTP 接口时,Controller 上的@Valid
不会生效。你需要自己手动调用Validator.validate()
来完成校验,包括嵌套对象。
🎯 建议你这样做:
- 把上面这段校验代码复制到你的项目中。
- 确保你引入了
spring-boot-starter-validation
依赖(Spring Boot 默认已包含)。 - 在测试的时候故意传空值(比如空的
serviceAreaCode
),看看能不能正确返回提示。 - 后续如果有多个类似的 DTO,可以把校验封装成一个通用方法。
如果你希望我帮你:
- 封装成一个通用的校验工具类
- 支持嵌套 List、Map 等复杂结构
- 使用分组校验(Group Validation)
- 结合统一异常处理器(@ControllerAdvice)
欢迎继续问我,我可以一步步带你实现 😊