Как правильно создать собственное исключение при использовании @ javax.validation.Valid? - PullRequest
0 голосов
/ 14 июля 2020

Как правильно создать собственное исключение при использовании @javax.validation.Valid?

Я использую @Valid в контроллере и @AssertTrue для проверки полей тела запроса.

public ResponseEntity<Foo> createFoo(
    @Valid @RequestBody Foo FooRequest ...
    @AssertTrue()
    public boolean isFooValid() {
        if (invalid)
            return false;
        ...
    }

Тем не менее, я хочу в некоторых условиях использовать настраиваемый класс Exception.

    @AssertTrue()
    public boolean isFooValid() {
        if (invalid)
            return false;
        ...

        // note below
        if (invalidInAnotherCondition)
            throw new CustomizedException(...);
    }

Я знаю, что это нежелательный способ использовать @Valid в контроллере и @AssertTrue. Тем не менее, поскольку я могу создать свой собственный класс Exception, который содержит настраиваемую информацию об ошибке, с удобством @Valid.

Однако возникает ошибка.

javax.validation.ValidationException: HV000090: Unable to access isFooValid
    at org.hibernate.validator.internal.util.ReflectionHelper.getValue(ReflectionHelper.java:245)
    at org.hibernate.validator.internal.metadata.location.GetterConstraintLocation.getValue(GetterConstraintLocation.java:89)
    at org.hibernate.validator.internal.engine.ValueContext.getValue(ValueContext.java:235)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:549)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:515)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:485)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:447)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:397)
    at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:173)
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:117)
    at org.springframework.boot.autoconfigure.validation.ValidatorAdapter.validate(ValidatorAdapter.java:70)
    at org.springframework.validation.DataBinder.validate(DataBinder.java:889)
    at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.validateIfApplicable(AbstractMessageConverterMethodArgumentResolver.java:266)
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:137)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:523)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:376)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.reflect.InvocationTargetException: null
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.hibernate.validator.internal.util.ReflectionHelper.getValue(ReflectionHelper.java:242)
    ... 70 common frames omitted
Caused by: com.finda.services.finda.common.exception.CustomizedException: 'df282e0d-1205-4574-adaa-0af819af66c0' 
    at ...
    ... 75 common frames omitted

Я думаю, что это происходит потому, что изначально , @AssertTrue генерирует собственное исключение, которое должно обрабатываться внутренней логикой; Однако настроенное выброшенное исключение неприемлемо, что можно увидеть в Caused by: java.lang.reflect.InvocationTargetException: null и javax.validation.ValidationException: HV000090: Unable to access isFooValid

Итак, мой последний вопрос ниже:

Могу ли я обойти эту ошибку, все еще бросая индивидуальное исключение?

Мне очень приятно, что вы заранее прочитали это длинное сообщение.

1 Ответ

1 голос
/ 14 июля 2020

Рассмотрим пример ниже, где я реализовал что-то вроде того, что вы просите:

@RestController
@RequestMapping("/accounts")
public class SavingsAccountController {

   private final BankAccountService accountService;

   @Autowired
   public SavingsAccountController(SavingsAccountService accountService) {
       this.accountService = accountService;
   }

   @PutMapping("withdraw")
   public ResponseEntity<AccountBalance> onMoneyWithdrawal(@RequestBody @Validated WithdrawMoney withdrawal, BindingResult errors) {

       //this is the validation barrier
       if (errors.hasErrors()) {
           throw new ValidationException(errors);
       }

       double balance = accountService.withdrawMoney(withdrawal);
       return ResponseEntity.ok(new AccountBalance(
               withdrawal.getAccountNumber(), balance));
   }

   @PutMapping("save")
   public ResponseEntity<AccountBalance> onMoneySaving(@RequestBody @Validated SaveMoney savings, BindingResult errors) {

       //this is the validation barrier
       if (errors.hasErrors()) {
           throw new ValidationException(errors);
       }

       double balance = accountService.saveMoney(savings);
       return ResponseEntity.ok(new AccountBalance(
               savings.getAccountNumber(), balance));
   }
}

В приведенном выше коде мы используем Bean Validation, чтобы проверить, что пользовательский DTO содержит достоверную информацию. Любые ошибки, обнаруженные в DTO, предоставляются через переменную BindingResult errors, из которой разработчик может извлечь все подробности того, что пошло не так на этапе проверки.

Чтобы разработчикам было легче с ними справиться. В этом шаблоне в приведенном выше коде я просто оборачиваю BindingResult в пользовательский ValidationException, который знает, как извлекать сведения об ошибке проверки.

public class ValidationException extends RuntimeException {

   private final BindingResult errors;

   public ValidationException(BindingResult errors) {
       this.errors = errors;
   }

   public List<String> getMessages() {
       return getValidationMessage(this.errors);
   }


   @Override
   public String getMessage() {
       return this.getMessages().toString();
   }


   //demonstrate how to extract a message from the binging result
   private static List<String> getValidationMessage(BindingResult bindingResult) {
       return bindingResult.getAllErrors()
               .stream()
               .map(ValidationException::getValidationMessage)
               .collect(Collectors.toList());
   }

   private static String getValidationMessage(ObjectError error) {
       if (error instanceof FieldError) {
           FieldError fieldError = (FieldError) error;
           String className = fieldError.getObjectName();
           String property = fieldError.getField();
           Object invalidValue = fieldError.getRejectedValue();
           String message = fieldError.getDefaultMessage();
           return String.format("%s.%s %s, but it was %s", className, property, message, invalidValue);
       }
       return String.format("%s: %s", error.getObjectName(), error.getDefaultMessage());
   }

}

Обратите внимание, что в моем определении контроллера я не использую Аннотации Bean Validation @ Valid , но аналог Spring @ Validated , но под капотом Spring будет использовать Bean Validation.

Как сериализовать настраиваемое исключение ?

В приведенном выше коде ValidationException будет выброшено, если полезная нагрузка недействительна. Как контроллер должен создать ответ для клиента?

Есть несколько способов справиться с этим, но, возможно, самым простым решением является определение класса, помеченного как @ControllerAdvice. В этом аннотированном классе мы разместим наши обработчики исключений для любого указанного c исключения, которое мы хотим обработать, и превратим их в допустимый объект ответа, чтобы вернуться к нашим клиентам:

@ControllerAdvice
public class ExceptionHandlers {

   @ExceptionHandler
   public ResponseEntity<ErrorModel> handle(ValidationException ex) {
       return ResponseEntity.badRequest()
                            .body(new ErrorModel(ex.getMessages()));
   }

   //...
}

Я написал несколько другие примеры этого и других методов проверки с помощью Spring на случай, если вам может быть интересно узнать больше об этом .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...