1. Введение
Я полагаю, что каждый из нас сталкивался с такой проблемой при разработке SpringMVC: когда наш код работает нормально, возвращаемые данные находятся в ожидаемом нами формате, например json или xml, но как только возникает исключение (например: NPE или массив за пределами) и т. д.), возвращаемый контент действительно является информацией стека исключений сервера, так что возвращаемые данные не могут быть нормально проанализированы клиентом; очевидно, это не те результаты, которые нам нужны.
Мы знаем, что относительно распространенная система будет включать в себя уровень управления, сервисный (бизнес) уровень, уровень кэширования, уровень хранения, интерфейсные вызовы и т. д. Каждая ссылка неизбежно будет сталкиваться с различными непредсказуемыми исключениями, которые необходимо обрабатывать. Если каждый шаг выполняется отдельно try..catch, система будет очень запутанной, удобочитаемость будет плохой, а затраты на обслуживание будут высокими.Общим способом является реализация унифицированной обработки исключений, чтобы отделить различные исключения от каждого модуля;
2. Общая глобальная обработка исключений
В Spring существует три общих глобальных обработки исключений:
(1) Аннотировать обработчик исключений
(2) Наследовать интерфейс HandlerExceptionResolver
(3) Аннотировать ControllerAdvice
В следующем объяснении мы в основном берем коды ошибок HTTP: 400 (неверный запрос) и 500 (внутренняя ошибка сервера) в качестве примеров, сначала посмотрим на тестовый код и результат возврата без какой-либо обработки, как показано ниже:
Рисунок 1: Тестовый код
Рисунок 2: Возврат ошибки без исключений
2.1 Аннотирование обработчика исключений
Объектом аннотации ExceptionHandler является метод, проще всего его использовать, поместить в файл контроллера, подробное определение аннотации приводить не будем. Если в проекте имеется несколько файлов контроллеров, обработка исключений ExceptionHandler обычно может быть реализована в baseController, и каждый контроллер наследует базовый контроллер для достижения цели унифицированной обработки исключений. Поскольку это относительно распространено, простой код выглядит следующим образом:
Рисунок 3: Использование ExceptionHandler в контроллере
При возврате исключения добавляется имя класса, которому оно принадлежит, что удобно всем для запоминания и понимания. Запустите и посмотрите результат:
Рисунок 4: Результат после добавления ExceptionHandler
Преимущества: ExceptionHandler прост и понятен, и нет ограниченного формата метода для обработки исключений;
Недостатки: Так как ExceptionHandler действует только на методы, в случае нескольких контроллеров он только на один метод.Все контроллеры, которым нужна обработка исключений, наследуют этот класс.Нехорошо заставлять их искать отца для заведомо неактуальных вещей.
2.2 Аннотирование ControllerAdvice
Хотя здесь используется аннотация ControllerAdvice, на самом деле это ее комбинация и ExceptionHandler. Как вы можете видеть выше, при использовании одного @ExceptionHandler он должен быть в контроллере, но когда он используется в сочетании с ControllerAdvice, такого ограничения вообще нет. Другими словами, их комбинация обеспечивает глобальную обработку перехвата исключений.
Рисунок 5: Аннотирование кода обработки исключений ControllerAdvice
Перед запуском нужно закомментировать ExceptionHandler в предыдущем Controller.Результаты теста следующие:
Рисунок 6: Аннотирование результатов обработки исключений ControllerAdvice
Как видно из приведенных выше результатов, обработка исключений действительно была изменена на класс ExceptionHandlerAdvice. Этот метод объединяет всю обработку исключений в одном месте, удаляет отношение наследования в контроллере и обеспечивает эффект глобального захвата.Этот метод рекомендуется;
2.3 Реализация интерфейса HandlerExceptionResolver
Сам HandlerExceptionResolver является интерфейсом внутри SpringMVC, а внутри resolveException есть только один метод.Реализуя этот интерфейс, мы можем достичь цели глобальной обработки исключений.
Рис. 7. Реализация интерфейса HandlerExceptionResolver Также перед выполнением закомментируйте обработку исключений двух вышеуказанных методов, и вы получите следующие результаты:
Рисунок 8: Результат реализации интерфейса HandlerExceptionResolver
Видно, что обработка исключения 500 вступила в силу, но обработка исключения 400 не вступила в силу, и возвращаемый результат тот же, что и до того, как корень не имеет исключения. Что тут происходит? Разве это не означает, что можно выполнить глобальную обработку исключений? Нет способа узнать причину проблемы, мы можем только докопаться до сути и отправиться в родовую могилу Spring Давайте объединим отладку исходного кода Spring, чтобы найти причину.
3. Анализ исходного кода обработки исключений в Spring
Как мы все знаем, первым классом, который получает запрос в Spring, является DispatcherServlet, а основным методом в этом классе является doDispatch, Мы можем разбивать точки в этом классе и шаг за шагом выполнять обработку исключений.
3.1 Поток обработки класса реализации HandlerExceptionResolver
Ссылаясь на следующие последующие шаги, в точке останова processHandlerException отслеживаемые результаты следующие:
Рисунок 9: точка останова processHandlerException
Видно, что по стрелке [1] на рисунке проходится handlerExceptionResolvers для обработки исключения, а по стрелке [2] в handlerExceptionResolvers находятся 4 элемента, последний из которых — это класс обработки исключений, определенный методом 2.3
Текущий запрос запроса запроса, в соответствии с вышеуказанным явлением, можно сделать вывод, что обработка исключений должна обрабатываться в первых 3 обработках исключений, что пропускает наше пользовательское исключение; с таким предположением мы продолжаем следить за F8, это можно проследить, что исключение вызвано третьим, т.е.DefaultHandlerExceptionResolverобработанный.
DefaultHandlerExceptionResolver: SpringMVC по умолчанию оснащен DefaultHandlerExceptionResolver.Метод doResolveException этого класса в основном обрабатывает некоторые специальные исключения и преобразует такие исключения в соответствующие коды состояния ответа. Исключением, вызванным запросом запроса, является MissingServletRequestParameterException, которое является исключением, на которое нацелен DefaultHandlerExceptionResolver, поэтому оно будет перехвачено исключением в этом классе.
Теперь, когда правда раскрыта, мы видим, что наш пользовательский класс MyHandlerExceptionResolver действительно может обрабатывать исключения глобально, но для исключений запросов запросов DefaultHandlerExceptionResolver вставляется посередине, поэтому обработка класса MyHandlerExceptionResolver пропускается, поэтому возвращается появляется результат 400. Для запроса calc блокировки посередине нет, поэтому ожидаемый эффект достигается.
3.2 Последовательность обработки трех типов исключений
До сих пор мы представили три типа глобальной обработки исключений.Согласно приведенному выше анализу видно, что способ реализации интерфейса HandlerExceptionResolver должен обрабатываться в последнюю очередь, так что кто порядок @ExceptionHandler и @ControllerAdvice? Откройте все три типа обработки исключений (закомментированы ранее), запустите их, чтобы увидеть эффект:
Рисунок 10: Полный результат обработки исключений
Из явления видно, что отдельная обработка исключений @ExceptionHandle в контроллере занимает первое место, а @ControllerAdvice — второе. Строгая детская обувь может написать Controller02, скопировать запрос и вычислить и не требует обработки исключений, поэтому, когда запрашивается метод c02, имя класса захвата исключения — это класс @ControllerAdvice.
Вышеизложенное — это выводы, которые мы получили на основе явления.Давайте перейдем к исходному коду Spring, чтобы найти «доказательства». На рисунке 9 есть 4 типа обработчиков в handlerExceptionResolvers, а обработка @ExceptionHandler и @ControllerAdvice находится в первом ExceptionHandlerExceptionResolver (это можно узнать, пройдя по точке останова). Продолжайте следовать до тех пор, пока не войдете в метод doResolveHandlerMethodException класса ExceptionHandlerExceptionResolver, где HandlerMethod — это метод, с помощью которого Spring сопоставляет HTTP-запросы с указанным контроллером, а Exception — это исключение, которое необходимо перехватить; сделано с использованием этих двух параметров, что случилось.
Рисунок 11. Точка останова doResolveHandlerMethodException
Продолжайте следить за методом getExceptionHandlerMethod и обнаружите, что есть две переменные, которые могут быть ключом к проблеме: exceptionHandlerCache и exceptionHandlerAdviceCache. Во-первых, очень подозрительны имена переменных у двоих; во-вторых, в коде первый явно использует класс как ключ для получения резольвера (resolver), что совпадает с правилами обработки @ExceptionHandler в Контроллере; наконец , два Порядок обработки каждого кэша также соответствует предыдущим выводам. Как я уже догадался, Spring отдает приоритет поиску соответствующего ExceptionHandler в соответствии с именем класса Controller.Если он не найден, он будет выполнять обработку исключений @ControllerAdvice.
Рисунок 12: Два кэша обработки исключений
Если вам интересно, вы можете продолжить копаться в исходном коде Spring.Вот краткий обзор ExceptionHandlerExceptionResolver:
ИсключениеHandlerCache содержит обработку исключения ExceptionHandler в контроллере.Во время обработки контроллер получается через HandlerMethod, а затем находится метод обработки исключения.Следует отметить, что значение ставится в процессе обработки исключения;
ИсключениеHandlerAdviceCache инициализируется при запуске проекта Общая идея состоит в том, чтобы найти bean-компонент с аннотацией @ControllerAdvice, чтобы кэшировать ExceptionHandler в bean-компоненте, и его необходимо выровнять и пройти, чтобы найти обработку во время обработки исключения, чтобы достичь цели глобальной обработки.
3.3 Переворачивание соленой рыбы
Так много было введено, просто нарисуйте картинку, чтобы подвести итог. Синяя часть — это 3 типа обработчиков исключений, добавленных Spring по умолчанию, а желтая часть — добавленный нами обработчик исключений, а также позиция и порядок его вызова. Чтобы увидеть, что еще остается неясным, оглянитесь назад (ResponseStatusExceptionResolver предназначен для аннотации @ResponseStatus и не будет здесь подробно описываться).
Рисунок 13: Сводка исключений
Если необходимо обработать MyHandlerExceptionResolver заранее, еще до ExceptionHandlerExceptionResolver, можно ли это сделать? Ответ - да. В Spring, если вы хотите заранее обработать исключение MyHandlerExceptionResolver, вам нужно реализовать интерфейс Ordered и реализовать метод getOrder внутри. Возвратите -1 здесь и поместите его сверху. На этот раз соленая рыба можно наконец перевернуться..
Рисунок 14: Реализация упорядоченного интерфейса
Запустите его, чтобы увидеть, соответствует ли результат ожидаемому, и напомните нам, что все три обработчика исключений допустимы, как показано на следующем рисунке:
Рисунок 15: Результат реализации интерфейса Ordered
4. Резюме
В этой статье в основном представлены три типа общей глобальной обработки исключений в SpringMVC, обнаружены проблемы при отладке, а затем приводится исходный код Spring для изучения причин и, наконец, решения проблемы. Я надеюсь, что каждый может что-то получить. Конечно, класс обработки исключений Spring не только представлен, пожалуйста, изучите его самостоятельно, если вам интересно!
Ссылка на ссылку:
[1] http://www.cnblogs.com/fangjian0423/p/springMVC-request-mapping.html
[2]blog.CSDN.net/валовая прибыль999888/ах…
Технологический институт CreditEase