Spring

@javax.validation.Valid를 사용할 때 적절한 방식으로 사용자 정의 예외를 발생시키는 방법은 무엇입니까? 물어보다

기록만이살길 2022. 11. 3. 12:43
반응형

@javax.validation.Valid를 사용할 때 적절한 방식으로 사용자 정의 예외를 발생시키는 방법은 무엇입니까? 물어보다

1. 질문(문제점):

사용할 때 적절한 방식으로 사용자 지정 예외를 throw하는 방법은 @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자체 Exception 자체를 던지고 내부 논리를 통해 처리되기 때문에 발생한다고 생각합니다. Caused by: java.lang.reflect.InvocationTargetException: null그러나 사용자 정의된 throw된 예외는 다음 에서 볼 수 있는 허용되지 않습니다.javax.validation.ValidationException: HV000090: Unable to access isFooValid

그래서 제 마지막 질문은 아래와 같습니다.

이 오류를 무시하고 여전히 사용자 정의 예외를 던질 수 있습니까?

긴 글을 미리 읽어주셔서 정말 감사합니다.

2. 해결방안:

다음은 내가 사용한 솔루션입니다(이 질문에 직접 답변하지 않을 수 있지만 나와 비슷한 의도로 이 페이지를 방문하는 다른 사람들에게 도움이 될 수 있음).

내 목표는 무엇보다도 잘못된 개체가 포함된 요청을 보낸 클라이언트에게 사용자 지정 오류 메시지로 응답하는 것이었습니다.

  1. 내 컨트롤러 에서 매개 변수 앞에 javax.validation.Valid@Valid 의 표준 어노테이션을 사용합니다.
  2. 내 엔티티 클래스에서 표준 유효성 검사 제약 조건을 사용합니다(예: @NotNull(message = "Field XYZ has to be provided")javax.validation.constraints.NotNull ) .
  3. 내 ControllerAdvice 클래스에서 ValidationException을 잡습니다.
@ControllerAdvice 
public class MyControllerAdvice extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException exception, 
            HttpHeaders headers, 
            HttpStatus status, 
            WebRequest request) {
        return new ResponseEntity<>(
                new JSONObject().put("message", extractValidationMessage(exception)).toString(),
                HttpStatus.BAD_REQUEST);
    }

    private String extractValidationMessage(MethodArgumentNotValidException exception) {
        String exceptionMessage = exception.getMessage();
        String[] messageParts = exceptionMessage.split(";");
        String finalPart = messageParts[messageParts.length -1];

        return finalPart.trim().replaceAll("default message \\[|]]","");
    } 
}
  1. 그러면 400 오류 코드와 다음 스타일의 본문이 반환됩니다.

{"message":"필드 XYZ를 제공해야 합니다."}

62896233
반응형