Как элегантно оформить исключения Java

задняя часть

Мой официальный аккаунт:MarkerHub,Веб-сайт:markerhub.com

Чтобы увидеть больше избранных статей, нажмите:Java Notes Daquan.md

Малый концентратор ведет к чтению:

Взяв в качестве примера добавление, удаление, изменение и проверку адреса доставки, автор подробно объясняет, как спроектировать хорошую обработку исключений, включая использование предварительных условий в Guava, гибернации-валидатора гибернации, а также как обрабатывать исключения и логику. обработки исключений.Статья немного длинновата.После просмотра все равно большой плюс!


Введение

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

Поскольку в этой статье рассказывается только о некотором опыте и не затрагиваются базовые знания, если читатель все еще не совсем понимает концепцию исключения, сначала проверьте базовые знания.

Как выбрать тип исключения

тип исключения

Как известно, надклассом исключений в java является java.lang.Throwable (далее опущен как Throwable), который имеет еще два важных подкласса, java.lang.Exception (здесь и далее опущен как Exception) и java.lang.Error (здесь и далее опущен как Exception). как Error), где Error управляется виртуальной машиной JVM, например, известное исключение OutOfMemoryError и т. д., поэтому в этой статье мы не будем заострять внимание на исключении Error, а затем поговорим об исключении Exception подробнее.

Exception имеет важный подкласс под названием RuntimeException. Мы вызываем RuntimeException или другие подклассы, наследуемые от RuntimeException, как непроверенные исключения, а другие подклассы, наследуемые от Exception, — как проверенные исключения.В этой статье основное внимание уделяется двум типам исключений: проверенным исключениям и непроверенным исключениям.

Как выбрать исключения

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

Точно так же, если при написании метода вы думаете, что это случайное исключение, теоретически вы думаете, что могут быть некоторые проблемы во время выполнения, и эти проблемы могут не обязательно возникать, и не требуют явного прохождения вызывающей стороной. используется для оценки работы бизнес-процесса, то можно использовать непроверенное исключение, такое как RuntimeException.

Ну, считается, что абзац, который я сказал выше, все еще неясен после того, как вы прочитали его много раз.

Итак, пожалуйста, следуйте за ходом моих мыслей и медленно понимайте это.

Когда вам нужно бросить исключение

Сначала нам нужно понять проблему, когда нам нужно генерировать исключение? Дизайн исключений удобен для разработчиков, но они не используются без разбора.Также автор спрашивал многих друзей о том, когда выбрасывать исключения, и мало кто может дать точные ответы. На самом деле эта проблема очень проста, если вы чувствуете, что какие-то «проблемы» не решить, то можете кинуть исключение.

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

какое исключение должно быть выброшено

Поняв, когда нужно генерировать исключение, давайте подумаем над другим вопросом,Действительно, когда мы выбрасываем исключение, какое исключение мы должны использовать? Это проверенное или непроверенное исключение (RuntimeException)?

Позвольте мне проиллюстрировать эту проблему на примере.Начнем с проверяемого исключения.Например,есть такая бизнес-логика,что нужно прочитать определенные данные из определенного файла.Эта операция чтения может быть вызвана другими проблемами,такими как удаление файла.Если есть ошибка чтения, вам нужно получить эти данные из базы данных redis или mysql, обратитесь к следующему коду, getKey(Integer) - это программа ввода.

public String getKey(Integer key){
    String  value;
    try {
        InputStream inputStream = getFiles("/file/nofile");
        //接下来从流中读取key的value指
        value = ...;
    } catch (Exception e) {
        //如果抛出异常将从mysql或者redis进行取之
        value = ...;
    }
}

public InputStream getFiles(String path) throws Exception {
    File file = new File(path);
    InputStream inputStream = null;
    try {
        inputStream = new BufferedInputStream(new FileInputStream(file));
    } catch (FileNotFoundException e) {
        throw new Exception("I/O读取错误",e.getCause());
    }
    return inputStream;
}


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

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

Далее, давайте взглянем на непроверенное исключение (RuntimeException).На самом деле мы видим множество исключений, таких как RuntimeException, таких как java.lang.NullPointerException/java.lang.IllegalArgumentException и т. д. Итак, когда мы должны генерировать это исключение?

Когда мы пишем метод, мы можем столкнуться с ошибкой случайно, мы думаем, что эта проблема может возникнуть во время выполнения, и теоретически, без этой проблемы, когда программа будет выполняться нормально, вызывающая сторона не обязана ловить это исключение, и в этом случае выдается исключение RuntimeException.

Например, при передаче пути вам нужно вернуть объект File, соответствующий пути:

public void test() {
    myTest.getFiles("");
}

public File getFiles(String path) {
    if(null == path || "".equals(path)){
        throw  new NullPointerException("路径不能为空!");
    }
    File file = new File(path);

    return file;
}


В приведенном выше примере показано, что если вызывающая сторона вызывает getFiles(String), если путь пуст, то генерируется исключение нулевого указателя (которое является подклассом RuntimeException), и вызывающей стороне не нужно явно выполнять попытку... ... операция, чтобы заставить его. Это требует, чтобы вызывающая сторона проверила перед вызовом такого метода, чтобы избежать RuntimeException. Как показано ниже:

public void test() {
    String path = "/a/b.png";
    if(null != path && !"".equals(path)){
        myTest.getFiles("");
    }
}

public File getFiles(String path) {
    if(null == path || "".equals(path)){
        throw  new NullPointerException("路径不能为空!");
    }
    File file = new File(path);

    return file;
}


Какое исключение следует использовать

На основе приведенных выше описаний и примеров можно сделать вывод о том, что разница между исключением **RuntimeException и проверенным исключением заключается в следующем: является ли обязательным для вызывающей стороны обрабатывать это исключение, если вызывающая сторона обязана обрабатывать его, то используйте проверенное исключение.Проверенное исключение, в противном случае выберите непроверенное исключение (RuntimeException). **В целом, если нет особых требований, мы рекомендуем использовать исключение RuntimeException.

Внедрение сценария и выбор технологии

Описание архитектуры

Как мы знаем, традиционные проекты разрабатываются на основе MVC, в этой статье в основном используется дизайн интерфейса в спокойном стиле, чтобы испытать элегантность обработки исключений.

Мы сосредоточимся на спокойном API-уровне (похожем на уровень контроллера в Интернете) и сервисном уровне, изучим, как исключения генерируются в сервисе, а затем как API-уровень захватывает и преобразует исключения.

Используемые технологии: spring-boot, jpa(hibernate), mysql, если вы не слишком знакомы с этими технологиями, читателям необходимо самостоятельно ознакомиться с соответствующими материалами.

Описание бизнес-сценария

Выберите относительно простой бизнес-сценарий. Возьмем в качестве примера управление адресом доставки в электронной коммерции. Когда пользователи покупают товары на мобильном терминале, им необходимо управлять адресом доставки. В проекте предоставьте несколько интерфейсов API для мобильного терминала, чтобы доступ. , например: добавить адрес доставки, удалить адрес доставки, изменить адрес доставки, настроить адрес доставки по умолчанию, запрос списка адресов доставки, запрос одного адреса доставки и другие интерфейсы.

Ограничения сборки

хорошо, это очень простой бизнес-сценарий, который был настроен.Конечно, независимо от того, какая операция API, она содержит некоторые правила:

Добавьте адрес доставки:
Потребление:

  • ID пользователя

  • Информация об адресе доставки

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

  • Обязательные поля для адреса доставки не могут быть пустыми

  • Если у пользователя еще нет адреса доставки, установите адрес доставки в качестве адреса доставки по умолчанию при его создании —

Удалить адрес доставки:
Потребление:

  • ID пользователя

  • идентификатор адреса доставки

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

  • Адрес доставки не может быть пустым, а адрес доставки существует.

  • Определите, является ли этот адрес доставки адресом доставки пользователя

  • Определите, является ли этот адрес доставки адресом доставки по умолчанию. Если это адрес доставки по умолчанию, его нельзя удалить.

Изменить адрес доставки:
Потребление:

  • ID пользователя

  • идентификатор адреса доставки

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

  • Адрес доставки не может быть пустым, а адрес доставки существует.

  • Определите, является ли этот адрес доставки адресом доставки пользователя

Настройки адреса по умолчанию:
Потребление:

  • ID пользователя

  • идентификатор адреса доставки

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

  • Адрес доставки не может быть пустым, а адрес доставки существует.

  • Определите, является ли этот адрес доставки адресом доставки пользователя

Запрос списка адресов доставки:
Потребление:

  • ID пользователя

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

Единый запрос адреса доставки:
Потребление:

  • ID пользователя

  • идентификатор адреса доставки

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

  • Адрес доставки не может быть пустым, а адрес доставки существует.

  • Определите, является ли этот адрес доставки адресом доставки пользователя

Оценка ограничений и выбор технологии

Для списка ограничений и функций, перечисленных выше, я выбираю для анализа несколько типичных сценариев обработки исключений: добавить адрес доставки, удалить адрес доставки и получить список адресов доставки.

Итак, каковы необходимые резервы знаний? Давайте посмотрим на функцию адреса доставки:

При добавлении адреса доставки необходимо проверить идентификатор пользователя и информацию об объекте адреса доставки Итак, как мы выбираем инструменты для непустых суждений? Традиционное суждение выглядит следующим образом:

/**
 * 添加地址
 * @param uid
 * @param address
 * @return
 */
public Address addAddress(Integer uid,Address address){
    if(null != uid){
        //进行处理..
    }
    return null;
}


В приведенном выше примере, если только uid считается пустым, все в порядке.Если некоторые необходимые атрибуты в адресе объекта признаны пустыми, в случае многих полей, это не что иное, как катастрофа.

Итак, как мы должны делать эти суждения об участии?Позвольте мне представить вам два важных момента:

  • Класс Preconditions в Guava реализует оценку многих методов ввода.

  • Спецификация проверки jsr 303 (текущая реализация - это hibernate-validator, реализованный hibernate)

Если использовать эти два рекомендательных метода, оценка параметров станет намного проще. **Рекомендуется больше использовать эти зрелые технологии и наборы инструментов JAR, что может уменьшить большую часть ненужной рабочей нагрузки. **** Нам просто нужно сосредоточиться на бизнес-логике. **Из-за этих суждений он больше не будет откладывать.

Как элегантно оформить исключения Java

введение домена

В соответствии со сценарием проекта требуются две модели предметной области: одна — объект пользователя, а другая — объект адреса.

Адресный домен выглядит следующим образом:

@Entity
@Data
public class Address {
    @Id
    @GeneratedValue
    private Integer id;
    private String province;//省
    private String city;//市
    private String county;//区
    private Boolean isDefault;//是否是默认地址

    @ManyToOne(cascade={CascadeType.ALL})
    @JoinColumn()
    private User user;
}


Пользовательский домен выглядит следующим образом:

@Entity
@Data
public class User {
    @Id
   @GeneratedValue
   private Integer id;
   private String name;//姓名

    @OneToMany(cascade= CascadeType.ALL,mappedBy="user",fetch = FetchType.LAZY)
        private Set<Address> addresses;
}


Хорошо, приведенное выше является моделью отношений, а отношения между пользователем и адресом доставки — это отношения 1-n. @Data выше использует инструмент под названием lombok, который автоматически генерирует такие методы, как Setter и Getter, что очень удобно в использовании, заинтересованные читатели могут узнать о нем самостоятельно.

Введение

Для уровня подключения к данным мы используем инфраструктуру spring-data-jpa, которая требует, чтобы нам нужно было только наследовать интерфейсы, предоставляемые инфраструктурой, и называть методы в соответствии с соглашениями для выполнения операций с базой данных, которые мы хотим.

Работа с пользовательской базой данных выглядит следующим образом:

@Repository
public interface IUserDao extends JpaRepository<User,Integer> {

}


Адрес доставки работает следующим образом:

@Repository
public interface IAddressDao extends JpaRepository<Address,Integer> {

}


Как могут видеть читатели, нашему DAO нужно только наследовать JpaRepository, он уже помог нам выполнить основные CURD и другие операции, если вы хотите узнать больше об этом проекте Spring-данных, пожалуйста, обратитесь к официальному документу Spring, это чем не планируйте наше изучение аномалий.

Дизайн исключений службы

хорошо, наконец, в центре нашего внимания, мы должны выполнить некоторые операции службы: добавить адрес доставки, удалить адрес доставки и получить список адресов доставки.

Сначала взгляните на мое определение интерфейса службы:

public interface IAddressService {

/**
 * 创建收货地址
 * @param uid
 * @param address
 * @return
 */
Address createAddress(Integer uid,Address address);

/**
 * 删除收货地址
 * @param uid
 * @param aid
 */
void deleteAddress(Integer uid,Integer aid);

/**
 * 查询用户的所有收货地址
 * @param uid
 * @return
 */
List<Address> listAddresses(Integer uid);
}


Сосредоточимся на реализации:

Добавить адрес доставки

Во-первых, давайте посмотрим на ограничения, которые были разобраны ранее:

Потребление:

  • ID пользователя

  • Информация об адресе доставки

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

  • Обязательные поля для адреса доставки не могут быть пустыми

  • Если у пользователя нет адреса доставки, установите адрес доставки в качестве адреса доставки по умолчанию при создании адреса доставки.

Сначала взгляните на следующую реализацию кода:

 @Override
public Address createAddress(Integer uid, Address address) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    Preconditions.checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new RuntimeException("找不到当前用户!");
    }
    //2.收货地址的必要字段不能为空
    BeanValidators.validateWithException(validator, address);
    //3.如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址
    if(ObjectUtils.isEmpty(user.getAddresses())){
        address.setIsDefault(true);
    }

    //============ 以下为正常执行的业务逻辑   ==============
    address.setUser(user);
    Address result = addressDao.save(address);
    return result;
}


Среди них три ограничения, описанные выше, выполнены.Когда три ограничения выполнены, может выполняться нормальная бизнес-логика, в противном случае будет выброшено исключение (вообще, здесь рекомендуется выбрасывать исключение времени выполнения — RuntimeException).

Представьте следующие методы, которые я использовал выше:

1. Preconfitions.checkNotNull(T t) Это определяется с помощью com.google.common.base.Preconditions в Guava, Поскольку в сервисе используется много проверок, рекомендуется изменить Preconfitions на статический импорт:

import static com.google.common.base.Preconditions.checkNotNull; 


Конечно, инструкции в github Guava также предполагают, что мы используем его таким образом.

2. BeanValidators.validateWithException(валидатор, адрес);
Это делается с использованием спецификации jsr 303, реализованной спящим режимом, необходимо передать валидатор и объект, который необходимо проверить, поэтому как получить валидатор следующим образом:

@Configuration
public class BeanConfigs {

@Bean
public javax.validation.Validator getValidator(){
    return new LocalValidatorFactoryBean();
}
}


Он получит объект Validator, и тогда мы сможем использовать его, внедрив его в сервис:

 @Autowired     
private Validator validator ;


Так как же реализован класс BeanValidators? На самом деле метод реализации очень прост, пока можно судить о аннотации jsr 303.

Так где же написаны аннотации jsr 303? Конечно, это написано в классе сущности адреса:

@Entity
@Setter
@Getter
public class Address {
@Id
    @GeneratedValue
    private Integer id;
    @NotNull
private String province;//省
@NotNull
private String city;//市
@NotNull
private String county;//区
private Boolean isDefault = false;//是否是默认地址

@ManyToOne(cascade={CascadeType.ALL})
@JoinColumn()
private User user;
}


Запишите ограничения, необходимые для вынесения суждений.Если это разумно, вы можете выполнять бизнес-операции для работы с базой данных.

Проверка этого блока необходима по одной из основных причин: такая проверка позволяет избежать вставки грязных данных.

Если у читателя есть опыт официального запуска, он может понять такую ​​вещь, ** любую ошибку кода можно допустить и изменить, но если есть проблема с грязными данными, то это может быть разрушительной катастрофой. **Проблемы программы можно изменить, но внешний вид грязных данных восстановить невозможно. Вот почему необходимо оценивать ограничения в сервисе, а затем выполнять операции бизнес-логики.

Суждение здесь представляет собой суждение бизнес-логики, которое является скрининговым суждением с точки зрения бизнеса.Кроме того, во многих сценариях могут быть разные бизнес-условия, и вам нужно делать это только в соответствии с требованиями.

Резюме ограничений выглядит следующим образом:

  • Основные ограничения суждения (базовые суждения, такие как нулевые значения)

  • Ограничения атрибутов объекта (для удовлетворения основных суждений, таких как jsr 303)

  • Ограничения бизнес-условий (различные бизнес-ограничения, предлагаемые требованиями)

Когда эти три пункта удовлетворены, можно приступать к следующему шагу.

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

Считается ли выбрасывание только RuntimeException изящным выбрасыванием? Конечно, нет, для исключений, создаваемых в сервисе, я думаю, есть примерно два способа их генерирования:

  • Выдает исключение с кодом состояния RumtimeException

  • Выдает исключение RuntimeException указанного типа

По сравнению с этими двумя исключениями, первое исключение означает, что все мои исключения генерируют исключения RuntimeException, но они должны приносить код состояния, и вызывающая сторона может проверить, какой сервис их кидает по коду состояния.

Второй тип исключения относится к тому, какое исключение выдается в службе, настраивается указанная ошибка исключения, а затем выдается выполняемое исключение.

Вообще говоря, если к системе нет других специальных требований, рекомендуется использовать второй метод при разработке и проектировании. Но, например, исключения, такие как основные суждения, могут полностью управляться с использованием библиотеки классов, предоставленной нам guava. Исключения jsr 303 также можно обрабатывать с помощью собственных инкапсулированных классов оценки исключений, поскольку эти два исключения являются базовыми оценками, и для них не требуется указывать специальные исключения. Но для исключения, вызванного третьим суждением об ограничении условия обязательства, необходимо создать исключение указанного типа.

за

throw new RuntimeException("找不到当前用户!");


Определите конкретный класс исключения для выполнения оценки этого исключения обязательства:

public class NotFindUserException extends RuntimeException {
public NotFindUserException() {
    super("找不到此用户");
}

public NotFindUserException(String message) {
    super(message);
}
}


Затем измените это на:

throw new NotFindUserException("找不到当前用户!");
or

throw new NotFindUserException();


Хорошо, благодаря вышеуказанным модификациям сервисного уровня изменения в коде выглядят следующим образом:

@Override
public Address createAddress(Integer uid, Address address) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new NotFindUserException("找不到当前用户!");
    }
    //2.收货地址的必要字段不能为空
    BeanValidators.validateWithException(validator, address);
    //3.如果用户还没有收货地址,当此收货地址创建时设置成默认收货地址
    if(ObjectUtils.isEmpty(user.getAddresses())){
        address.setIsDefault(true);
    }

    //============ 以下为正常执行的业务逻辑   ==============
    address.setUser(user);
    Address result = addressDao.save(address);
    return result;
}


Такой сервис кажется более стабильным и понятным.

Удалить адрес доставки:

Потребление:

  • ID пользователя

  • идентификатор адреса доставки

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

  • Адрес доставки не может быть пустым, а адрес доставки существует.

  • Определите, является ли этот адрес доставки адресом доставки пользователя

  • Определите, является ли этот адрес доставки адресом доставки по умолчанию. Если это адрес доставки по умолчанию, его нельзя удалить.

Аналогичен вышеописанному добавлению адреса доставки, поэтому повторяться не буду, сервис удаления устроен следующим образом:

@Override
public void deleteAddress(Integer uid, Integer aid) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new NotFindUserException();
    }
    //2.收货地址不能为空,且此收货地址确实是存在的
    checkNotNull(aid);
    Address address = addressDao.findOne(aid);
    if(null == address){
        throw new NotFindAddressException();
    }
    //3.判断此收货地址是否是用户的收货地址
    if(!address.getUser().equals(user)){
        throw new NotMatchUserAddressException();
    }
    //4.判断此收货地址是否为默认收货地址,如果是默认收货地址,那么不能进行删除
    if(address.getIsDefault()){
       throw  new DefaultAddressNotDeleteException();
    }

    //============ 以下为正常执行的业务逻辑   ==============
    addressDao.delete(address);
}


Разработаны четыре связанных класса исключений:
NotFindUserException,NotFindAddressException,NotMatchUserAddressException,DefaultAddressNotDeleteException.
Различные исключения создаются в соответствии с различными бизнес-требованиями.

Получить список адресов доставки:
Потребление:

  • ID пользователя

ограничение:

  • Идентификатор пользователя не может быть пустым, и пользователь существует

код показывает, как показано ниже:

 @Override
public List<Address> listAddresses(Integer uid) {
    //============ 以下为约束条件   ==============
    //1.用户id不能为空,且此用户确实是存在的
    checkNotNull(uid);
    User user = userDao.findOne(uid);
    if(null == user){
        throw new NotFindUserException();
    }

    //============ 以下为正常执行的业务逻辑   ==============
    User result = userDao.findOne(uid);
    return result.getAddresses();
}


дизайн исключений API

Существует примерно два способа броска:

  • Выдает исключение с кодом состояния RumtimeException

  • Выдает исключение RuntimeException указанного типа

Это упоминается при разработке исключения сервисного уровня.Посредством введения сервисного уровня мы выбираем второй метод генерации, когда сервисный уровень генерирует исключение.Разница в том, что нам нужно использовать уровень API для генерации исключения.Там есть два способа генерировать: указать тип исключения API и указать соответствующий код состояния, а затем генерировать исключение Ядро этого дизайна исключения состоит в том, чтобы пользователь, который вызывает API, более четко знал о возникновении исключения Подробности.

В дополнение к выдаче исключений нам также необходимо сделать соответствующую таблицу, показывающую детали исключения, соответствующие коду состояния, и возможные проблемы исключения, которые будут отображаться пользователю, что удобно пользователю для запроса. (например, документация по API, предоставленная github, документация по API, предоставленная WeChat и т. д.), есть еще одно преимущество: если пользователю необходимо настроить подсказку, он может изменить подсказку в соответствии с возвращенным кодом состояния.

ограничения проверки API

В первую очередь для оформления апи нужен объект dto, который отвечает за общение и передачу данных с звонилкой, а затем dto->domain передается сервису для работы, что необходимо отметить.

Второй момент заключается в том, что в дополнение к базовому суждению (нулевое суждение) и проверке jsr 303 упомянутой службы уровень API также должен выполнить соответствующую проверку. вызов не удался. Дальнейший доступ к услуге не должен осуществляться с незаконными данными.

Тогда читатель может немного запутаться, дело не в том, что сервис верифицирован, почему апи-слой еще нужно верифицировать? Вот разработанная концепция: закон Мерфи в программировании, если проверка данных на уровне API небрежна, возможно, что незаконные данные будут доставлены на уровень обслуживания, а затем грязные данные будут сохранены в базе данных.

Таким образом, суть тщательного программирования заключается в следующем:Никогда не верьте, что данные, которые вы получаете, являются законными.

дизайн исключений API

При разработке исключений уровня API, как мы сказали выше, вам необходимо предоставить коды ошибок и информацию об ошибках, затем вы можете спроектировать таким образом, чтобы предоставить общее исключение суперкласса API и другие различные исключения API, наследуемые от этого суперкласса:

public class ApiException extends RuntimeException {
protected Long errorCode ;
protected Object data ;

public ApiException(Long errorCode,String message,Object data,Throwable e){
    super(message,e);
    this.errorCode = errorCode ;
    this.data = data ;
}

public ApiException(Long errorCode,String message,Object data){
    this(errorCode,message,data,null);
}

public ApiException(Long errorCode,String message){
    this(errorCode,message,null,null);
}

public ApiException(String message,Throwable e){
    this(null,message,null,e);
}

public ApiException(){

}

public ApiException(Throwable e){
    super(e);
}

public Long getErrorCode() {
    return errorCode;
}

public void setErrorCode(Long errorCode) {
    this.errorCode = errorCode;
}

public Object getData() {
    return data;
}

public void setData(Object data) {
    this.data = data;
}
}


Затем отдельно определите исключения слоя API:
ApiDefaultAddressNotDeleteException,ApiNotFindAddressException,ApiNotFindUserException,ApiNotMatchUserAddressException
В качестве примера возьмем адрес по умолчанию, который нельзя удалить:

public class ApiDefaultAddressNotDeleteException extends ApiException {

public ApiDefaultAddressNotDeleteException(String message) {
    super(AddressErrorCode.DefaultAddressNotDeleteErrorCode, message, null);
}
}


AddressErrorCode.DefaultAddressNotDeleteErrorCode
Это код ошибки, который необходимо сообщить вызывающему абоненту. Классы кодов ошибок следующие:

public abstract class AddressErrorCode {
    public static final Long DefaultAddressNotDeleteErrorCode = 10001L;//默认地址不能删除
    public static final Long NotFindAddressErrorCode = 10002L;//找不到此收货地址
    public static final Long NotFindUserErrorCode = 10003L;//找不到此用户
    public static final Long NotMatchUserAddressErrorCode = 10004L;//用户与收货地址不匹配
}


ок, значит исключение слоя апи спроектировано.Скажу еще вот что.В классе кодов ошибок AddressErrorCode хранятся возможные коды ошибок.Разумнее вынести его в конфигурационный файл для управления.

API обрабатывает исключения

Уровень API будет вызывать уровень службы, а затем обрабатывать все исключения, которые появляются в службе Прежде всего, нам нужно убедиться, что уровень API должен быть очень легким, в основном делая его функцией пересылки (параметры интерфейса, передаваемые в служебные параметры, возвращают данные вызывающей стороне, эти три основные функции), а затем выполняют обработку исключений для вызова метода, переданного служебному параметру.

Вот только пример добавления адреса:

 @Autowired
private IAddressService addressService;


/**
 * 添加收货地址
 * @param addressDTO
 * @return
 */
@RequestMapping(method = RequestMethod.POST)
public AddressDTO add(@Valid @RequestBody AddressDTO addressDTO){
    Address address = new Address();
    BeanUtils.copyProperties(addressDTO,address);
    Address result;
    try {
        result = addressService.createAddress(addressDTO.getUid(), address);
    }catch (NotFindUserException e){
        throw new ApiNotFindUserException("找不到该用户");
    }catch (Exception e){//未知错误
        throw new ApiException(e);
    }
    AddressDTO resultDTO = new AddressDTO();
    BeanUtils.copyProperties(result,resultDTO);
    resultDTO.setUid(result.getUser().getId());

    return resultDTO;
}


Решение здесь состоит в том, чтобы оценить тип исключения при вызове службы, затем преобразовать любое исключение службы в исключение API, а затем создать исключение API, что является широко используемым методом преобразования исключений. Точно так же удаление адреса доставки и получение адреса доставки обрабатываются одинаково и здесь не повторяются.

преобразование исключений API

Я объяснил, как генерировать исключения и как преобразовывать служебные исключения в исключения API. Завершается ли обработка исключений путем их прямого генерирования в исключения API? Ответ — нет, когда возникает исключение API, нам нужно сделать данные (json или xml), возвращаемые исключением API, понятными пользователю, затем нам нужно преобразовать исключение API в объект dto (ErrorDTO) , см. следующий код:

@ControllerAdvice(annotations = RestController.class)
class ApiExceptionHandlerAdvice {

/**
 * Handle exceptions thrown by handlers.
 */
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseEntity<ErrorDTO> exception(Exception exception,HttpServletResponse response) {
    ErrorDTO errorDTO = new ErrorDTO();
    if(exception instanceof ApiException){//api异常
        ApiException apiException = (ApiException)exception;
        errorDTO.setErrorCode(apiException.getErrorCode());
    }else{//未知异常
        errorDTO.setErrorCode(0L);
    }
    errorDTO.setTip(exception.getMessage());
    ResponseEntity<ErrorDTO> responseEntity = new ResponseEntity<>(errorDTO,HttpStatus.valueOf(response.getStatus()));
    return responseEntity;
}

@Setter
@Getter
class ErrorDTO{
    private Long errorCode;
    private String tip;
}
}


Хорошо, это завершает преобразование исключений API в объекты DTO, понятные пользователям.В коде используется @ControllerAdvice, который представляет собой специальную обработку аспектов, предоставляемую Spring MVC.

При возникновении исключения при вызове интерфейса апи пользователь также может получить данные в обычном формате, например, когда пользователя нет (uid равен 2), для этого пользователя добавляется адрес доставки, после почтальона (используется плагин Google) для имитации http-запросов) Данные:

{
  "errorCode": 10003,
  "tip": "找不到该用户"
}


Суммировать

В этой статье основное внимание уделяется разработке исключений. Передача API и обработка служб должны быть оптимизированы. Например, доступ к интерфейсу API должен быть зашифрован с помощью https, интерфейс API требует авторизации OAuth2.0 или интерфейс API требует аутентификации по подписи и другие вопросы В этой статье основное внимание уделяется тому, как работать с исключениями, поэтому читателям нужно обратить внимание только на проблемы и методы обработки, связанные с исключениями.

Рекомендуемое чтение

Java Notes Daquan.md

Удивительно, на этом веб-сайте Java есть все виды проектов! https://markerhub.com

Мастер UP этой станции B, java действительно хорош!

классно! Последнюю версию идей программирования на Java можно прочитать онлайн!