Как обернуть все методы обслуживания Grails замыканием Groovy?

Grails 2.4.x здесь.

У меня есть требование, чтобы все методы всех моих сервисов Grails, генерируемые grails create-service <xyz>Быть "обернутым" / перехваченным следующей логикой:

try {
    executeTheMethod()
} catch(MyAppException maExc) {
    log.error(ExceptionUtils.getStackTrace(maExc))
    myAppExceptionHandler.handleOrRethrow(maExc)
}

Куда:

  • log.error(...) это регистратор, предоставляемый SLF4J, который вы получаете, когда комментируете свой класс @Slf4j аннотаций; а также
  • ExceptionUtils это один из org.apache.commons:commons-lang3:3.4; а также
  • myAppExceptionHandler имеет тип com.example.myapp.MyAppExceptionHandler; а также
  • Такое поведение существует (или имеет возможность существовать в случае, если его необходимо как-то явно вызвать) для каждого метода, определенного в сервисе Grails

Очевидно, этот код-обертка должен включать import заявления для этих классов, а также.

Так, например, если у меня есть WidgetService это выглядит так:

class WidgetService {
    WidgetDataService widgetDataService = new WidgetDataService()

    Widget getWidgetById(Long widgetId) {
        List<Widget> widgets = widgetDataService.getAllWidgets()
        widgets.each {
            if(it.id.equals(widgetId)) {
                return it
            }
        }

        return null
    }
}

Затем, после того, как происходит волшебство Groovy/Grails/closure, мне нужно, чтобы код вел себя так, как будто я написал его так:

import groovy.util.logging.Slf4j
import org.apache.commons.lang3.exception.ExceptionUtils
import com.example.myapp.MyAppExceptionHandler

@Slf4j
class WidgetService {
    WidgetDataService widgetDataService = new WidgetDataService()

    MyAppExceptionHandler myAppExceptionHandler = new MyAppExceptionHandler()

    Widget getWidgetById(Long widgetId) {
        try {
            List<Widget> widgets = widgetDataService.getAllWidgets()
            widgets.each {
                if(it.id.equals(widgetId)) {
                    return it
                }
            }

            return null
        } catch(MyAppException maExc) {
            log.error(ExceptionUtils.getStackTrace(maExc))
            myAppExceptionHandler.handleOrRethrow(maExc)
        }
    }
}

Любые идеи относительно того, как я мог бы достичь этого? Я обеспокоен тем, что чистое закрытие Groovy может как-то мешать тому, что Grails делает со своими службами во время выполнения (поскольку они все классы, которые явно не расширяют родительский класс).

2 ответа

Решение

Вот что я пытался указать в своем комментарии:

package com.example

import groovy.util.logging.Log4j

@Log4j
trait SomeTrait {

    def withErrorHandler(Closure clos) {
        try {
            clos()
        } catch(Exception e) {
            log.error e.message
            throw new ApplicationSpecificException(
                "Application Specific Message: ${e.message}"
            )
        }
    }
}

Класс обслуживания:

package com.example

class SampleService implements SomeTrait {

    def throwingException() {
        withErrorHandler {
            throw new Exception("I am an exception")
        }
    }

    def notThrowingException() {
        withErrorHandler {
            println "foo bar"
        }
    }
}

Тестовое задание:

package com.example

import grails.test.mixin.TestFor
import spock.lang.Specification

@TestFor(SampleService)
class SampleServiceSpec extends Specification {

    void "test something"() {
        when:
        service.throwingException()

        then:
        ApplicationSpecificException e = thrown(ApplicationSpecificException)
        e.message == "Application Specific Message: I am an exception"
    }

    void "test something again"() {
        when:
        service.notThrowingException()

        then:
        notThrown(Exception)
    }
}

Вот пример приложения.

Grails 3.0.9 но это не должно иметь значения. это применимо для Grails 2.4.*

Вы можете перехватывать вызовы методов класса Service, используя MetaInjection или Spring AOP. Таким образом, вам не нужно писать замыкание в каждом классе Service. Вы можете заглянуть в этот блог, который объясняет оба подхода с примерами.

Другие вопросы по тегам