Полностью залил готовый код. В консоле норм, как и в примере.
Нажал старт и вот, то, что ниже…
xecutor.py [LINE:362] #INFO [2022-03-04 00:36:07,638] Bot: rekrut [@rekrut5_bot]
dispatcher.py [LINE:358] #INFO [2022-03-04 00:36:07,638] Start polling.
dispatcher.py [LINE:390] #ERROR [2022-03-04 00:36:11,412] Cause exception while getting updates.
Traceback (most recent call last):
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 381, in start_polling
updates = await self.bot.get_updates(
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotbot.py", line 110, in get_updates
result = await self.request(api.Methods.GET_UPDATES, payload)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotbase.py", line 231, in request
return await api.make_request(await self.get_session(), self.server, self.__token, method, data, files,
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotapi.py", line 140, in make_request
return check_result(method, response.content_type, response.status, await response.text())
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotapi.py", line 119, in check_result
exceptions.ConflictError.detect(description)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramutilsexceptions.py", line 140, in detect
raise err(cls.text or description)
aiogram.utils.exceptions.TerminatedByOtherGetUpdates: Terminated by other getupdates request; make sure that only one bot instance is running
base_events.py [LINE:1729] #ERROR [2022-03-04 00:36:16,548] Task exception was never retrieved
future: <Task finished name='Task-8' coro=<Dispatcher._process_polling_updates() done, defined at G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py:407> exception=FileNotFoundError(2, 'No such file or directory')>
Traceback (most recent call last):
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 415, in _process_polling_updates
for responses in itertools.chain.from_iterable(await self.process_updates(updates, fast)):
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 235, in process_updates
return await asyncio.gather(*tasks)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherhandler.py", line 116, in notify
response = await handler_obj.handler(*args, **partial_data)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 256, in process_update
return await self.message_handlers.notify(update.message)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherhandler.py", line 116, in notify
response = await handler_obj.handler(*args, **partial_data)
File "G:PYTHONPROSTO_botmain.py", line 36, in welcome
joinedFile = open("user.txt", "r")
FileNotFoundError: [Errno 2] No such file or directory: 'user.txt'
dispatcher.py [LINE:390] #ERROR [2022-03-04 00:36:18,501] Cause exception while getting updates.
Traceback (most recent call last):
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 381, in start_polling
updates = await self.bot.get_updates(
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotbot.py", line 110, in get_updates
result = await self.request(api.Methods.GET_UPDATES, payload)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotbase.py", line 231, in request
return await api.make_request(await self.get_session(), self.server, self.__token, method, data, files,
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotapi.py", line 140, in make_request
return check_result(method, response.content_type, response.status, await response.text())
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotapi.py", line 119, in check_result
exceptions.ConflictError.detect(description)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramutilsexceptions.py", line 140, in detect
raise err(cls.text or description)
aiogram.utils.exceptions.TerminatedByOtherGetUpdates: Terminated by other getupdates request; make sure that only one bot instance is running
base_events.py [LINE:1729] #ERROR [2022-03-04 00:36:23,635] Task exception was never retrieved
future: <Task finished name='Task-12' coro=<Dispatcher._process_polling_updates() done, defined at G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py:407> exception=FileNotFoundError(2, 'No such file or directory')>
Traceback (most recent call last):
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 415, in _process_polling_updates
for responses in itertools.chain.from_iterable(await self.process_updates(updates, fast)):
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 235, in process_updates
return await asyncio.gather(*tasks)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherhandler.py", line 116, in notify
response = await handler_obj.handler(*args, **partial_data)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 256, in process_update
return await self.message_handlers.notify(update.message)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherhandler.py", line 116, in notify
response = await handler_obj.handler(*args, **partial_data)
File "G:PYTHONPROSTO_botmain.py", line 36, in welcome
joinedFile = open("user.txt", "r")
FileNotFoundError: [Errno 2] No such file or directory: 'user.txt'
dispatcher.py [LINE:390] #ERROR [2022-03-04 00:36:25,095] Cause exception while getting updates.
Traceback (most recent call last):
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramdispatcherdispatcher.py", line 381, in start_polling
updates = await self.bot.get_updates(
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotbot.py", line 110, in get_updates
result = await self.request(api.Methods.GET_UPDATES, payload)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotbase.py", line 231, in request
return await api.make_request(await self.get_session(), self.server, self.__token, method, data, files,
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotapi.py", line 140, in make_request
return check_result(method, response.content_type, response.status, await response.text())
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogrambotapi.py", line 119, in check_result
exceptions.ConflictError.detect(description)
File "G:PYTHONPROSTO_botbotlibsite-packagesaiogramutilsexceptions.py", line 140, in detect
raise err(cls.text or description)
aiogram.utils.exceptions.TerminatedByOtherGetUpdates: Terminated by other getupdates request; make sure that only one bot instance is running
dispatcher.py [LINE:433] #INFO [2022-03-04 00:36:32,082] Stop polling...
executor.py [LINE:329] #WARNING [2022-03-04 00:36:32,087] Goodbye!
title | subTitle | currentMenu |
---|---|---|
Error handling |
PHP Best practices |
php-exceptions |
Using Exceptions
Beginner
###Rule
When writing a function, you should throw exceptions for error management instead of returning a boolean.
###Explanation
What you should NOT do:
function writeDateInFile(): bool { $result = file_put_contents("date", date("Y-m-d")); if ($result) { return true; } else { return false; } }
Why is this bad? Because you are relying on the developer using your function to actively check the return value
and see if everything went all right.
// You assume the developer using your function will write: $result = writeDateInFile(); if ($result) { // Do stuff } else { // Do something to actually manage the error }
But developers are lazy. They tend to forget to add required checks. Or they don’t have time and they skip
error handling. So instead of returning a status code, your function should not return anything but it should
throw an exception in case something goes wrong.
So your code should look like this:
function writeDateInFile(): void { $result = file_put_contents("date", date("Y-m-d")); if ($result === false) { throw new FileWriteException("There was a problem writing file 'date'"); } }
Subtyping exceptions
Intermediate
Rule
You should never throw the Exception
class directly. Instead, you should consider
extending the Exception
class or using one of the available sub-classes.
Explanation
What you should NOT do:
function writeDateInFile(): void { $data = $this->dao->getSomeData(); $result = file_put_contents("date", json_encode($data)); if ($result === false) { throw new Exception("Throw some generic exception"); } }
Why is this bad? Because you are preventing the developer to catch specific problems.
Look at the code above. The getSomeData
method could also throw an exception that the developer using
your function might not want to catch.
So instead, you should consider creating your own exception or using an exception from the SPL library:
namespace MyNamespace; class FileWritingException extends Exception { }
Please note that your class does not need to contain any code. It just needs to extend the Exception
class.
Now, your code looks like this:
function writeDataInFile(): void { $data = $this->dao->getSomeData(); $result = file_put_contents("date", json_encode($data)); if ($result === false) { throw new FileWritingException("Throw my specific exception"); } }
and the developer can catch this specific exception if he wants to:
try { $this->writeDataInFile(); } catch (FileWritingException $e) { // Do some specific stuff if we have problems with disk writing. }
Fail early, fail loud
Beginner
###Rule
Do not catch exceptions! If something unexpected happens, your code should
fail loudly (with a big error message) rather than trying to hide what is going wrong.
###Explanation
What you should NOT do:
function doCleverStuff(): void { try { $results = $this->db->makeRequest("SELECT ...[insert complex SQL here] "); // ... Do stuff } catch (DBException $e) { $this->log->error($e->getMessage()); } }
This code is clearly evil. So you have this big complex SQL request, and if it fails (because there is a parse error
in the SQL), the code is catching the error, logging it… and that’s it!
How long will it take before someone notices that the logs are full of SQL errors?
An SQL error is not a runtime error, it is a design error. So if there is a design error, there is a bug.
And you don’t catch bugs. Never.
Instead, the correct code is:
function doCleverStuff() { $results = $this->db->makeRequest("SELECT ...[insert complex SQL here] "); // ... Do stuff }
See how this is easier? If an exception is thrown because there is a problem in the SQL, it will bubble up. It will
probably be caught by your MVC framework that will display a nice HTTP 500 page, with a nice stacktrace.
If your framework does not provide you with a nice error page, consider switching to another framework. Or use
Whoops, a nice error reporting library.
In general, there are very few cases where you will want to catch an exception. This is because exceptions are thrown
when something goes seriously wrong and generally, there is nothing you can do to fix it:
- Your database is not reachable? There is usually nothing your program can do to fix this
- Your application does not have rights to write in a directory? There is usually nothing your program can do to fix this
- Your hard disk is full? There is usually nothing your program can do to fix this
- You have a SQL error? There is usually nothing your program can do to fix this
See? Most exceptions are meant to bubble up, so do not catch them!
So unless you are writing an error handler, or rethrowing the exception, you should always carefully consider what
exceptions you want to catch and never try to catch the Exception
class, the Throwable
interface or the
RuntimeException
class (RuntimeException
is supposed to be used for unrecoverable exceptions).
Bad:
try { // ... } catch (Exception $e) { // NO! Catching the Exception root class is evil unless you are writing an error handler. }
Good:
try { // ... } catch (Exception $e) { // Do some stuff... // This is OK, because the exception is rethrown throw $e; }
Always log exceptions with the stacktrace
Intermediate
###Rule
Ok, so you read the rule just above, and you still want to catch that exception…
When logging exceptions, please log the whole stack-trace, not only the message.
###Explanation
What you should NOT do:
try { // ... } catch (MyException $e) { // This is bad, you just lost the stacktrace! $this->logger->error($e->getMessage()); }
Instead, the correct code is:
try { // ... } catch (MyException $e) { // This is good, the stacktrace is logged. $this->logger->error($e->getMessage(), [ 'exception' => $e ]); }
You are using a PSR-3 compatible logger, right? The PSR-3 states that you can pass an exception to the logger in the
exception
key of the context array. By doing so, your logger will be allowed to log the stacktrace. If an exception
is ever thrown, you will have the complete stack-trace and you will know where in the code it was triggered.
Wrapping an exception? Do not lose the previous exception!
Pro
###Rule
When wrapping exceptions, please pass on the root exception.
###Explanation
What you should NOT do:
try { // ... } catch (DatabaseException $e) { // This is bad, you just lost the reason why the exception happened. throw new MyServiceException("Something wrong happened with the database"); }
The code above is evil because you completely lost any knowledge of the DatabaseException
that was triggered.
What was the message? Which line of code did trigger this exception? You don’t know anymore. The only thing you know
is that you have a new MyServiceException
that is not very helpful.
Instead, the correct code is:
try { // ... } catch (DatabaseException $e) { // This is good. The exception is passed as 3rd parameter of the exception constructor. // Now, your MyServiceException embeds the DatabaseException and both stack-traces will be displayed. throw new MyServiceException("Something wrong happened with the database", 0, $e); }
This is good. The third parameter to the Exception
constructor is another exception (the one that triggered this
exception). Most loggers and error reporting tools will show you both exceptions, so you will have a very detailed
view of what is going wrong.
Во время работы вашего приложения часто будут возникать исключительные ситуации. Когда у вас простое консольное приложение, то все просто – ошибка выводится в консоль. Но как быть с веб-приложением?
Допустим у пользователя отсутсвует доступ, или он передал некорректные данные. Лучшим вариантом будет в ответ на такие ситуации, отправлять пользователю сообщения с описанием ошибки. Это позволит клиенту вашего API скорректировать свой запрос.
В данной статье разберём основные возможности, которые предоставляет SpringBoot для решения этой задачи и на простых примерах посмотрим как всё работает.
@ExceptionHandler
@ExceptionHandler
позволяет обрабатывать исключения на уровне отдельного контроллера. Для этого достаточно объявить метод в контроллере, в котором будет содержаться вся логика обработки нужного исключения, и пометить его аннотацией.
Для примера у нас будет сущность Person
, бизнес сервис к ней и контроллер. Контроллер имеет один эндпойнт, который возвращает пользователя по логину. Рассмотрим классы нашего приложения:
Сущность Person
:
package dev.struchkov.general.sort;
import java.text.MessageFormat;
public class Person {
private String lastName;
private String firstName;
private Integer age;
//getters and setters
}
Контроллер PersonController
:
package dev.struchkov.example.controlleradvice.controller;
import dev.struchkov.example.controlleradvice.domain.Person;
import dev.struchkov.example.controlleradvice.service.PersonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("api/person")
@RequiredArgsConstructor
public class PersonController {
private final PersonService personService;
@GetMapping
public ResponseEntity<Person> getByLogin(@RequestParam("login") String login) {
return ResponseEntity.ok(personService.getByLoginOrThrown(login));
}
@GetMapping("{id}")
public ResponseEntity<Person> getById(@PathVariable("id") UUID id) {
return ResponseEntity.ok(personService.getById(id).orElseThrow());
}
}
И наконец PersonService
, который будет возвращать исключение NotFoundException
, если пользователя не будет в мапе persons
.
package dev.struchkov.example.controlleradvice.service;
import dev.struchkov.example.controlleradvice.domain.Person;
import dev.struchkov.example.controlleradvice.exception.NotFoundException;
import lombok.NonNull;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@Service
public class PersonService {
private final Map<UUID, Person> people = new HashMap<>();
public PersonService() {
final UUID komarId = UUID.randomUUID();
people.put(komarId, new Person(komarId, "komar", "Алексей", "ertyuiop"));
}
public Person getByLoginOrThrown(@NonNull String login) {
return people.values().stream()
.filter(person -> person.getLogin().equals(login))
.findFirst()
.orElseThrow(() -> new NotFoundException("Пользователь не найден"));
}
public Optional<Person> getById(@NonNull UUID id) {
return Optional.ofNullable(people.get(id));
}
}
Перед тем, как проверить работу исключения, давайте посмотрим на успешную работу эндпойнта.
Все отлично. Нам в ответ пришел код 200, а в теле ответа пришел JSON нашей сущности. А теперь мы отправим запрос с логином пользователя, которого у нас нет. Посмотрим, что сделает Spring по умолчанию.
Обратите внимание, ошибка 500 – это стандартный ответ Spring на возникновение любого неизвестного исключения. Также исключение было выведено в консоль.
Как я уже говорил, отличным решением будет сообщить пользователю, что он делает не так. Для этого добавляем метод с аннотацией @ExceptionHandler
, который будет перехватывать исключение и отправлять понятный ответ пользователю.
@RequestMapping("api/person")
@RequiredArgsConstructor
public class PersonController {
private final PersonService personService;
@GetMapping
public ResponseEntity<Person> getByLogin(@RequestParam("login") String login) {
return ResponseEntity.ok(personService.getByLoginOrThrown(login));
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorMessage> handleException(NotFoundException exception) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
}
Вызываем повторно наш метод и видим, что мы стали получать понятное описание ошибки.
Но теперь вернулся 200 http код, куда корректнее вернуть 404 код.
Однако некоторые разработчики предпочитают возвращать объект, вместо ResponseEntity<T>
. Тогда вам необходимо воспользоваться аннотацией @ResponseStatus
.
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NotFoundException.class)
public ErrorMessage handleException(NotFoundException exception) {
return new ErrorMessage(exception.getMessage());
}
Если попробовать совместить ResponseEntity<T>
и @ResponseStatus
, http-код будет взят из ResponseEntity<T>
.
Главный недостаток @ExceptionHandler
в том, что он определяется для каждого контроллера отдельно. Обычно намного проще обрабатывать все исключения в одном месте.
Хотя это ограничение можно обойти если @ExceptionHandler
будет определен в базовом классе, от которого будут наследоваться все контроллеры в приложении, но такой подход не всегда возможен, особенно если перед нами старое приложение с большим количеством легаси.
HandlerExceptionResolver
Как мы знаем в программировании магии нет, какой механизм задействуется, чтобы перехватывать исключения?
Интерфейс HandlerExceptionResolver
является общим для обработчиков исключений в Spring. Все исключений выброшенные в приложении будут обработаны одним из подклассов HandlerExceptionResolver
. Можно сделать как свою собственную реализацию данного интерфейса, так и использовать существующие реализации, которые предоставляет нам Spring из коробки.
Давайте разберем стандартные для начала:
ExceptionHandlerExceptionResolver
— этот резолвер является частью механизма обработки исключений помеченных аннотацией @ExceptionHandler
, которую мы рассмотрели выше.
DefaultHandlerExceptionResolver
— используется для обработки стандартных исключений Spring и устанавливает соответствующий код ответа, в зависимости от типа исключения:
Exception | HTTP Status Code |
---|---|
BindException | 400 (Bad Request) |
ConversionNotSupportedException | 500 (Internal Server Error) |
HttpMediaTypeNotAcceptableException | 406 (Not Acceptable) |
HttpMediaTypeNotSupportedException | 415 (Unsupported Media Type) |
HttpMessageNotReadableException | 400 (Bad Request) |
HttpMessageNotWritableException | 500 (Internal Server Error) |
HttpRequestMethodNotSupportedException | 405 (Method Not Allowed) |
MethodArgumentNotValidException | 400 (Bad Request) |
MissingServletRequestParameterException | 400 (Bad Request) |
MissingServletRequestPartException | 400 (Bad Request) |
NoSuchRequestHandlingMethodException | 404 (Not Found) |
TypeMismatchException | 400 (Bad Request) |
Мы можем создать собственный HandlerExceptionResolver
. Назовем его CustomExceptionResolver
и вот как он будет выглядеть:
package dev.struchkov.example.controlleradvice.service;
import dev.struchkov.example.controlleradvice.exception.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class CustomExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
final ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
if (e instanceof NotFoundException) {
modelAndView.setStatus(HttpStatus.NOT_FOUND);
modelAndView.addObject("message", "Пользователь не найден");
return modelAndView;
}
modelAndView.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
modelAndView.addObject("message", "При выполнении запроса произошла ошибка");
return modelAndView;
}
}
Мы создаем объект представления – ModelAndView
, который будет отправлен пользователю, и заполняем его. Для этого проверяем тип исключения, после чего добавляем в представление сообщение о конкретной ошибке и возвращаем представление из метода. Если ошибка имеет какой-то другой тип, который мы не предусмотрели в этом обработчике, то мы отправляем сообщение об ошибке при выполнении запроса.
Так как мы пометили этот класс аннотацией @Component
, Spring сам найдет и внедрит наш резолвер куда нужно. Посмотрим, как Spring хранит эти резолверы в классе DispatcherServlet
.
Все резолверы хранятся в обычном ArrayList
и в случае исключнеия вызываются по порядку, при этом наш резолвер оказался последним. Таким образом, если непосредственно в контроллере окажется @ExceptionHandler
обработчик, то наш кастомный резолвер не будет вызван, так как обработка будет выполнена в ExceptionHandlerExceptionResolver
.
Важное замечание. У меня не получилось перехватить здесь ни одно Spring исключение, например MethodArgumentTypeMismatchException
, которое возникает если передавать неверный тип для аргументов @RequestParam
.
Этот способ был показан больше для образовательных целей, чтобы показать в общих чертах, как работает этот механизм. Не стоит использовать этот способ, так как есть вариант намного удобнее.
@RestControllerAdvice
Исключения возникают в разных сервисах приложения, но удобнее всего обрабатывать все исключения в каком-то одном месте. Именно для этого в SpringBoot предназначены аннотации @ControllerAdvice
и @RestControllerAdvice
. В статье мы рассмотрим @RestControllerAdvice
, так как у нас REST API.
На самом деле все довольно просто. Мы берем методы помеченные аннотацией @ExceptionHandler
, которые у нас были в контроллерах и переносим в отдельный класс аннотированный @RestControllerAdvice
.
package dev.struchkov.example.controlleradvice.controller;
import dev.struchkov.example.controlleradvice.domain.ErrorMessage;
import dev.struchkov.example.controlleradvice.exception.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
@RestControllerAdvice
public class ExceptionApiHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorMessage> notFoundException(NotFoundException exception) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorMessage> mismatchException(MethodArgumentTypeMismatchException exception) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
}
За обработку этих методов класса точно также отвечает класс ExceptionHandlerExceptionResolver
. При этом мы можем здесь перехватывать даже стандартные исключения Spring, такие как MethodArgumentTypeMismatchException
.
На мой взгляд, это самый удобный и простой способ обработки возвращаемых пользователю исключений.
Еще про обработку
Все написанное дальше относится к любому способу обработки исключений.
Запись в лог
Важно отметить, что исключения больше не записываются в лог. Если помимо ответа пользователю, вам все же необходимо записать это событие в лог, то необходимо добавить строчку записи в методе обработчике, например вот так:
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorMessage> handleException(NotFoundException exception) {
log.error(exception.getMessage(), exception);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
Перекрытие исключений
Вы можете использовать иерархию исключений с наследованием и обработчики исключений для всей своей иерархии. В таком случае обработка исключения будет попадать в самый специализированный обработчик.
Допустим мы бросаем NotFoundException
, как в примере выше, который наследуется от RuntimeException
. И у вас будет два обработчика исключений для NotFoundException
и RuntimeException
. Исключение попадет в обработчик для NotFoundException
. Если этот обработчик убрать, то попадет в обработчик для RuntimeException
.
Резюмирую
Обработка исключений это важная часть REST API. Она позволяет возвращать клиентам информационные сообщения, которые помогут им скорректировать свой запрос.
Мы можем по разному реализовать обработку в зависимости от нашей архитектуры. Предпочитаемым способом считаю вариант с @RestControllerAdvice
. Этот вариант самый чистый и понятный.