Привет, ребята! Здесь, кто вы пишете это Эдсон Сильва, разработчик программного обеспечения для разработки в 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 Seterrors; 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(); MapgetMapDetails(); }
BaseRuntimeException.java
public abstract class BaseRuntimeException extends RuntimeException implements MessageException { private final MapmapDetails; 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 ResponseEntityhandlerMethodArgumentNotValid( 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 ResponseEntityexampleModelValidationEndpoint(@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”