Обзор
В этой статье мы рассмотрим процесс создания настраиваемых как отмеченных, так и непроверенных исключений в Java.
Если вы хотите узнать больше об исключениях и обработке исключений в Java, мы подробно рассмотрели это в - Обработка исключений в Java: Полное руководство с лучшими и наихудшими практиками.
Зачем использовать специальные исключения?
Хотя исключения Java, как таковые, охватывают почти все исключительные случаи и условия, ваше приложение может генерировать конкретное настраиваемое исключение, уникальное для вашего кода и логики.
Иногда нам нужно создать собственные для представления исключений
бизнес-логики, то есть исключений, специфичных для нашей бизнес-логики
или рабочего процесса. Например, EmailNotUniqueException
,
InvalidUserStateException
и т. Д.
Они помогают клиентам приложений лучше понять, что пошло не так. Они особенно полезны для обработки исключений для REST API, поскольку различные ограничения бизнес-логики требуют отправки клиенту разных кодов ответа.
Если определение настраиваемого исключения не дает преимуществ по сравнению с использованием обычного исключения в Java, нет необходимости определять настраиваемые исключения, и вы должны просто придерживаться тех, которые уже доступны - не нужно снова изобретать горячую воду.
Пользовательское проверенное исключение
Давайте рассмотрим сценарий, в котором мы хотим проверить электронное письмо, переданное в качестве аргумента методу.
Мы хотим проверить, действительно ли это. Теперь мы могли бы
использовать встроенное в Java IllegalArgumentException
, что
нормально, если мы просто проверяем одну вещь, например, соответствует
ли она предопределенному REGEX или нет.
Но предположим, что у нас также есть бизнес-условие для проверки того,
что все электронные письма в нашей системе должны быть уникальными.
Теперь нам нужно выполнить вторую проверку (вызов БД / сетевой вызов).
Мы, конечно, можем использовать то же IllegalArgumentException
, но
будет неясно, какова точная причина - было ли электронное письмо не
прошло проверку REGEX или оно уже существует в базе данных.
Давайте создадим специальное исключение для обработки этой ситуации.
Чтобы создать исключение, как и любое другое исключение, мы должны
расширить класс java.lang.Exception
public class EmailNotUniqueException extends Exception {
public EmailNotUniqueException(String message) {
super(message);
}
}
Обратите внимание, что мы предоставили конструктор, который принимает
String
и вызывает конструктор родительского класса. Это не
обязательно, но это обычная практика - иметь какие-то подробности о
произошедшем исключении.
Вызывая super(message)
, мы инициализируем сообщение об ошибке
исключения, а базовый класс заботится о настройке настраиваемого
сообщения в соответствии с message
.
Теперь давайте используем это настраиваемое исключение в нашем коде.
Поскольку мы определяем метод, который может генерировать исключение на
уровне обслуживания, мы отметим его ключевым словом throws
Если входящий адрес электронной почты уже существует в нашей базе данных
(в данном случае это список), мы throw
собственное исключение:
public class RegistrationService {
List<String> registeredEmails = Arrays.asList(" [email protected] ", " [email protected] ");
public void validateEmail(String email) throws EmailNotUniqueException {
if (registeredEmails.contains(email)) {
throw new EmailNotUniqueException("Email Already Registered");
}
}
}
Теперь напишем клиента для нашего сервиса. Поскольку это проверенное исключение, мы должны придерживаться правила дескриптора или объявления. В следующем примере мы решили «обработать» это:
public class RegistrationServiceClient {
public static void main(String[] args) {
RegistrationService service = new RegistrationService();
try {
service.validateEmail(" [email protected] ");
} catch (EmailNotUniqueException e) {
// logging and handling the situation
}
}
}
Выполнение этого фрагмента кода даст:
mynotes.custom.checked.exception.EmailNotUniqueException: Email Already Registered
at mynotes.custom.checked.exception.RegistrationService.validateEmail(RegistrationService.java:12)
at mynotes.custom.checked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:9)
Примечание. Процесс обработки исключений опущен для краткости, но, тем не менее, это важный процесс.
Пользовательское исключение без отметки
Это работает отлично, но наш код стал немного беспорядочным. Кроме того,
мы заставляем клиента фиксировать наше исключение в блоке try-catch
В
некоторых случаях это может привести к тому, что разработчиков заставят
писать шаблонный код.
В этом случае может быть полезно вместо этого создать настраиваемое
исключение времени выполнения. Чтобы создать настраиваемое непроверенное
исключение, мы должны расширить класс java.lang.RuntimeException
Давайте рассмотрим ситуацию, когда нам нужно проверить, есть ли в электронном письме действительное доменное имя или нет:
public class DomainNotValidException extends RuntimeException {
public DomainNotValidException(String message) {
super(message);
}
}
Теперь позвольте использовать это в нашем сервисе:
public class RegistrationService {
public void validateEmail(String email) {
if (!isDomainValid(email)) {
throw new DomainNotValidException("Invalid domain");
}
}
private boolean isDomainValid(String email) {
List<String> validDomains = Arrays.asList("gmail.com", "yahoo.com", "outlook.com");
if (validDomains.contains(email.substring(email.indexOf("@") + 1))) {
return true;
}
return false;
}
}
Обратите внимание, что нам не нужно использовать throws
в сигнатуре
метода, поскольку это непроверенное исключение.
Теперь напишем клиента для нашего сервиса. На этот раз нам не нужно
использовать блок try-catch
:
public class RegistrationServiceClient {
public static void main(String[] args) {
RegistrationService service = new RegistrationService();
service.validateEmail(" [email protected] ");
}
}
Выполнение этого фрагмента кода даст:
Exception in thread "main" mynotes.custom.unchecked.exception.DomainNotValidException: Invalid domain
at mynotes.custom.unchecked.exception.RegistrationService.validateEmail(RegistrationService.java:10)
at mynotes.custom.unchecked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:7)
Примечание. Конечно, вы можете окружить свой код try-catch
чтобы
зафиксировать возникающее исключение, но теперь оно не принудительно
выполняется компилятором.
Повторное создание исключения, заключенного в настраиваемое исключение
Иногда нам нужно перехватить исключение и повторно выбросить его, добавив еще несколько деталей. Обычно это происходит, если в вашем приложении определены различные коды ошибок, которые необходимо либо зарегистрировать, либо вернуть клиенту в случае этого конкретного исключения.
Предположим, в вашем приложении есть стандартный класс ErrorCodes
public enum ErrorCodes {
VALIDATION_PARSE_ERROR(422);
private int code;
ErrorCodes(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}
Создадим собственное исключение:
public class InvalidCurrencyDataException extends RuntimeException {
private Integer errorCode;
public InvalidCurrencyDataException(String message) {
super(message);
}
public InvalidCurrencyDataException(String message, Throwable cause) {
super(message, cause);
}
public InvalidCurrencyDataException(String message, Throwable cause, ErrorCodes errorCode) {
super(message, cause);
this.errorCode = errorCode.getCode();
}
public Integer getErrorCode() {
return errorCode;
}
}
Обратите внимание, что у нас есть несколько конструкторов, и пусть класс
обслуживания решает, какой из них использовать. Поскольку мы повторно
генерируем исключение, всегда рекомендуется фиксировать основную причину
исключения, следовательно, Throwable
который можно передать
конструктору родительского класса.
errorCode
код ошибки в одном из конструкторов и устанавливаем
errorCode внутри самого исключения. errorCode
может использоваться
клиентом для ведения журнала или любой другой цели. Это помогает в более
централизованном стандарте обработки исключений.
Напишем наш класс обслуживания:
public class CurrencyService {
public String convertDollarsToEuros(String value) {
try {
int x = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new InvalidCurrencyDataException("Invalid data", e, ErrorCodes.VALIDATION_PARSE_ERROR);
}
return value;
}
}
Итак, мы перехватили стандартное NumberFormatException
и выбросили
собственное InvalidCurrencyDataException
. Мы передали родительское
исключение нашему настраиваемому исключению, чтобы не потерять основную
причину, из которой они возникли.
Напишем тестовый клиент для этого сервиса:
public class CurrencyClient {
public static void main(String[] args) {
CurrencyService service = new CurrencyService();
service.convertDollarsToEuros("asd");
}
}
Выход:
Exception in thread "main" mynotes.custom.unchecked.exception.InvalidCurrencyDataException: Invalid data
at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:10)
at mynotes.custom.unchecked.exception.CurrencyClient.main(CurrencyClient.java:8)
Caused by: java.lang.NumberFormatException: For input string: "asd"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:8)
... 1 more
Как видите, у нас есть хорошая трассировка стека исключения, которая может быть полезна для целей отладки.
Лучшие практики для настраиваемых исключений
- Соблюдайте общее соглашение об именах во всей экосистеме Java - все имена классов настраиваемых исключений должны заканчиваться на «Exception».
- Избегайте создания пользовательских исключений, если стандартные исключения из самого JDK могут служить этой цели. В большинстве случаев определять собственные исключения не требуется.
- Предпочитайте исключения времени выполнения установленным исключениям. Фреймворки, такие как Spring , обернули все проверенные исключения в исключения времени выполнения, следовательно, не заставляя клиента писать шаблонный код, который им не нужен или не нужен.
- Предоставьте много перегруженных конструкторов в зависимости от того, как будет создано настраиваемое исключение. Если он используется для повторного создания существующего исключения, обязательно укажите конструктор, который устанавливает причину.
Заключение
Пользовательские исключения используются для конкретной бизнес-логики и требований. В этой статье мы обсудили необходимость в них, а также рассказали об их использовании.
Код примеров, использованных в этой статье, можно найти на Github .