Как ни раздражает попытка поймать, я такой капризный! Получите исключение за один раз!

Java

задний план

В процессе разработки программного обеспечения неизбежно приходится иметь дело с различными исключениями, насколько я понимаю, как минимум больше половины времени приходится на различные исключения, поэтому ошибок в коде будет очень много.try {...} catch {...} finally {...}Блоки кода не только содержат много избыточного кода, но и влияют на читабельность кода. Сравните два изображения ниже, чтобы увидеть, какой стиль кода вы сейчас пишете? И какой стиль кодирования вы предпочитаете?

уродливая попытка поймать блок

Элегантный контроллер

Приведенный выше пример все еще толькоControllerслой, если он находится вServiceслоев, может быть большеtry catchкодовый блок. Это серьезно повлияет на читабельность кода, «эстетичность».

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

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

Итак, вопрос в том, как изящно обрабатывать различные исключения?

Что такое унифицированная обработка исключений

SpringДобавлена ​​аннотация в версии 3.2@ControllerAdvice,С участием@ExceptionHandler,@InitBinder,@ModelAttributeДругие аннотации аннотаций используются вместе.Что касается функций этих аннотаций, я не буду здесь вдаваться в подробности.Если вы не понимаете, вы можете обратиться к новой аннотации @ControllerAdvice в Spring 3.2, чтобы сначала получить общее представление.

Однако только аннотации связаны с обработкой исключений.@ExceptionHandler, буквально, естьобработчик исключенийзначит, его действительное действие таково: если в определенномControllerКласс определяет метод обработки исключений и добавляет аннотацию к методу, затем, когда возникает указанное исключение, будет выполнен метод обработки исключений, который может использовать привязку данных, предоставляемую springmvc, например, внедрение HttpServletRequest и т. д., и может также примите текущий брошенный объект Throwable.

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

Конечно, вы можете сказать, а затем определить что-то вродеBaseControllerБазовый класс, так что все в порядке.

Хотя это так, это все же не идеально, потому что такой код является навязчивым и связанным. простоController, Почему я должен наследовать такой класс, если другие базовые классы уже унаследованы. каждый знаетJavaТолько один класс может быть унаследован.

Есть ли решение, которое не требуетControllerсцепление, также можно определитьобработчик исключенийприменим ко всем контроллерам? Так что обратите внимание@ControllerAdviceПроще говоря, эта аннотация может применять обработчики исключений ко всем контроллерам, а не к одному контроллеру. С помощью этой аннотации мы можем добиться: в отдельном месте, например отдельном классе, определить набор механизмов обработки различных исключений, а затем добавить аннотации в сигнатуру класса@ControllerAdvice, единая пара不同阶段的,不同异常для обработки. Это принцип единой обработки исключений.

Обратите внимание, что вышеуказанные исключения классифицируются по этапам, которые можно условно разделить на: введитеControllerпредыдущее исключение иServiceИсключение слоя, пожалуйста, обратитесь к следующему рисунку для получения подробной информации:

Исключения на разных этапах

Цель

Уничтожить более 95%try catchкодовые блоки с элегантнымиAssert(Утверждение) для проверки бизнес-исключений, фокусируясь только на бизнес-логике, не тратя много энергии на запись избыточных данных.try catchкодовый блок.

Единая практика обработки исключений

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

Замените исключение throw на Assert

предположительноAssert(断言)Всем знакомы такие, какSpringсемьяorg.springframework.util.Assert, который часто используется, когда мы пишем тестовые примеры.Использование утверждений может дать нам необычное шелковистое ощущение при кодировании, например:

    @Test    
    public void test1() {    
    ...        
    User user = userDao.selectById(userId);
    Assert.notNull(user, "用户不存在.");
        ...   
 } 

 @Test    
    public void test2() {       
     // 另一种写法        
    User user = userDao.selectById(userId);
    if (user == null) {
            throw new IllegalArgumentException("用户不存在.");
        }
    }

Считаете ли вы, что первый способ записи непустого суждения очень элегантен, а второй способ записи относительно безобразен?if {...}кодовый блок. такой волшебныйAssert.notNull()Что происходит за кулисами? НижеAssertЧасть исходного кода:

public abstract class Assert {
    public Assert() {
    }

    public static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }}

можно увидеть,AssertНа самом деле, чтобы помочь намif {...}Упаковка, разве это не удивительно? Несмотря на простоту, нельзя отрицать, что опыт кодирования, по крайней мере, на высоте. Так можем ли мы подражатьorg.springframework.util.Assert, также напишите класс утверждения, но исключение, выдаваемое после сбоя утверждения, неIllegalArgumentExceptionЭти встроенные исключения, но наши собственные определенные исключения. Давайте попробуем.

Assertpublic interface Assert {
    /**
     * 创建异常
     * @param args
     * @return
     */
    BaseException newException(Object... args);
    /**
     * 创建异常
     * @param t
     * @param args
     * @return
     */
    BaseException newException(Throwable t, Object... args);
    /**
     * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
     *
     * @param obj 待判断对象
     */
    default void assertNotNull(Object obj) {
        if (obj == null) {
            throw newException(obj);
        }
    }
    /**
     * <p>断言对象<code>obj</code>非空。如果对象<code>obj</code>为空,则抛出异常
     * <p>异常信息<code>message</code>支持传递参数方式,避免在判断之前进行字符串拼接操作
     *
     * @param obj 待判断对象
     * @param args message占位符对应的参数列表
     */
    default void assertNotNull(Object obj, Object... args) {
        if (obj == null) {
            throw newException(args);
        }
    }}

надAssertМетод утверждения определяется с использованием метода интерфейса по умолчанию, а затем вы обнаружили, что когда утверждение терпит неудачу, выбрасываемое исключение не является конкретным исключением, а передается двумnewExceptionПредусмотрены методы интерфейса. Поскольку исключения, которые появляются в бизнес-логике, в основном соответствуют конкретным сценариям, таким как получение информации о пользователе на основе идентификатора пользователя, результат запросаnull, исключение, сгенерированное в это время, может бытьUserNotFoundException, и имеет определенный код исключения (например, 7001) и информацию об исключении «пользователь не существует». Итак, какое конкретное исключение выброшено, естьAssertРешает класс реализации.

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

Эмпатическое перечисление

пользовательское исключениеBaseExceptionЕсть 2 свойства, а именноcode,message, такая пара свойств, вы когда-нибудь думали о каком-нибудь классе, который вообще определяет эти два свойства? Да, класс перечисления. посмотрим как я будуEnumа такжеAssertВ сочетании, я верю, я заставлю ваши глаза сиять. следующее:

public interface IResponseEnum {
    int getCode();
    String getMessage();}
/**
 * <p>业务异常</p>
 * <p>业务处理时,出现异常,可以抛出该异常</p>
 */
public class BusinessException extends  BaseException {
    private static final long serialVersionUID = 1L;
    public BusinessException(IResponseEnum responseEnum, Object[] args, String message) {
        super(responseEnum, args, message);
    }
    public BusinessException(IResponseEnum responseEnum, Object[] args, String message, Throwable cause) {
        super(responseEnum, args, message, cause);
    }}public interface BusinessExceptionAssert extends IResponseEnum, Assert {
    @Override
    default BaseException newException(Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);
        return new BusinessException(this, args, msg);
    }
    @Override
    default BaseException newException(Throwable t, Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);
        return new BusinessException(this, args, msg, t);
    }}@Getter
@AllArgsConstructor
public enum ResponseEnum implements BusinessExceptionAssert {
    /**
     * Bad licence type
     */
    BAD_LICENCE_TYPE(7001, "Bad licence type."),
    /**
     * Licence not found
     */
    LICENCE_NOT_FOUND(7002, "Licence not found.")
    ;
    /**
     * 返回码
     */
    private int code;
    /**
     * 返回消息
     */
    private String message;
}

Глядя на это, есть ли какое-то чувство блеска?В примере кода определены два экземпляра перечисления:BAD_LICENCE_TYPE,LICENCE_NOT_FOUND, соответствующийBadLicenceTypeException,LicenceNotFoundExceptionДва исключения. Каждый раз, когда в будущем будет добавлено исключение, вам нужно будет добавить только экземпляр перечисления, и нет необходимости определять класс исключения для каждого исключения. Затем посмотрите, как его использовать, предполагая, чтоLicenceServiceс проверкойLicenceЕсть ли метод, как показано ниже:

/**
     * 校验{@link Licence}存在
     * @param licence
     */
    private void checkNotNull(Licence licence) {
        ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
    }

Без утверждений код может выглядеть так:

private void checkNotNull(Licence licence) {
        if (licence == null) {
            throw new LicenceNotFoundException();
            // 或者这样
            throw new BusinessException(7001, "Bad licence type.");
        }
    }

Объединение (наследование) с использованием классов enumAssert, просто определите разные экземпляры перечисления в соответствии с конкретными исключениями, как указано выше.BAD_LICENCE_TYPE,LICENCE_NOT_FOUND, вы можете генерировать определенные исключения для различных ситуаций (здесь имеется в виду наличие определенных кодов исключений и сообщений об исключениях), так что вам не нужно определять большое количество классов исключений, но также иметь хорошую читаемость утверждений.Конечно, преимущества этой схемы Это гораздо больше, пожалуйста, продолжайте читать следующий текст и медленно прочувствуйте его.

Примечание. Приведенные выше примеры относятся к конкретным службам, а некоторые ненормальные ситуации являются общими, например: занят сервер, сбой в сети, сбой в работе сервера, сбой при проверке параметров, ошибка 404 и т. д.CommonResponseEnum,ArgumentResponseEnum,ServletResponseEnumServletResponseEnumЭто будет подробно объяснено позже.

Определить унифицированный класс обработчика исключений

@Slf4j
@Component
@ControllerAdvice
@ConditionalOnWebApplication
@ConditionalOnMissingBean(UnifiedExceptionHandler.class)
public class UnifiedExceptionHandler {
    /**
     * 生产环境
     */
    private final static String ENV_PROD = "prod";
    @Autowired
    private UnifiedMessageSource unifiedMessageSource;
    /**
     * 当前环境
     */
    @Value("${spring.profiles.active}")
    private String profile;
    /**
     * 获取国际化消息
     *
     * @param e 异常
     * @return
     */
    public String getMessage(BaseException e) {
        String code = "response." + e.getResponseEnum().toString();
        String message = unifiedMessageSource.getMessage(code, e.getArgs());
        if (message == null || message.isEmpty()) {
            return e.getMessage();
        }
        return message;
    }
    /**
     * 业务异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public ErrorResponse handleBusinessException(BaseException e) {
        log.error(e.getMessage(), e);
        return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
    }
    /**
     * 自定义异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BaseException.class)
    @ResponseBody
    public ErrorResponse handleBaseException(BaseException e) {
        log.error(e.getMessage(), e);
        return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e));
    }
    /**
     * Controller上一层相关异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({
            NoHandlerFoundException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            // BindException.class,
            // MethodArgumentNotValidException.class
            HttpMediaTypeNotAcceptableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    @ResponseBody
    public ErrorResponse handleServletException(Exception e) {
        log.error(e.getMessage(), e);
        int code = CommonResponseEnum.SERVER_ERROR.getCode();
        try {
            ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf(e.getClass().getSimpleName());
            code = servletExceptionEnum.getCode();
        } catch (IllegalArgumentException e1) {
            log.error("class [{}] not defined in enum {}", e.getClass().getName(), ServletResponseEnum.class.getName());
        }
        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如404.
            code = CommonResponseEnum.SERVER_ERROR.getCode();
            BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
            String message = getMessage(baseException);
            return new ErrorResponse(code, message);
        }
        return new ErrorResponse(code, e.getMessage());
    }
    /**
     * 参数绑定异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BindException.class)
    @ResponseBody
    public ErrorResponse handleBindException(BindException e) {
        log.error("参数绑定校验异常", e);
        return wrapperBindingResult(e.getBindingResult());
    }
    /**
     * 参数校验异常,将校验失败的所有异常组合成一条错误信息
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseBody
    public ErrorResponse handleValidException(MethodArgumentNotValidException e) {
        log.error("参数绑定校验异常", e);
        return wrapperBindingResult(e.getBindingResult());
    }
    /**
     * 包装绑定异常结果
     *
     * @param bindingResult 绑定结果
     * @return 异常结果
     */
    private ErrorResponse wrapperBindingResult(BindingResult bindingResult) {
        StringBuilder msg = new StringBuilder();
        for (ObjectError error : bindingResult.getAllErrors()) {
            msg.append(", ");
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
        }
        return new ErrorResponse(ArgumentResponseEnum.VALID_ERROR.getCode(), msg.substring(2));
    }
    /**
     * 未定义异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ErrorResponse handleException(Exception e) {
        log.error(e.getMessage(), e);
        if (ENV_PROD.equals(profile)) {
            // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息.
            int code = CommonResponseEnum.SERVER_ERROR.getCode();
            BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR);
            String message = getMessage(baseException);
            return new ErrorResponse(code, message);
        }
        return new ErrorResponse(CommonResponseEnum.SERVER_ERROR.getCode(), e.getMessage());
    }}

Видно, что исключения разделены на несколько категорий выше, но на самом деле категорий всего две, однаServletException,ServiceException, помните упомянутое вышепоэтапно, что соответствует вводуControllerпредыдущее исключение иServiceисключение слоя; затемServiceExceptionДалее он делится на пользовательские исключения и неизвестные исключения. Соответствующее соотношение выглядит следующим образом:

  • Исключение перед входом в «Контроллер»: handleServletException, handleBindException, handleValidException
  • Пользовательское исключение: handleBusinessException, handleBaseException
  • Неизвестное исключение: handleException

В следующих разделах подробно описаны эти обработчики исключений.

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

handleServletException

Одинhttpзапрос, по прибытииControllerПеред запросом будет выполнена серия проверок между информацией о запросе и информацией о целевом контроллере. Вот краткое описание:

NoHandlerFoundException: первый за запросUrlУзнать, есть ли соответствующий контроллер, если нет, то будет выброшено исключение, которое всем очень знакомо404аномальный;

HttpRequestMethodNotSupportedException: при совпадении (результатом совпадения является список, разницаhttpразличными методами, такими как: Get, Post и т. д.), попробуйтеhttpМетод сопоставляется с контроллером списка, если нет соответствующегоhttpКонтроллер метода выдает исключение;

HttpMediaTypeNotSupportedException: затем сравните заголовок запроса с тем, что поддерживает контроллер, напримерcontent-typeзаголовок запроса, если подпись параметра контроллера содержит аннотации@RequestBody, но запрошенныйcontent-typeЗначение заголовка запроса не содержитapplication/json, то будет выброшено исключение (разумеется, это исключение будет выброшено не только в этом случае);

MissingPathVariableException: параметр пути не обнаружен. Например, URL-адрес:/licence/{licenceId}, подпись параметра содержит@PathVariable("licenceId"), когда запрошенный URL/licence, без явного определения URL как/licenceВ случае , это будет оцениваться как: параметр пути отсутствует;

MissingServletRequestParameterException: Отсутствует параметр запроса. Например, если параметр @RequestParam("licenceId") String licenceId определен, но этот параметр не передается при инициации запроса, будет выдано это исключение;

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

HttpMessageNotReadableException: с указанным вышеHttpMediaTypeNotSupportedExceptionПриведенный пример полностью противоположен, то есть заголовок запроса несет"content-type: application/json;charset=UTF-8", но полученный параметр не аннотируется@RequestBody, или процесс десериализации строки json, переносимой телом запроса, в pojo завершится неудачно, это исключение также будет выдано;

HttpMessageNotWritableException: возвращаемый pojo не удалось сериализовать в json, затем выдать это исключение;

handleBindException

Проверка параметра является ненормальной, что будет подробно объяснено позже.

handleValidException

Проверка параметра является ненормальной, что будет подробно объяснено позже.

обработчикBusinessException, обработчикBaseException

Обрабатывайте пользовательские бизнес-исключения, простоhandleBaseExceptionрассматривается в дополнение кBusinessExceptionНеожиданные все бизнес-исключения. На данный момент эти два могут быть объединены в один.

handleException

Обрабатывать все неизвестные исключения, например исключения, которые не позволяют работать с базой данных.

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

Необычное 404

Как упоминалось выше, когда запрос не соответствует контроллеру, он выдаетNoHandlerFoundExceptionИсключение, но по умолчанию это не так, по умолчанию появится страница, похожая на следующую:

Whitelabel Error Page

Как появилась эта страница? На самом деле, когда появляется 404, по умолчанию не выбрасывается исключение, аforwardперенаправить на/errorконтроллер,springтакже предоставляет значение по умолчаниюerrorконтроллер следующим образом:

Итак, как сделать так, чтобы 404 тоже выбрасывал исключение, просто вpropertiesДобавьте в файл следующую конфигурацию:

spring.mvc.throw-exception-if-no-handler-found=truespring.resources.add-mappings=false

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

Поймать исключение, соответствующее 404

Равномерно вернуть результат

Прежде чем проверять юниформ-обработчик исключений, кстати, юниформа возвращает результат. Грубо говоря, это фактически унификация структуры данных возвращаемого результата.code,messageЭто обязательное поле во всех возвращаемых результатах, и когда данные должны быть возвращены, требуется еще одно поле.dataПредставлять.

Итак, сначала определитеBaseResponseкак базовый класс для всех возвращаемых результатов;

Затем определите общий класс возвращаемого результатаCommonResponse,наследоватьBaseResponseи другие поляdata;

Чтобы различать успешные и неудачные результаты возврата, определите другойErrorResponse

Наконец, существует общий возвращаемый результат, т. е. возвращаемые данные содержат информацию о подкачке.Поскольку этот интерфейс относительно распространен, необходимо отдельно определить класс возвращаемого результата.QueryDataResponse, который наследуется отCommonResponse, просто поставьdataТип поля ограниченQueryDdata,QueryDdataСоответствующие поля пейджинговой информации определены вtotalCount,pageNo,pageSize,records.

Среди наиболее часто используемых толькоCommonResponseа такжеQueryDataResponse, но имя слишком длинное, почему бы вместо этого не определить 2 класса с очень простыми именами? тогдаRа такжеQRОн родился, и когда вы возвращаете результат в будущем, вам нужно только написать его так:new R<>(data),new QR<>(queryData).

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

Проверить унифицированную обработку исключений

Поскольку этот набор унифицированной обработки исключений можно назвать универсальным, все они могут быть спроектированы как одно целое.commonpackage, и каждый новый проект/модуль в будущем должен импортировать только этот package. Итак, чтобы проверить, вам нужно создать новый проект и ввестиcommonМешок.

основной код

Вот основной исходный код для проверки:

@Service
public class LicenceService extends ServiceImpl<LicenceMapper, Licence> {
    @Autowired
    private OrganizationClient organizationClient;
    /**
     * 查询{@link Licence} 详情
     * @param licenceId
     * @return
     */
    public LicenceDTO queryDetail(Long licenceId) {
        Licence licence = this.getById(licenceId);
        checkNotNull(licence);
        OrganizationDTO org = ClientUtil.execute(() -> organizationClient.getOrganization(licence.getOrganizationId()));
        return toLicenceDTO(licence, org);    }
    /**
     * 分页获取
     * @param licenceParam 分页查询参数
     * @return
     */
    public QueryData<SimpleLicenceDTO> getLicences(LicenceParam licenceParam) {
        String licenceType = licenceParam.getLicenceType();
        LicenceTypeEnum licenceTypeEnum = LicenceTypeEnum.parseOfNullable(licenceType);
        // 断言, 非空
        ResponseEnum.BAD_LICENCE_TYPE.assertNotNull(licenceTypeEnum);
        LambdaQueryWrapper<Licence> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Licence::getLicenceType, licenceType);
        IPage<Licence> page = this.page(new QueryPage<>(licenceParam), wrapper);
        return new QueryData<>(page, this::toSimpleLicenceDTO);
    }
    /**
     * 新增{@link Licence}
     * @param request 请求体
     * @return
     */
    @Transactional(rollbackFor = Throwable.class)
    public LicenceAddRespData addLicence(LicenceAddRequest request) {
        Licence licence = new Licence();
        licence.setOrganizationId(request.getOrganizationId());
        licence.setLicenceType(request.getLicenceType());
        licence.setProductName(request.getProductName());
        licence.setLicenceMax(request.getLicenceMax());
        licence.setLicenceAllocated(request.getLicenceAllocated());
        licence.setComment(request.getComment());
        this.save(licence);
        return new LicenceAddRespData(licence.getLicenceId());
    }
    /**
     * entity -> simple dto
     * @param licence {@link Licence} entity
     * @return {@link SimpleLicenceDTO}
     */
    private SimpleLicenceDTO toSimpleLicenceDTO(Licence licence) {
        // 省略
    }
    /**
     * entity -> dto
     * @param licence {@link Licence} entity
     * @param org {@link OrganizationDTO}
     * @return {@link LicenceDTO}
     */
    private LicenceDTO toLicenceDTO(Licence licence, OrganizationDTO org) {
        // 省略
    }
    /**
     * 校验{@link Licence}存在
     * @param licence
     */
    private void checkNotNull(Licence licence) {
        ResponseEnum.LICENCE_NOT_FOUND.assertNotNull(licence);
    }
}

PS: Здесь используется структура DAO.mybatis-plus. При запуске автоматически вставляются данные:

-- licenceINSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (1, 1, 'user','CustomerPro', 100,5);
INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (2, 1, 'user','suitability-plus', 200,189);
INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (3, 2, 'user','HR-PowerSuite', 100,4);
INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated)
VALUES (4, 2, 'core-prod','WildCat Application Gateway', 16,16);

-- organizationsINSERT INTO organization (id, name, contact_name, contact_email, contact_phone)
VALUES (1, 'customer-crm-co', 'Mark Balster', 'mark.balster@custcrmco.com', '823-555-1212');
INSERT INTO organization (id, name, contact_name, contact_email, contact_phone)
VALUES (2, 'HR-PowerSuite', 'Doug Drewry','doug.drewry@hr.com', '920-555-1212');
начать проверку
Перехватить пользовательское исключение

\1. Получить несуществующийlicenceПодробности:http://локальный:10000/лицензия/5. Успешно ответил на запросы: licenseId=1

проверить не пусто

Исключение Catch License not found

Licence not found

2. По несуществующемуlicence typeПолучатьlicenceСписок:http://localhost:10000/licence/list?licenceType=ddd. по желанию licence typeДля: пользователя, core-prod.

проверить не пусто

Поймать исключение «Плохой тип лицензии»

Bad licence type

Перехватите исключение перед входом в контроллер

\1. Доступ к несуществующему интерфейсу:http://localhost:10000/licence/list/ddd

Перехватить исключение 404

\ 2. Метод http не поддерживает:http://localhost:10000/licence

PostMapping

Метод запроса перехвата не поддерживается, исключение

Request method not supported

\3.Проверить исключение 1:http://localhost:10000/licence/list?licenceType=

getLicences

LicenceParam

Исключения проверки привязки параметров перехвата

licence type cannot be empty

4. Исключение проверки 2: почтовый запрос с использованием имитации почтальона здесь.

addLicence

LicenceAddRequest

URL-адрес запроса является результатом

Исключения проверки привязки параметров перехвата

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

Сбор аномальной информации

Поймать неизвестное исключение

Допустим, мы прямо сейчасLicenceдобавить полеtest, но без изменения структуры таблицы базы данных, затем получите доступ:http://локальный:10000/лицензия/1.

Добавить тестовое поле

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

Error querying database

резюме

Как видите, тестируемые исключения можно перехватывать, а затемcode,messageвернулся в виде . Для каждого проекта/модуля при определении бизнес-исключений необходимо только определить класс перечисления, а затем реализовать интерфейс.BusinessExceptionAssertи, наконец, определите соответствующий экземпляр перечисления для каждого бизнес-исключения вместо определения множества классов исключений. Это также очень удобно в использовании, и его использование похоже на утверждение.

расширять

В производственной среде при захватеНеизвестное исключениеилиServletException, потому что это длинная серия ненормальной информации, если она отображается непосредственно пользователю, она недостаточно профессиональна. Поэтому мы можем сделать следующее: когда будет обнаружено, что текущая среда является производственной средой, а затем напрямую вернуться " Сетевое исключение».

Рабочая среда возвращает «Сетевое исключение»

Текущую среду можно изменить следующими способами:

Изменить текущую среду на рабочую среду

Суммировать

использоватьутверждениеа такжеперечисляемый классВ сочетании с унифицированной обработкой исключений можно перехватить большинство исключений. Зачем говорить большинство исключений, потому что когда введениеspring cloud securityПосле этого будут исключения аутентификации/авторизации, исключения деградации службы шлюза, исключения вызовов между модулями, исключения удаленных сторонних служб и т. д. Методы захвата этих исключений не такие, как описанные в этой статье, но должны к ограничениям по объему, они не будут подробно описываться здесь, учтите, что в будущем будет отдельная статья-предисловие.

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

Получайте международные новости

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

Вы также можете использовать следующий объект jsonResult для обработки, а также опубликовать код.

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 没有登录
     * @param request
     * @param response
     * @param e
     * @return
     */
    @ExceptionHandler(NoLoginException.class)
    public Object noLoginExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
    {
        log.error("[GlobalExceptionHandler][noLoginExceptionHandler] exception",e);
        JsonResult jsonResult = new JsonResult();
        jsonResult.setCode(JsonResultCode.NO_LOGIN);
        jsonResult.setMessage("用户登录失效或者登录超时,请先登录");
        return jsonResult;
    }
    /**
     * 业务异常
     * @param request
     * @param response
     * @param e
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    public Object businessExceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
    {
        log.error("[GlobalExceptionHandler][businessExceptionHandler] exception",e);
        JsonResult jsonResult = new JsonResult();
        jsonResult.setCode(JsonResultCode.FAILURE);
        jsonResult.setMessage("业务异常,请联系管理员");
        return jsonResult;
    }
    /**
     * 全局异常处理
     * @param request
     * @param response
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Object exceptionHandler(HttpServletRequest request,HttpServletResponse response,Exception e)
    {
        log.error("[GlobalExceptionHandler][exceptionHandler] exception",e);
        JsonResult jsonResult = new JsonResult();
        jsonResult.setCode(JsonResultCode.FAILURE);
        jsonResult.setMessage("系统错误,请联系管理员");
        return jsonResult;
    }
}

Источник этой статьи: cnblogs.com/jurendage/p/11255197.html.

Для получения дополнительной информации, пожалуйста, обратите внимание на публичный номер: Java Advanced Journey!