我正在构建一个REST API,它将@PathParameter用于父PK,将@RequestBody用于子表单参数。接下来,我需要使用@RequestBody中的param键和@PathParameter中的父pk来针对存储在数据库中的正则表达式值验证@RequestBody值,但是我一直无法找出添加@PathParameter的好方法在不使用@ModelAttribute的情况下调用@Valid之前,将pk id传递给@RequestBody子对象。
使用@ModelAttribute,我已经能够将@PathParameter添加到@RequestBody对象,然后使用@Valid验证@RequestBody对象。但是我发现在使用@ModelAttribute时,Spring不再抛出MethodArgumentNotValidException,因此消除了使用全局异常处理程序的能力。
我发现将BindingResult添加到控制器处理程序中,然后在存在错误时抛出新的MethodArgumentNotValidException可能触发全局异常处理程序。
我的理解是ModelAttribute是针对Spring MVC的,而不是RestController的,因为我没有使用视图,所以我想知道这是否是正确的方法,或者是否有更好的解决方案。这是我的代码示例。
HTTP发布
localhost:8072/api/clover/graph_run/2
[
{
"graphKey" : "DATA_DIRECTORY",
"graphValue" : "/data/clover-prod"
},
{
"graphKey" : "DATAOUT_DIR",
"graphValue" : "/data/clover-prod/94l"
},
{
"graphKey" : "DELAY_MS",
"graphValue" : "0"
}
]
RestController
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/clover")
public class GraphRunController {
final CloverServerService cloverServerService;
@PostMapping("/graph_run/{graphId}")
public String graphRun(@ModelAttribute("graphId") @Valid GraphJobDTO graphJobDTO,
BindingResult bindingResult ) throws MethodArgumentNotValidException {
log.debug("GraphRun {}", graphJobDTO);
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(null, bindingResult);
}
return cloverServerService.runGraph(graphJobDTO);
}
}
ControllerAdvise中的ModelAttribute
@Slf4j
@RequiredArgsConstructor
@ControllerAdvice( assignableTypes = {GraphRunController.class})
public class GraphRunControllerAdvise {
final GraphJobRepository graphJobRepository;
@ModelAttribute("graphId")
public GraphJobDTO addGraphId(@PathVariable(value = "graphId") Long graphId,
@RequestBody List<GraphJobPropertyDTO> graphProperties) {
log.debug("ModelAttribute graphId {}", graphId);
//Query database for the graph job and all it's parameters
GraphJob graphJob = graphJobRepository.findById(graphId)
.orElseThrow(() -> new HRINotFoundException("No such job execution."
+ graphId));
GraphJobDTO graphJobDTO = new GraphJobDTO();
graphJobDTO.setGraph(graphJob.getGraph());
graphJobDTO.setGraphProperties(graphProperties);
//Create a map from the database with the key being the GraphKey contained in both the request and the database
Map<String, GraphJobProperty> jobMap = graphJob.getJobProperties().stream().collect(
Collectors.toMap(s -> s.getGraphKey().toUpperCase(), Function.identity()));
graphProperties.forEach(v -> {
String graphKey = v.getGraphKey();
//If the graphKey in the request cannot be found in the database, throw an exception. We will handle the exception
//in th exception handler
if(!jobMap.containsKey(graphKey)) {
throw new HRINotFoundException(String.format("%s is an invalid job parameter.", v.getGraphKey()));
}
//Within the database record is the validation message as well as the regex used to validate the incoming value,
//we set the message and regex in the GraphPropertyDTO object for cross validation later on in the validator.
GraphJobProperty jobProperty = jobMap.get(graphKey);
v.setValidationMessage(jobProperty.getValidationMessage());
v.setValidationRegex(jobProperty.getValidationRegex());
});
//Return the graphJobDTO object for validation
return graphJobDTO;
}
}
约束验证器
@Slf4j
public class GraphRegexValidator implements ConstraintValidator<GraphRegex, GraphJobPropertyDTO> {
@Override
public void initialize(final GraphRegex graphRegex) {
}
@Override
public boolean isValid(final GraphJobPropertyDTO dto, final ConstraintValidatorContext context) {
String regex = dto.getValidationRegex();
String value = dto.getGraphValue();
if(regex != null && value != null && !Pattern.matches(regex, value)) {
log.debug("isValid {} - {}", false, dto);
addConstraintViolation(context, getMessage(dto, context));
return false;
}
log.debug("isValid {} - {}", true, dto);
return true;
}
private void addConstraintViolation(ConstraintValidatorContext context, String message) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode("graphKey").addConstraintViolation();
}
private String getMessage(GraphJobPropertyDTO dto, ConstraintValidatorContext context) {
return dto.getValidationMessage() != null ? String.format(dto.getValidationMessage(), dto.getGraphValue()) :
context.getDefaultConstraintMessageTemplate();
}
}
DTO
@Data
public class GraphJobDTO {
@NotNull
Long graphId;
@NotNull
String graph;
@Valid
List<GraphJobPropertyDTO> graphProperties;
}
@Data
@GraphRegex
public class GraphJobPropertyDTO {
String graphKey;
String graphValue;
String validationMessage;
String validationRegex;
}