Java / Kotlin / Spring Boot. How can we automatically retrieve parameter values when an exception occurs?

半腔热情 提交于 2021-01-28 11:14:59

问题


Considering that we are using Kotlin, Spring Boot, annotations and other related libraries.

If we have a situation in which our code throws an exception, how could we automatically retrieve the method parameters values in the moment of that exception?

Can we do this using AOP, Spring Interceptors or other techniques?

We would like to have this to enrich our error messages so we could replicate the errors from where they occurred.

Please note that we are searching for a solution that we don't need to annotate all possible methods but something that would handle the code when an exception occurs. We can use the Java stacktrace elements to retrieve some useful information like the method, line and file where the exception occurred but we don't have the parameters values there.

In Spring we have the Controller Advice feature that we can use to handle all of our exceptions, so we would like to put something there for this purpose, for example.

Edit

Adding some example code:

fun exceptionHandler(throwable: Throwable) {
    logger.severe("""
        Error ${throwable.message}
        File: ${throwable.stackTrace[2].fileName}
        Class: ${throwable.stackTrace[2].className}
        Method: ${throwable.stackTrace[2].methodName}
        Line: ${throwable.stackTrace[2].lineNumber}
        Parameters: ## Somehow get the parameters values here, in this case "Hello, 1, false"
    """.trimIndent())
    }

fun myController() {
    myMethodWithErrors("Hello", 1, false)
}

fun myMethodWithErrors(param1: String, param2: Int, param3: Boolean) {
    throw RuntimeException("Some bad thing happened here when executing this code.")
}

回答1:


I assume that you were talking about rest API parameters and not every single java method parameter. You can implement controller advice that captures all exceptions in your rest API calls.

@ControllerAdvice
public class ExceptionHandler {

    @ExceptionHandler(value = [Exception::class])
    @ResponseBody
    fun onException(exception: Exception, request: WebRequest): ResponseEntity<ErrorDetailsClass> {
         log.error("error when request with parameters ${request.parameterMap} ")
         return buildDetails(request)
    }
}

In this way, you can do both retrieve a proper error message and also log something internally for error tracking purposes.




回答2:


With Spring AOP this requirement can be met with @AfterThrowing advice.

Following example Aspect will intercept all method calls under package org.aop.bean.impl that exits with an exception . We can further filter to the specific exception type with throwing attribute. The given example filters out the methods exiting with IllegalArgumentException.

The arguments during the method call can be obtained with joinpoint.getArgs() method.

@Aspect
@Component
public class ExceptionLoggerAspect {

    @Pointcut("execution(* org.aop.bean.impl..*(..))")
    public void allExceptions() {

    }

    @AfterThrowing(pointcut = "allExceptions()",throwing="ex")
    public void logException(JoinPoint jp , IllegalArgumentException ex) {
        Object[] args= jp.getArgs();
        for(Object obj:args) {
            System.out.println(obj);
        }
    }
}

From the docs

Often, you want the advice to run only when exceptions of a given type are thrown, and you also often need access to the thrown exception in the advice body. You can use the throwing attribute to both restrict matching (if desired — use Throwable as the exception type otherwise) and bind the thrown exception to an advice parameter




回答3:


The example I'm writing is in spring-boot using org.springframework.web.bind.annotation.ExceptionHandler annotation

It works perfectly fine for me

Suppose I made a Get request to https://example.com/user-api/users/a535c777-c906-45e2-b1c3-940965a507f2q , then our api validates if that user-id exists or not and if not throws a proper message including which parameters are invalid or has errors.

Response ex 1:

{
"apierror": {
    "dateTime": "2020-02-13T06:24:14.985",
    "timestamp": "1581603854985",
    "status": 404,
    "error": "Not Found",
    "message": "User not found",
    "debugMessage": null,
    "errors": [
        {
            "field": "userId",
            "rejectedValue": "a535c777-c906-45e2-b1c3-940965a507f2q",
            "message": "User not found with userId:a535c777-c906-45e2-b1c3-940965a507f2q"
        }
    ]
}

}

Response ex2:

        {
      "apierror": {
        "dateTime": "2020-02-13T06:43:23.377",
        "timestamp": "1581605003377",
        "status": 400,
        "error": "Bad Request",
        "message": "Validation error",
        "debugMessage": null,
        "errors": [
          {
            "field": "userName",
            "rejectedValue": "Ash",
            "message": "Username should have at least 6 characters"
          },
          {
            "field": "userName",
            "rejectedValue": "Ash",
            "message": "Invalid username"
          },
          {
            "field": "password",
            "rejectedValue": "shutosh@",
            "message": "Invalid password"
          }
        ]
      }
    }

Exception message "User not found with userId:a535c777-c906-45e2-b1c3-940965a507f2q" is as per the api. Below is the use-case.

Controller:

@PrivilegeMapper.HasPlaceUserPrivilege
@GetMapping(value = "/{userId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> getUserProfile(@NotBlank @PathVariable String userId) {
 return myService.buildUserProfile(userId);
}

Service:

 @Override
public ResponseEntity<?> buildUserProfile(final String userId) {

    ApiUser apiUser = userRepository.findById(userId)
            .orElseThrow(() -> new ApiUserNotFoundException("userId",userId));

    return ResponseEntity.ok(sirfUser);
}

Exception Classes:

    @Getter
    @Setter
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    public class ApiUserNotFoundException extends NotFoundException {

        public ApiUserNotFoundException(String msg, Throwable t) {
            super(msg, t);
        }

        public ApiUserNotFoundException(String msg) {
            super(msg);
        }

        public ApiUserNotFoundException(String key, String value) {
            super(key, value);
        }

        public ApiUserNotFoundException(String key, String value, List<Error> errors) {
            super(key, value, errors);
        }
    }   


    @Getter
    @Setter
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public class NotFoundException extends RuntimeException {

        private String key;
        private String value;
        private List<Error> errors;

        public NotFoundException(String msg, Throwable t) {
            super(msg, t);
        }

        public NotFoundException(String msg) {
            super(msg);
        }

        public NotFoundException(String key, String value) {
            this.key = key;
            this.value = value;
        }

        public NotFoundException(String key, String value, List<Error> errors) {
            this.key = key;
            this.value = value;
            this.errors = errors;
        }

    }       

Exception Handler:

@ExceptionHandler(ApiUserNotFoundException.class)
protected ResponseEntity<Object> handleSirfUserNotFound(ApiUserNotFoundException ex) {
    log.error(String.format("User not found with %s:%s",ex.getKey(),ex.getValue()));
    ApiError apiError = new ApiError(NOT_FOUND);
    apiError.setMessage("User not found");
    List<Error> errors = new ArrayList<>();
    Error error = new ApiValidationError(SirfUser.class.getSimpleName());
    ((ApiValidationError) error).setMessage(String.format("User not found with %s:%s",ex.getKey(),ex.getValue()));
    ((ApiValidationError) error).setField(ex.getKey());
    ((ApiValidationError) error).setRejectedValue(ex.getValue());
    errors.add(error);
    apiError.setErrors(errors);
    return buildResponseEntity(apiError);
}

And this is it. You are done. such type of handling is always useful both for logging and ui perspective.



来源:https://stackoverflow.com/questions/60209758/java-kotlin-spring-boot-how-can-we-automatically-retrieve-parameter-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!