Рубрики
Без рубрики

Tratamento de erros personalizados para APIs rest com Пружинный ботинок.

Привет, ребята! Здесь, кто вы пишете это Ledson Петров, разработчик программного обеспечения, начиная с середины 2014 e… С тегами java, spring, spring boot, rest.

Привет, ребята! Здесь, кто вы пишете это Эдсон Сильва, разработчик программного обеспечения для разработки в 2014 году, цель которого состоит в том, чтобы публиковать и демонстрировать фасад для реализации процесса персонализации ошибок в api, без загрузки пружины.

В настоящее время spring имеет общий ответ в случае ошибки, и в большинстве случаев мы хотим вернуть для клиентов, ответ более сложный, который имеет больше информации, и в первую очередь, когда мы работаем с javax validation подготовить ответ элегантный.

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

Давай!!

ПРИМЕЧАНИЕ: в Этом примере я использую Java 11 и наш проект spring имеет следующие зависимости:


    org.springframework.boot
    spring-boot-starter-validation



    org.apache.commons
    commons-lang3
    3.5

Шаг 01: На первом шаге мы будем создавать два файла, где будут размещены наши ошибки. Сделаем что-то с этой структурой, в рамках пакета resources:

В файл бизнес.properties сосредоточим внимание всех сообщений от бизнес-правил, и в validation.properties будут сообщения проверки данных.

Шаг 02: Создать файл конфигурации для spring восстановить эти файлы, сообщения, в случае мы будем использовать только схема интернационализации spring.

MessageConfiguration.java

@Configuration
public class MessageConfiguration implements WebMvcConfigurer {
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:/messages/business/business", "classpath:/messages/validation/validation");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        bean.setValidationMessageSource(messageSource);
        return bean;
    }

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.US);
        return localeResolver;
    }

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        return localeChangeInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

Пассо 03: Создать структуру пользовательских ошибок, в случае, если объекты dto, что мы вернемся на нашего api:

ErrorDTO.java

public class ErrorDTO {
    private String key;
    private String message;

    public ErrorDTO() {
    }

    public ErrorDTO(String key, String message) {
        this.key = key;
        this.message = message;
    }

    ... getters and setters
}

ApiErrorDTO.java

public class ApiErrorDTO {
    private Date timestamp;
    private Integer status;
    private String code;
    private Set errors;

    public ApiErrorDTO() {
    }

    public ApiErrorDTO(Date timestamp, Integer status, String code, Set errors) {
        this.timestamp = timestamp;
        this.status = status;
        this.code = code;
        this.errors = errors;
    }

    ... getters and setters
}

Шаг 04: В четвертом шаге мы создадим наш exception, который мы будем использовать, чтобы расширяет их при создании наших exceptions бизнеса.

MessageException.java

public interface MessageException {
    String getExceptionKey();
    Map getMapDetails();
}

BaseRuntimeException.java

public abstract class BaseRuntimeException extends RuntimeException implements MessageException {
    private final Map mapDetails;

    public BaseRuntimeException() {
        mapDetails = null;
    }
    public BaseRuntimeException(final Map mapDetails) {
        this.mapDetails = mapDetails;
    }

    public abstract String getExceptionKey();

    public Map getMapDetails() {
        return this.mapDetails;
    }
}

Пассо 05: Здесь находится святой грааль!! создадим носа clase, который будет отвечать за перехватить исключения и превращает их в нашей персональный ответ в соответствии с dto, который мы создали ранее.

ExceptionHandlerAdvice.java

@ControllerAdvice
public class ExceptionHandlerAdvice {
    private static final String UNKNOWN_ERROR_KEY = "unknown.error";

    private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlerAdvice.class);
    private final MessageSource messageSource;

    public ExceptionHandlerAdvice(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity handlerMethodArgumentNotValid(
            MethodArgumentNotValidException exception
    ) {
        logger.error("Exception {}, Message: {}", exception.getClass().getName(), exception.getMessage());
        Set errors = exception.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(error -> buildError(error.getCode(), error.getDefaultMessage()))
                .collect(Collectors.toSet());

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(baseErrorBuilder(HttpStatus.BAD_REQUEST, errors));
    }

    @ExceptionHandler(BaseRuntimeException.class)
    public ResponseEntity handlerBaseException(Throwable exception) {
        logger.error("Exception {}", exception.getClass().getName());
        MessageException messageException = (MessageException) exception;
        ErrorDTO error = buildError(messageException.getExceptionKey(),
                bindExceptionKeywords(messageException.getMapDetails(),messageException.getExceptionKey()));

        Set errors = Set.of(error);
        ApiErrorDTO apiErrorDto = baseErrorBuilder(getResponseStatus(exception), errors);

        return ResponseEntity
                .status(getResponseStatus(exception))
                .body(apiErrorDto);
    }

    @ExceptionHandler(Throwable.class)
    public ResponseEntity handlerMethodThrowable(Throwable exception) {
        logger.error("Exception {}, Message: {}", exception.getClass().getName(), exception.getMessage());
        Set errors = Set.of(buildError(UNKNOWN_ERROR_KEY, exception.getMessage()));
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(baseErrorBuilder(HttpStatus.INTERNAL_SERVER_ERROR, errors));
    }

    private ErrorDTO buildError(String code, String message) {
        return new ErrorDTO(code, message);
    }

    private ApiErrorDTO baseErrorBuilder(HttpStatus httpStatus, Set errorList) {
        return new ApiErrorDTO(
                new Date(),
                httpStatus.value(),
                httpStatus.name(),
                errorList);
    }

    private String bindExceptionKeywords(Map keywords, String exceptionKey) {
        String message = messageSource.getMessage(exceptionKey, null, LocaleContextHolder.getLocale());
        return Objects.nonNull(keywords) ? new StrSubstitutor(keywords).replace(message) : message;
    }

    private HttpStatus getResponseStatus(Throwable exception) {
        ResponseStatus responseStatus = exception.getClass().getAnnotation(ResponseStatus.class);
        if (exception.getClass().getAnnotation(ResponseStatus.class) == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return responseStatus.value();
    }
}

Iso-это уже достаточно, чтобы превратить наших “исключения” в нашей персональный ответ. И теперь мы будем создавать структуру с Контроллера и DTO, чтобы requests, где у нас будет два endpoints POST даже пример ошибки проверки, и, чтобы другой ошибок бизнеса!

ExampleDTO.java

public class ExampleDTO {
    @NotNull(message = "{required.validation}")
    private Long id;

    @NotBlank(message = "{required.validation}")
    @Size(min = 4, max = 30, message = "{size.validation}")
    private String name;

    public ExampleDTO() {
    }

    public ExampleDTO(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    ... getters and setters
}

ExampleExceptionController.java

@RestController
@RequestMapping(path = "custom-exception-example")
public class ExampleExceptionController {
    @PostMapping(path = "validation")
    public ResponseEntity exampleModelValidationEndpoint(@Validated @RequestBody ExampleDTO dto) {
        return ResponseEntity.ok(dto);
    }

    @PostMapping(path = "business")
    public ResponseEntity exampleBusinessValidationEndpoint(@Validated @RequestBody ExampleDTO dto) {
        if (dto.getName().equalsIgnoreCase("params")) {
            throw new ExampleNameRuleWithParamsException("params");
        }

        if (!dto.getName().equalsIgnoreCase("business")) {
            throw new ExampleNameRuleException();
        }
        return ResponseEntity.ok("Success!");
    }
}

И последнее и не менее важное-это сообщения, файлы, которые мы создали в первом шаге.

бизнес.недвижимость

example.name.rule=No campo name somente é permitido o valor: business
example.name.rule.with.params=Não é permitido digitar o valor: ´${value}´ no campo name

проверка.свойства

required.validation=Existem campos obrigatórios que não foram preenchidos
size.validation=Tamanho inválido! Digite no mínimo {min} e no máximo {max} caracteres

Только одно объяснение!! В нашем DTO, например, когда у нас есть аннотации проверки, например, @NotBlank нужно задать раздел, в котором будет представлять в файл сообщений, например:

@NotNull(сообщение)

И если exceptions бизнес, в котором нужно создать исключение, которое распространяется в Исключение BaseRuntimeException.java james manos bridge a implementar o método String getExceptionKey() , esera neste método que retornaremos nossa chave correspondente em nosso arquivo бизнес.свойства .

Exemplo:

ExampleNameRuleException.java

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class ExampleNameRuleException extends BaseRuntimeException {
    private static final String KEY = "example.name.rule";

    public ExampleNameRuleException() {
        super();
    }

    @Override
    public String getExceptionKey() {
        return KEY;
    }
}

ExampleNameRuleWithParamsException.java

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class ExampleNameRuleWithParamsException extends BaseRuntimeException {
    private static final String KEY = "example.name.rule.with.params";

    public ExampleNameRuleWithParamsException(String value) {
        super(Map.of("value", value));
    }

    @Override
    public String getExceptionKey() {
        return KEY;
    }
}

Смысл??!!! если не оставить комментарий, что здесь обменяем идея.

Красота, ребята!!! у нас есть для нашей реализации, и, в конце концов, мой проект был с этой структурой:

И наконец, следует prints испытаний:

Ошибки проверки:

Ошибок бизнеса:

Потому что мне нравится такой подход?? парень, таким образом, я могу получить стандартный ответ ошибок для моей api , так, чтобы ошибки проверки, обязательного etc.. сколько ошибок!!!!! кроме того, также отдельные сообщения об ошибках в файлы с определенным и с ключами, которые могут быть повторно использован!!! когда нужно изменить сообщение об ошибке, не нужно, чтобы ctrl + f, в проекте и выйти, изменив на несколько файлов и кодов… находится сосредоточены только файлы сообщений.

А это, ребята!! это то, что мне нравится организовывать своих проектов по отношению к ошибкам API, и я надеюсь, что вам понравилось реализации.

Все файлы, упомянутые в посте можно найти в мой github .

Оригинал: “https://dev.to/ledsonsilva/tratamento-de-erros-personalizados-para-apis-rest-com-spring-boot-4dpd”