未使用Spring DATA REST序列化的Bean验证(JSR-303)错误

我正在使用Spring Boot 2.1.11,Spring DATA REST,Hibernate。我正在尝试使JRS 303与SDR一起使用,以便获取验证错误的JSON响应。

到目前为止,它仍然有效,但是仅当我执行POST时,当我执行PATCH时,我才收到意外响应。看来ConstraintViolationException已按照here的说明进行了包装。

要给出一个完整的场景,这是我的配置:

CustomConfiguration.java:

@Configuration
@EnableRetry
@EnableTransactionmanagement
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
// To activate the Spring Data Envers repository factory
@EnableJpaRepositories(basePackages = "server.repositories",repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
public class CustomConfiguration {
    public static CustomConfiguration INSTANCE;

    @PostConstruct
    public void init() {
        INSTANCE = this;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public static SpringSecurityAuditorAware springSecurityAuditorAware() {
        return new SpringSecurityAuditorAware();
    }

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:/i18n/messages");

        messageSource.setUseCodeAsDefaultMessage(false);
        messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
        messageSource.setfallbackToSystemLocale(false);
        return messageSource;
    }

    @Bean
    public MessageSourceaccessor messageSourceaccessor() {
        return new MessageSourceaccessor(messageSource());
    }

    @Bean
    public LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setValidationmessageSource(messageSource());
        return factoryBean;
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
        methodValidationPostProcessor.setValidator(validator());
        return methodValidationPostProcessor;
    }

    @Bean
    public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }


    @Bean
    FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
        FilterRegistrationBean<ForwardedHeaderFilter> bean = new FilterRegistrationBean<>();
        bean.setfilter(new ForwardedHeaderFilter());
        return bean;
    }
}

GlobalRepositoryRestConfigurer.java

@Configuration
public class GlobalRepositoryRestConfigurer implements RepositoryRestConfigurer {

    @Autowired(required = false)
    private Jackson2ObjectMapperBuilder objectMapperBuilder;

    @Autowired
    private Validator validator;


    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.getcorsRegistry().addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins)
                .allowedHeaders(corsAllowedHeaders).allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);

    }

    @Override
    public void configureConversionService(ConfigurableConversionService conversionService) {

    }

    @Bean
    public ValidationExceptionSerializer validationExceptionSerializer() {
        return new ValidationExceptionSerializer();
    }

    @Bean
    public CustomValidationExceptionSerializer customValidationExceptionSerializer() {
        return new CustomValidationExceptionSerializer();
    }

    @Bean
    public ConstraintViolationExceptionSerializer constraintViolationExceptionSerializer() {
        return new ConstraintViolationExceptionSerializer();
    }


    @Bean
    public Module customJacksonmodule() {
        SimpleModule customJacksonmodule = new SimpleModule();
        customJacksonmodule.addSerializer(ConstraintViolationException.class,constraintViolationExceptionSerializer());
        customJacksonmodule.addSerializer(ValidationException.class,validationExceptionSerializer());
        customJacksonmodule.addSerializer(it.rebus.server.exceptions.ValidationException.class,customValidationExceptionSerializer());
        return customJacksonmodule;
    }



    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        validatingListener.addValidator("beforeCreate",validator);
        validatingListener.addValidator("beforeSave",validator);
    }

    @Override
    public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        messageConverters.add(new ResourceHttpMessageConverter());
    }

}

WebMvcConfiguration.java

@Configuration
@EnableHypermediaSupport(type = { HypermediaType.HAL })
public class WebMvcConfiguration implements WebMvcConfigurer {

    private Validator validator;

    @Bean
    public LocaleResolver localeResolver() {
        return new SmartLocaleResolver();
    }

    public class SmartLocaleResolver extends CookieLocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            String acceptLanguage = request.getHeader("accept-Language");
            if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
                return super.determineDefaultLocale(request);
            }
            return request.getLocale();
        }
    }

    @Autowired
    public WebMvcConfiguration(Validator validator) {
        this.validator = validator;
    }

    @Override
    public Validator getValidator() {
        return validator;
    }


    @Bean
    public CustomErrorAttributes myCustomErrorAttributes() {
        return new CustomErrorAttributes();
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(corsMapping).exposedHeaders(corsExposedHeaders).allowedOrigins(corsAllowedOrigins)
                .allowedHeaders(corsAllowedHeaders).allowedMethods(corsAllowedMethod).maxAge(corsMaxAge);
    }

}

RequestBodyValidationProcessor.java

@ControllerAdvice
@Log4j2
public class RequestBodyValidationProcessor extends RequestBodyAdviceAdapter {

    @Autowired
    private LocalValidatorFactoryBean validator;

    @Override
    public boolean supports(final MethodParameter methodParameter,final Type targetType,final Class<? extends HttpMessageConverter<?>> converterType) {
        final Annotation[] parameterAnnotations = methodParameter.getParameterAnnotations();
        for (final Annotation annotation : parameterAnnotations) {
            if (annotation.annotationType().equals(Valid.class)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public Object afterBodyRead(final Object body,final HttpInputMessage inputMessage,final MethodParameter parameter,final Class<? extends HttpMessageConverter<?>> converterType) {
        final Object obj = super.afterBodyRead(body,inputMessage,parameter,targetType,converterType);

        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj);
        if (!constraintViolations.isEmpty()) {
            throw new ConstraintViolationException(constraintViolations);
        }
        return obj;
    }

}

ApplicationExceptionHandler.java

@RestControllerAdvice
@Log4j2
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {

    @Autowired
    private ErrorLogRepository errorLogRepository;

    @Autowired
    private MessageSource messageSource;

    private MessageSourceaccessor messageSourceaccessor = null;

    @PostConstruct
    public void postConstruct() {
        Assert.notNull(messageSource,"MessageSource must not be null!");
        this.messageSourceaccessor = new MessageSourceaccessor(messageSource);
    }



    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,HttpHeaders headers,HttpStatus status,WebRequest request) {
        HttpServletRequest httpServlet = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.error(String.format("MethodArgumentNotValidException caused from client with ip  %s. Error: %s",AppUtils.getRemoteIp(httpServlet),ExceptionUtils.getRootCauseMessage(ex)));

        return response(HttpStatus.BAD_REQUEST,new HttpHeaders(),buildGenericError(ex,ExceptionCode.ERROR_CODE,httpServlet,HttpStatus.BAD_REQUEST,LocaleContextHolder.getLocale()));
    }

    @Override
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,WebRequest request) {
        HttpServletRequest httpServlet = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.error(String.format("HttpMediaTypeNotSupportedException caused from client with ip  %s. Error: %s",LocaleContextHolder.getLocale()));
    }

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,WebRequest request) {
        HttpServletRequest httpServlet = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.error(String.format("HttpMessageNotReadableException caused from client with ip  %s. Error: %s",ExceptionUtils.getRootCauseMessage(ex)));

        if (ExceptionUtils.getRootCauseMessage(ex).contains("Duplicate entry")) {

            return response(HttpStatus.CONFLICT,buildIntegrityError(ex,HttpStatus.CONFLICT,LocaleContextHolder.getLocale()));
        } else {
            return response(HttpStatus.BAD_REQUEST,LocaleContextHolder.getLocale()));
        }
    }


    @Override
    protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex,WebRequest request) {
        HttpServletRequest httpServlet = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.error("",ex);
        return response(HttpStatus.INTERNAL_SERVER_ERROR,ExceptionCode.INTERNAL_ERROR,HttpStatus.INTERNAL_SERVER_ERROR,LocaleContextHolder.getLocale()));
    }


    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<?> handleConflictException(DataIntegrityViolationException ex,HttpServletRequest request,Locale locale) throws Exception {

        if (ex instanceof RepositoryConstraintViolationException) {
            return response(HttpStatus.BAD_REQUEST,new RepositoryConstraintViolationExceptionmessage((RepositoryConstraintViolationException) ex,messageSourceaccessor));
        }


        return response(HttpStatus.CONFLICT,request,locale));
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<?> handleValidationException(ConstraintViolationException ex,Locale locale) throws Exception {
        try {
            ResponseEntity<ConstraintViolationException> response = new ResponseEntity<ConstraintViolationException>(ex,HttpStatus.BAD_REQUEST);
            return response;
        } catch (Exception e) {
            log.error("",e);
        }
        return response(HttpStatus.BAD_REQUEST,"");
    }


    @ExceptionHandler(TransactionSystemException.class)
    public ResponseEntity<?> handleTransactionSystemException(TransactionSystemException ex,Locale locale) throws Exception {
        if (ex.getcause() instanceof RollbackException) {
            RollbackException rollbackException = (RollbackException) ex.getcause();
            if (rollbackException.getcause() instanceof ApplicationExceptionInterface) {
                ApplicationExceptionInterface finalException = (ApplicationExceptionInterface) rollbackException.getcause();
                return response(HttpStatus.BAD_REQUEST,buildGenericError(rollbackException.getcause(),ExceptionCode.fromCode(finalException.getcode()),LocaleContextHolder.getLocale()));
            }
        }

        return response(HttpStatus.INTERNAL_SERVER_ERROR,LocaleContextHolder.getLocale()));
    }

    @ExceptionHandler(InternalException.class)
    public ResponseEntity<?> handleInternalException(InternalException ex,Locale locale) throws Exception {
        return response(HttpStatus.BAD_REQUEST,ExceptionCode.fromCode(ex.getcode()),LocaleContextHolder.getLocale()));
    }


    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<?> handleFileUpload(MaxUploadSizeExceededException ex,Locale locale) throws Exception {
        log.error(String.format("Received a file too big from %s. Error: %s",AppUtils.getRemoteIp(request),ExceptionUtils.getRootCauseMessage(ex)));
        return response(HttpStatus.BAD_REQUEST,LocaleContextHolder.getLocale()));
    }


    private JsonException buildIntegrityError(final Throwable exception,final HttpServletRequest request,final HttpStatus httpStatus,Locale locale) {
        return buildIntegrityError(exception,request.getRequesturi(),httpStatus,locale);
    }


    private JsonException buildIntegrityError(final Throwable exception,String requesturi,Locale locale) {
        String finalMessage = "";
        String rootMsg = ExceptionUtils.getRootCauseMessage(exception);
        Optional<Map.Entry<String,ExceptionCode>> entry = constraintCodeMap.entryset().stream().filter((it) -> rootMsg.contains(it.getKey())).findAny();
        if (entry.isPresent()) {
            finalMessage = messageSource.getMessage(entry.get().getvalue().getcode(),new Object[] {},locale);
        } else {
            finalMessage = messageSource.getMessage(ExceptionCode.INTEGRITY_VIOLATION.getcode(),new Object[] { rootMsg },locale);
        }
        JsonException jsonException = new JsonException();
        jsonException.setError(httpStatus.getReasonPhrase());
        jsonException.setStatus(httpStatus.value());
        jsonException.setException(exception.getclass().getName());
        jsonException.setMessage(finalMessage);
        jsonException.setPath(requesturi);

        return jsonException;
    }


    private JsonException buildGenericError(final Throwable exception,final ExceptionCode exceptionCode,Locale locale) {
        String rootMsg = ExceptionUtils.getRootCauseMessage(exception);
        String finalMessage = "";
        Object[] args = new Object[]{rootMsg};

        if (exception instanceof ApplicationExceptionInterface) {
            args = ((ApplicationExceptionInterface) exception).getargs();
        }
        try {
            // Not storing in DB ValidationExceptions
            if (!(exception instanceof ValidationException)) {
                try {
                    ErrorLog errorLog = dbStoreException(exception);
                    String dbCode = messageSource.getMessage(ExceptionCode.ERROR_CODE.getcode(),new Object[]{errorLog.getcode()},locale);

                    finalMessage = dbCode + " " + MessageUtils.getMessage(locale,exceptionCode.getcode(),args);
                } catch (Exception e) {
                    finalMessage = messageSource.getMessage(exceptionCode.getcode(),args,locale);
                }
            } else {
                finalMessage = messageSource.getMessage(exceptionCode.getcode(),locale);
            }
        } catch (Exception e) {
            finalMessage = messageSource.getMessage(exceptionCode.getcode(),locale);
        }

        JsonException jsonException = new JsonException();
        jsonException.setError(httpStatus.getReasonPhrase());
        jsonException.setStatus(httpStatus.value());
        jsonException.setException(exception.getclass().getName());
        jsonException.setMessage(finalMessage);
        jsonException.setPath(request.getRequesturi());
        if (exception instanceof ApplicationExceptionInterface) {
            jsonException.setErrorCode(((ApplicationExceptionInterface) exception).getcode());
        }

        return jsonException;
    }

    private static <T> ResponseEntity<T> response(HttpStatus status,T body) {

        Assert.notNull(headers,"Headers must not be null!");
        Assert.notNull(status,"HttpStatus must not be null!");

        return new ResponseEntity<T>(body,headers,status);
    }


    private ErrorLog dbStoreException(Throwable throwable) {
        HttpServletRequest httpServlet = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        ErrorLog errorLog = new ErrorLog();
        errorLog.setTitle(ExceptionUtils.getRootCauseMessage(throwable));
        errorLog.setText(ExceptionUtils.getStackTrace(throwable));
        errorLog.setLocation(AppUtils.getExceptionPosition(throwable));
        errorLog.setRemoteAddress(AppUtils.getRemoteIp(httpServlet));
        return errorLogRepository.save(errorLog);
    }
}

我正在使用存储库中公开的保存/更新方法:

@Transactional
public interface ContactRepository extends JpaRepository<Contact,Long>,RevisionRepository<Contact,Long,Integer> {
}

这是未使用的控制器,因为我正在使用SDR通过存储库公开端点:

    @RepositoryRestController
@Log4j2
public class ContactController extends RevisionController<Contact> {
@Autowired
private LocalValidatorFactoryBean validator;

@PersistenceContext
private EntityManager entityManager;

@Autowired
private ContactRepository contactRepository;

@Autowired
private ContactService contactService;

@Autowired
private NoteService noteService;

@Autowired
private MessageSource messageSource;

@Autowired
private MediaService mediaService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@SuppressWarnings("rawtypes")
@Autowired
private PagedResourcesAssembler pagedResourcesAssembler;

@InitBinder
protected void initBinder(WebdataBinder binder) {
    binder.addValidators(validator);
}

@PreAuthorize("permitAll()")
@GetMapping(path = "/contacts/types")
public ResponseEntity<?> getcontactTypes(Locale locale) {
    return ResponseEntity.ok(AppUtils.listToResourcesList(Arrays.asList(PersonType.values())));
}

@GetMapping(path = "/contacts/{id:[0-9]+}")
public ResponseEntity<?> findOne(@PathVariable("id") long id,Locale locale,PersistentEntityResourceAssembler resourceAssembler) {
    //
}

@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_BACK_OFFICE','ROLE_accOUNTANT')")
@PostMapping(path = "/contacts/searches")
public ResponseEntity<?> search(@RequestBody(required = true) List<Filter> filters,Pageable pageable,'ROLE_accOUNTANT')")
@PostMapping(path = "/contacts/{id}/enableWallet")
public ResponseEntity<?> enableWallet(@PathVariable("id") long contactId,PersistentEntityResourceAssembler resourceAssembler) {
    //

}


@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_accOUNTANT')")
@PostMapping(path = "/contacts/{id}/balanceThreshold")
public ResponseEntity<?> balanceThreshold(@PathVariable("id") long contactId,@RequestBody(required = true) BigDecimal balanceThreshold,PersistentEntityResourceAssembler resourceAssembler) {
    //
}


@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_accOUNTANT','ROLE_BACK_OFFICE')")
@SuppressWarnings("unchecked")
@GetMapping(path = "/contacts/{id}/movements")
public ResponseEntity<?> getMovements(@PathVariable("id") long contactId,@RequestParam(value = "from",required = false) Instant from,@RequestParam(value = "until",required = false) Instant until,@RequestParam(value = "description",required = false) String description,'ROLE_accOUNTANT')")
@GetMapping(path = "/contacts/{id}/notes")
public ResponseEntity<?> getNotes(@PathVariable(value = "id") long id,PersistentEntityResourceAssembler resourceAssembler) {
    //

}

@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_accOUNTANT')")
@GetMapping(path = "/contacts/{id}/auditLogs")
public ResponseEntity<?> getauditLogs(@PathVariable(value = "id") long id,'ROLE_accOUNTANT')")
@GetMapping(path = "/contacts/{id}/media")
public ResponseEntity<?> getMedia(@PathVariable(value = "id") long id,'ROLE_accOUNTANT')")
@PostMapping(path = "/contacts/{id}/notes")
public ResponseEntity<?> addNote(@PathVariable(value = "id") long id,@Valid @RequestBody(required = true) Note note,'ROLE_accOUNTANT')")
@GetMapping(path = "/contacts/{id}/revisions")
public ResponseEntity<?> findRevisions(@PathVariable(value = "id") Long id,Pageable pageable) {
    //
}


@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_accOUNTANT')")
@GetMapping(path = "/contacts/{id}/revisions/{revid}")
public ResponseEntity<?> getchanges(@PathVariable(value = "id") Long id,@PathVariable(value = "revid") Integer revId,'ROLE_accOUNTANT')")
@PostMapping(path = "/contacts/{id}/media",consumes = {"multipart/form-data"})
public ResponseEntity<?> addMedia(@PathVariable("id") long id,@RequestPart("files") List<MultipartFile> files,PersistentEntityResourceAssembler resourceAssembler) {
    //
}


@PreAuthorize("permitAll()")
@PostMapping(path = "/contacts/resetPassword")
public ResponseEntity<?> resetPassword(@RequestBody(required = true) String username,PersistentEntityResourceAssembler resourceAssembler) {
    //
}


@PreAuthorize("permitAll()")
@PostMapping(path = "/contacts/register")
public ResponseEntity<?> register(@Valid @RequestBody(required = true) Contact contact,PersistentEntityResourceAssembler resourceAssembler) {
   //
}

}

这是bean的第一部分:

@Entity
@EntityListeners(ContactListener.class)
@Table(indexes = {@Index(name = "idx_enabled",columnList = "enabled"),@Index(name = "idx_name",columnList = "name")})
@ScriptAssert.List({
        //CHECK TAX CODE VALIDITY
        @ScriptAssert(lang = "javascript",script = "_.taxCode != null && _.taxCode != '' && _.personType=='NATURAL_PERSON'?_.isTaxCodeValid(_.taxCode,_.country):true",alias = "_",reportOn = "taxCode",message = "{contact.invalid.taxcode}"),//CHECK VAT NUMber VALIDITY
        @ScriptAssert(lang = "javascript",script = "_.vatNumber != null && _.vatNumber != '' && _.personType=='LEGAL_PERSON'?_.isVatNumberValid(_.vatNumber,reportOn = "vatNumber",message = "{contact.invalid.vatNumber}")
})
@Data
@EqualsAndHashCode(callSuper = true,onlyExplicitlyIncluded = true)
@AllArgsConstructor
@Builder
@ToString(callSuper = true)
public class Contact extends AbstractEntity {

    @Builder.Default
    @Audited
    @NotNull
    @Column(nullable = false,columnDefinition = "VARCHAR(255) DEFAULT 'LEGAL_PERSON'")
    @Enumerated(EnumType.STRING)
    private PersonType personType = PersonType.LEGAL_PERSON;

    @Audited
    @NotEmpty
    @Size(min = 3,max = 255)
    @Column(nullable = false)
    private String name;

    @OneToOne(cascade = CascadeType.ALL,orphanRemoval = true,fetch = FetchType.EAGER)
    private account account;

    @Audited
    @NotBlank
    private String address;

但是,当我执行POST保存该实体时,出现了一些错误,我得到了如下信息:

{"errors":[{"entity":"Contact","property":"address","invalidValue":null,"message":"Il campo non può essere vuoto. Inserire un valore valido e ripetere l'operazione."},{"entity":"Contact","property":"personType","property":"city","message":"Il campo non può essere vuoto. Inserire un valore valido e ripetere l'operazione."}]}

很好。当我用表格中的一些错误进行PATCH时,我得到以下答复:

{"timestamp":"2020-01-08T19:42:31.633+0000","status":400,"error":"Bad Request","exception":"org.springframework.http.converter.HttpMessageNotReadableException","message":"Cod. errore [494-577]. Cod. errore [ConstraintViolationException: Validation failed for classes [it.test.server.model.accounts.Contact] during update time for groups [javax.validation.groups.Default,]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='{contact.invalid.taxcode}',propertyPath=taxCode,rootBeanClass=class it.test.server.model.accounts.Contact,messageTemplate='{contact.invalid.taxcode}'}\n]].","path":"/api/v1/contacts/5752","errorCode":null}

那是错误的。 在第一种情况下,异常是从@ExceptionHandler(DataIntegrityViolationException.class)方法捕获的,在第二种情况下是从handleHttpMessageNotReadable()方法捕获的。

您是否有任何提示以正确的方式指出我的问题?

s1y2f3x4 回答:未使用Spring DATA REST序列化的Bean验证(JSR-303)错误

您可以使用{em>扩展的ProblemHandling,{em} ValidationAdviceTrait使用MethodArgumentNotValidAdviceTraitConstraintViolationAdviceTrait。您可以使用带有标题,状态代码,详细信息等的 ProblemBuilder 自定义消息。还可以使用handleException(ExceptionClass ex,NativeWebRequest request)处理其他异常。两种方法 POST PATCH 都可以正常工作。

@ControllerAdvice
public class ExceptionTranslator implements ProblemHandling,SecurityAdviceTrait {

    private static final String FIELD_ERRORS_KEY = "fieldErrors";
    private static final String MESSAGE_KEY = "message";
    private static final String PATH_KEY = "path";
    private static final String VIOLATIONS_KEY = "violations";

    @Value("${jhipster.clientApp.name}")
    private String applicationName;

    private final Logger log = LoggerFactory.getLogger(ExceptionTranslator.class);

    /**
     * Post-process the Problem payload to add the message key for the front-end if needed.
     */
    @Override
    public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity,NativeWebRequest request) {
        log.debug("process invalid input(s) for entity: {},request: {}",entity,request);

        List<String> messages = new ArrayList<>();
        String messageStr = "";

        if (entity == null) {
            return entity;
        }

        Problem problem = entity.getBody();
        try {
            ((ConstraintViolationProblem) problem).getViolations().forEach(m-> messages.add(m.getMessage()));
            messageStr = messages.toString();
            log.debug("Error message: {}",messageStr);
        } catch (ClassCastException e) {
            log.debug("Cannot cast Problem to ConstraintViolationProblem");
            messageStr = problem.getDetail();
            log.debug("Error message detail: {}",messageStr);
        }

        if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
            return entity;
        }
        ProblemBuilder builder = Problem.builder()
            .withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
            .withStatus(problem.getStatus())
            .withTitle(problem.getTitle())
            .withDetail(messageStr)
            .with(PATH_KEY,request.getNativeRequest(HttpServletRequest.class).getRequestURI());

        if (problem instanceof ConstraintViolationProblem) {
            builder
                .with(VIOLATIONS_KEY,((ConstraintViolationProblem) problem).getViolations())
                .with(MESSAGE_KEY,ErrorConstants.ERR_VALIDATION)
                .withDetail(messageStr);
        } else {
            builder
                .withCause(((DefaultProblem) problem).getCause())
                .withDetail(messageStr)
                .withInstance(problem.getInstance());
            problem.getParameters().forEach(builder::with);
            if (!problem.getParameters().containsKey(MESSAGE_KEY) && problem.getStatus() != null) {
                builder.with(MESSAGE_KEY,"error.http." + problem.getStatus().getStatusCode()).withDetail(messageStr);
            }
        }
        return new ResponseEntity<>(builder.build(),entity.getHeaders(),entity.getStatusCode());
    }

    @Override
    public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,@Nonnull NativeWebRequest request) {
        log.debug("Handle invalid method arguments: {}",ex.toString());

        BindingResult result = ex.getBindingResult();
        List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
            .map(f -> new FieldErrorVM(f.getObjectName().replaceFirst("DTO$",""),f.getField(),f.getCode()))
            .collect(Collectors.toList());

        List<String> messages = new ArrayList<>();
        fieldErrors.forEach(m -> messages.add("Please provide a valid value for " + m.getField()));
        log.debug("Error message: {}",messages);

        Problem problem = Problem.builder()
            .withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
            .withTitle("Method argument not valid")
            .withStatus(defaultConstraintViolationStatus())
            .with(MESSAGE_KEY,ErrorConstants.ERR_VALIDATION)
            .with(FIELD_ERRORS_KEY,fieldErrors)
            .withDetail(messages.toString())
            .build();
        return create(ex,problem,request);
    }
}

对于 POST,修补程序

,响应如下
{
  "type": "https://www.jhipster.tech/problem/constraint-violation","title": "Method argument not valid","status": 400,"detail": "[Please provide a valid value for category]","path": "/api/categories","message": "error.validation","fieldErrors": [
    {
      "objectName": "botCategory","field": "category","message": "Pattern"
    }
  ]
}

@PatchMapping("/endpoint")
    public ResponseEntity<yourResponseDTO> create(@Valid @RequestBody yourRequestDTO requestDTO) 
{...}

@PostMapping("/endpoint")
    public ResponseEntity<yourResponseDTO> update(@Valid @RequestBody yourRequestDTO requestDTO) 
{...}

public class yourRequestDTO {

    private Long id;

    @NotNull(message = ErrorConstants.INVALID_NAME)
    @Size(max = 50,message = ErrorConstants.INVALID_SIZE_50)
    @Pattern(regexp = Constants.BOTCATEGORY_REGEX,message = ErrorConstants.INVALID_BOT_CATEGORY_PATTERN)
    private String category;

    @Size(max = 100,message = ErrorConstants.INVALID_SIZE_100)
    private String description;
}

相关进口

import io.github.jhipster.web.util.HeaderUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.DefaultProblem;
import org.zalando.problem.Problem;
import org.zalando.problem.ProblemBuilder;
import org.zalando.problem.Status;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import org.zalando.problem.spring.web.advice.security.SecurityAdviceTrait;
import org.zalando.problem.violations.ConstraintViolationProblem;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.PersistenceException;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
,

最后,我解决了这个问题。确实配置是正确的,但是我有一个例外,该例外仅在DEBUG级别可见(感谢@Angelo Immediata):

17/01/2020 14:51:35,016 DEBUG http-nio-8081-exec-3 ServletInvocableHandlerMethod:174 - Could not resolve parameter [1] in public org.springframework.http.ResponseEntity<org.springframework.hateoas.ResourceSupport> org.springframework.data.rest.webmvc.RepositoryEntityController.patchItemResource(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.data.rest.webmvc.PersistentEntityResource,java.io.Serializable,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler,org.springframework.data.rest.webmvc.support.ETag,java.lang.String) throws org.springframework.web.HttpRequestMethodNotSupportedException,org.springframework.data.rest.webmvc.ResourceNotFoundException: Could not read payload!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction (through reference chain: it.test.server.model.accounts.Contact["country"])

问题是我在CountryRepository中重写了此方法:

    @Override
    @PreAuthorize("permitAll()")
    Optional<Country> findById(Long id);

删除此方法可以解决此问题。

本文链接:https://www.f2er.com/2801027.html

大家都在问