Обработка исключений в контроллерах Grails
Я знаю, как выполнять общую обработку исключений в Grails с использованием UrlMappings и ErrorController для общей обработки исключений, так что если исключение ускользает от контроллера, пользователь будет отправлен на общую страницу ошибок, и исключение будет зарегистрировано. Я также знаю, как использовать блоки try/catch для обработки определенных исключений и пытаться восстановить их.
Но в большинстве контроллеров я просто хочу дать пользователю несколько более конкретное сообщение об ошибке, если возникает исключение. Поэтому в действии create я хочу сообщить пользователю, что элемент не был создан. Или в действии импорта я хочу сообщить пользователю, что импорт завершился неудачно. Прямо сейчас контроллеры выглядят так:
class ThingController {
def create = {
try {
// The real controller code, which quickly hands it off to a service
} catch (Exception e) {
handleException(e, "There was an error while attempting to create the Thing")
}
}
def delete = {
try {
// The real controller code, which quickly hands it off to a service
} catch (Exception e) {
handleException(e, "There was an error while attempting to delete the Thing")
}
}
private void handleException(Exception e, String message) {
flash.message = message
String eMessage = ExceptionUtils.getRootCauseMessage(e)
log.error message(code: "sic.log.error.ExceptionOccurred", args: ["${eMessage}", "${e}"])
redirect(action:index)
}
}
Обратите внимание, что блоки catch не делают ничего другого в зависимости от типа или содержимого исключения; они просто дают немного более описательное сообщение об ошибке на основе контроллера. "Настоящий" код контроллера обычно составляет 6-10 строк, поэтому наличие дополнительных 4 строк кода просто для изменения сообщения об ошибке кажется чрезмерным. Кроме того, правило CodeNarc "CatchException" жалуется, что подтверждает мое мнение о том, что должен быть лучший способ сделать это. Я предполагаю, что другие приложения Grails имеют аналогичные требования. Что такое idiomatic способ указать разные сообщения об ошибках на основе того, какое действие вышло из исключения?
Мне интересны ответы, которые исходят из опыта с определенным способом решения этой проблемы или даже лучше, ссылки на кодовые базы, где я вижу решение на практике.
Ответы
Ответ 1
У Grails есть механизм для общих исключений контроллера обработки.
Вы можете сделать это внутри специального контроллера ошибок. Регулярные контроллеры не должны использовать try/catch.
Контроллер:
class ThingController {
def create() {
def id = params.id as Long
if (id == null) {
throw new MissingPropertyException("thingId")
}
// The real controller code, which mostly parses things out and hands it
// off to a service.
// Service methods can throws exception
}
}
Добавить обработку 500 ошибок в UrlMappings:
class UrlMappings {
static mappings = {
// Exception handling in ErrorController
"500"(controller: "error")
}
}
ErrorController:
class ErrorController {
def index() {
def exception = request.exception.cause
def message = ExceptionMapper.mapException(exception)
def status = message.status
response.status = status
render(view: "/error", model: [status: status, exception: exception])
}
}
Вы можете обрабатывать исключения REST и исключение REST, используя этот подход.
Также есть плагин Declarative Exception Handling, но у меня нет
Обновление
Вы можете получить конкретные сообщения об ошибках в контроллере ошибок.
Когда в контроллере выбрано новое RuntimeException ( "Произошла ошибка при попытке удалить Thing" ), тогда в диспетчере ошибок request.exception.cause.message появится сообщение: "При попытке удалить Thing произошла ошибка".
Ответ 2
См. также Как узнать, откуда была выбрана ошибка 500 (Grails)
Я создаю собственные страницы ошибок на основе аннотаций на контроллерах, предоставляя общие процедуры обработки исключений для нескольких контроллеров.
class ErrorsController {
def index() {
def initialController = request.exception?.className
if (initialController) {
def controller = grailsApplication.getArtefact("Controller", initialController).getReferenceInstance()
// do some rendering based on the annotations
render "Controller: ${initialController}, annotations ${controller.getClass().getDeclaredAnnotations()}"
return
}
render 'no initial controller'
}