Java / Kotlin / Spring Boot. Как мы можем автоматически получить значения параметров при возникновении исключения? - PullRequest
1 голос
/ 13 февраля 2020

Учитывая, что мы используем Kotlin, Spring Boot, аннотации и другие связанные библиотеки.

Если у нас есть ситуация, в которой наш код вызывает исключение, как мы можем автоматически получить значения параметров метода в момент этого исключения?

Можем ли мы сделать это, используя AOP, Spring Interceptors или другие методы?

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

Обратите внимание, что мы ищем решение, которое нам не нужно аннотировать все возможные методы, но что-то, что будет обрабатывать код при возникновении исключения. Мы можем использовать элементы Java stacktrace для получения некоторой полезной информации, такой как метод, строка и файл, где произошло исключение, но у нас там нет значений параметров.

В Spring у нас есть функция Controller Advice что мы можем использовать для обработки всех наших исключений, поэтому мы хотели бы поместить что-то там для этой цели, например.

Редактировать

Добавление некоторого примера кода:

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.")
}

Ответы [ 3 ]

2 голосов
/ 13 февраля 2020

Я предполагаю, что вы говорили о параметрах остальных API, а не о каждом параметре java метода. Вы можете реализовать совет контроллера , который фиксирует все исключения в ваших остальных вызовах API.

@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)
    }
}

Таким образом, вы можете как получить правильное сообщение об ошибке, так и записать что-то внутри для отслеживания ошибок.

1 голос
/ 13 февраля 2020

В Spring AOP это требование можно выполнить с помощью @ AfterThrowing advice.

В следующем примере Aspect будет перехватывать все вызовы методов в пакете org.aop.bean.impl, который завершается с исключением. Мы можем дополнительно отфильтровать к указанному c типу исключения с атрибутом throwing. Данный пример отфильтровывает методы, завершающиеся с помощью IllegalArgumentException.

Аргументы во время вызова метода могут быть получены с помощью метода joinpoint.getArgs().

@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);
        }
    }
}

Из документов

Зачастую вы хотите, чтобы совет выполнялся только тогда, когда выбрасываются исключения определенного типа, и вам также часто требуется доступ к выброшенному исключению в теле совета. Вы можете использовать атрибут throwing как для ограничения соответствия (при желании - в противном случае используйте Throwable в качестве типа исключения), так и для привязки брошенного исключения к параметру совета

0 голосов
/ 13 февраля 2020

Пример, который я пишу, находится в весенней загрузке с использованием org.springframework.web.bind.annotation.ExceptionHandler annotation

Это прекрасно работает для меня

Предположим, я сделал Get запрос к https://example.com/user-api/users/a535c777-c906-45e2-b1c3-940965a507f2q, тогда наш API проверяет, существует ли этот идентификатор пользователя или нет, и если нет, выдает правильное сообщение, в том числе о том, какие параметры недопустимы или имеет ошибки.

Ответ 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"
        }
    ]
}

}

Ответ 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"
          }
        ]
      }
    }

Исключительное сообщение "Пользователь не найден с userId: a535c777-c906-45e2-b1c3-940965a507f2q" согласно api. Ниже приведен пример использования.

Контроллер:

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

Служба:

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

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

    return ResponseEntity.ok(sirfUser);
}

Классы исключений:

    @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;
        }

    }       

Обработчик исключений:

@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);
}

И это все. Вы сделали. такой тип обработки всегда полезен как для ведения журнала, так и для перспективы пользовательского интерфейса.

...