Рекомендации по унифицированной обработке исключений Spring Boot

Spring Boot

Резюме:Обработка исключений SpringBoot.

FundebugПерепечатано с разрешения, авторские права принадлежат оригинальному автору.

предисловие

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

  • Когда нужно снимать(try-catch) исключение, когда его нужно выбросить (throws) исключение для верхнего слоя.
  • существуетdaoЗахват слоя все еще выполняетсяserviceзахват, ещеcontrollerЗахват слоя.
  • Что делать после возникновения исключения Как вернуться на страницу с сообщением об ошибке.

пример счетчика обработки исключений

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

Выводить на консоль только после перехвата исключения

интерфейсный код

$.ajax({
    type: "GET",
    url: "/user/add",
    dataType: "json",
    success: function(data){
        alert("添加成功");
    }
});

внутренний код

try {
    // do something
} catch (Exception e) {
    e.printStackTrace();
}

Это самый метод обработки исключений, который я видел.Если это метод добавления товаров, фронтенд отправляет запрос на бэкенд через ajax, ожидая вернуть json-информацию для указания результата добавления.Но если в этом коде возникает исключение :

  • Затем сцена, которую видит пользователь, это нажатие кнопки добавления, но ничего не происходит (на самом деле возвращается страница ошибки 500, но здесь интерфейс не слушает событие ошибки, только событие успеха. Но даже если добавленоerror: function(data) {alert("添加失败");}) и что? Пользователь не знает, почему это не удалось.

  • За кулисамиe.printStackTrace()Выведенный в консоль лог так же будет похоронен в длинном логе, и вполне вероятно, что исключение вывода не будет видно.Но это не самый худший случай, даже хужеe.printStackTrace()ничего такого,catchБлок пустой, так что в бэкенд-консоли ничего не видно, и этот код всегда будет лежать в системе в засаде, как невидимая бомба.

запутанный путь назад

интерфейсный код

$.ajax({
    type: "GET",
    url: "/goods/add",
    dataType: "json",
    success: function(data) {
        if (data.flag) {
            alert("添加成功");
        } else {
            alert(data.message);
        }
    },
    error: function(data){
        alert("添加失败");
    }
});

внутренний код

@RequestMapping("/goods/add")
@ResponseBody
public Map add(Goods goods) {
    Map map = new HashMap();
    try {
        // do something
        map.put(flag, true);
    } catch (Exception e) {
        e.printStackTrace();
        map.put("flag", false);
        map.put("message", e.getMessage());
    }
    reutrn map;
}

После перехвата исключения таким образом возвращается сообщение об ошибке, а передний план выполнил некоторую обработку, что выглядит идеально?HashMapсерединаflagиmessageС такой строкой легко обращаться как с ключом, например, вы вызываете здесьmessage, Другие люди называютmsg,даже иногда руки дрожат и ошибаются,что делать?Сменить ресепшн наmsgИли другие персонажи? Передняя часть и задняя часть были изменены туда и обратно вот так?

Более того, в случае кейса А возвращать json, в случае кейса Б перенаправлять на определенную страницу, что еще более запутанно, очень хлопотно иметь дело с такой неоднородной структурой.

Спецификация обработки исключений

Так как это собираетсяОбъединитьОбработка исключений, то должна быть спецификация, которую нельзя перепутать.Эта спецификация включает в себя front-end и back-end.

не ловить никаких исключений

да, не вв бизнес-кодеОтлавливать исключения, то есть все исключения в слоях dao, service и controller кидают на верхний слой.Это не вызовет кучу бизнес-кодовtry-catchЭто загромождает бизнес-код.

Единый набор возвращаемых результатов

Не используйте Map для возврата результатов, Map нелегко контролировать и легко ошибиться, вы должны определить класс сущности Java.Чтобы представить унифицированные возвращаемые результаты, например, определить класс сущности:

public class ResultBean<T> {
    private int code;
    private String message;
    private Collection<T> data;

    private ResultBean() {

    }

    public static ResultBean error(int code, String message) {
        ResultBean resultBean = new ResultBean();
        resultBean.setCode(code);
        resultBean.setMessage(message);
        return resultBean;
    }

    public static ResultBean success() {
        ResultBean resultBean = new ResultBean();
        resultBean.setCode(0);
        resultBean.setMessage("success");
        return resultBean;
    }

    public static <V> ResultBean<V> success(Collection<V> data) {
        ResultBean resultBean = new ResultBean();
        resultBean.setCode(0);
        resultBean.setMessage("success");
        resultBean.setData(data);
        return resultBean;
    }

    // getter / setter 略
}
  • Обычный случай: звонокResultBean.success()илиResultBean.success(Collection<V> data), не нужно возвращать данные, то есть вызывать первое, нужно возвращать данные, вызывать второе.Например:
@RequestMapping("/goods/add")
@ResponseBody
public ResultBean<Goods> getAllGoods() {
    List<Goods> goods = goodsService.findAll();
    return ResultBean.success(goods);
}
@RequestMapping("/goods/update")
@ResponseBody
public ResultBean updateGoods(Goods goods) {
    goodsService.update(goods);
    return ResultBean.success();
}

Обычно нужно вызывать только метод запросаResultBean.success(Collection<V> data)чтобы вернуть N фрагментов данных, должны быть вызваны другие методы, такие как удаление, изменение и т. д.ResultBean.success(), то есть в бизнес-коде обрабатывается только правильная функция, а по исключению не выносится никакого суждения.Нет необходимости судить о количестве обновлений обновления или удаления (личное предложение, на самом деле нужно основываться на бизнес). Пока не выдается исключение, мы считаем, что операция пользователя выполнена успешно. И оперативная информация об успешной операции обрабатывается во внешнем интерфейсе, не возвращайте такие поля, как «операция выполнена успешно», в фоновом режиме.

Информация, полученная на стойке регистрации:

{
    "code": 0,
    "message": "success",
    "data": [
        {
            "name": "商品1",
            "price": 50.00,
        },
        {
            "name": "商品2",
            "price": 99.99,
        }
    ]
}
  • Генерация исключения: после генерации исключения мы должны вызватьResultBean.error(int code, String message), чтобы вернуть код состояния и сообщение об ошибке, мы согласилисьcode0 означает, что операция прошла успешно,1или2Одинаковые положительные числа указывают на ошибки пользовательского ввода,-1, -2Такие отрицательные числа указывают на системные ошибки.

Информация, полученная на стойке регистрации:

{
    "code": -1,
    "message": "XXX 参数有问题, 请重新填写",
    "data": null
}

Интерфейсная унифицированная обработка:

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

/**
 * 显示错误信息
 * @param result: 错误信息
 */
function showError(s) {
    alert(s);
}

/**
 * 处理 ajax 请求结果
 * @param result: ajax 返回的结果
 * @param fn: 成功的处理函数 ( 传入data: fn(result.data) )
 */
function handlerResult(result, fn) {
    // 成功执行操作,失败提示原因
    if (result.code == 0) {
        fn(result.data);
    }
    // 用户操作异常, 这里可以对 1 或 2 等错误码进行单独处理, 也可以 result.code > 0 来粗粒度的处理, 根据业务而定.
    else if (result.code == 1) {
        showError(result.message);
    }
    // 系统异常, 这里可以对 -1 或 -2 等错误码进行单独处理, 也可以 result.code > 0 来粗粒度的处理, 根据业务而定.
    else if (result.code == -1) {
        showError(result.message);
    }
    // 如果进行细粒度的状态码判断, 那么就应该重点注意这里没出现过的状态码. 这个判断仅建议在开发阶段保留用来发现未定义的状态码.
    else {
        showError("出现未定义的状态码:" + result.code);
    }
}

/**
 * 根据 id 删除商品
 */
function deleteGoods(id) {
    $.ajax({
        type: "DELETE",
        url: "/goods/delete",
        dataType: "json",
        success: function(result){
            handlerResult(result, deleteDone);
        }
    });
}

function deleteDone(data) {
    alert("删除成功");
}

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

Затем сосредоточьтесь на методе отправки запроса и обработки правильного результата, например здесь функция deleteDone, которая используется для обработки подсказки для пользователя, когда операция выполнена успешно.Разумно, и сообщение об ошибке известно только фон, поэтому фон нужно вернуть.

Серверная часть обрабатывает исключения единообразно

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

К счастью, Spring предоставляет нам аннотацию для унифицированной обработки исключений:

@ControllerAdvice
@ResponseBody
public class WebExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class);

    @ExceptionHandler
    public ResultBean unknownAccount(UnknownAccountException e) {
        log.error("账号不存在", e);
        return ResultBean.error(1, "账号不存在");
    }

    @ExceptionHandler
    public ResultBean incorrectCredentials(IncorrectCredentialsException e) {
        log.error("密码错误", e);
        return ResultBean.error(-2, "密码错误");
    }

    @ExceptionHandler
    public ResultBean unknownException(Exception e) {
        log.error("发生了未知异常", e);
        // 发送邮件通知技术人员.
        return ResultBean.error(-99, "系统出现错误, 请联系网站管理员!");
    }
}

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

Суммировать

Кратко опишите метод унифицированной обработки исключений:

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

Простой демонстрационный проект:GitHub.com/Чжао, июнь 1998 г.…

Автор этой статьи:Чжао ЦзюньСсылка на эту статью: Woohoo, Чжао Цзюнь, IM/spring boot-… Уведомление об авторских правах:Все статьи в этом блоге, если не указано иное, используютBY-NC-SAсоглашение. Пожалуйста, укажите источник!