If you are new to AST Trasformations in Groovy, please start with Groovy documentation. This post shows how to intercept a function call and provide AOP style around advice to it. There are other ways to achieve this behavior:
  1. Using GroovyInterceptable
  2. Spring AOP
There are pros and cons to both approaches and your favorite search engine can point you to resources describing them in detail. I like using AST Transformations for this purpose because it's easier, has more flexibility and I have more control over program order.

The Problem Statement

Given a function like SomethingService.addSomething(), invoke AopService.prologue() before addSomething() is executed and invoke AopService.epilogue() after. Notice that prologue() and epilogue() have same number and type of parameters but that's not a requirement. That's what I meant by flexibility. You can implement this however you like but for the purpose of this article, I'll use the same function parameter types.
public class SomethingService {
    /**
     * Inserts a new value into table.
     */
    def addSomething(String id, String value) {
        def sql = new Sql(dataSource)
        sql.executeUpdate("INSERT INTO table VALUES(?, ?)", [
            id,
            value
        ])
    }
}
public class AopService {
    /**
     * To be called before SomethingService#addSomething() is invoked.
     */
    def prologue(String id, String value) {
    }

    /**
     * To be called after SomethingService#addSomething() is invoked.
     */
    def epilogue(String id, String value) {
    }
}

Before advice

Let's start with the simplest - before advice. The plan is to get hold of addSomething() - the target method's body, insert a function call as the first statement in the body. That's it. We need to annotate addSomething() with @Before, a method annotation. That tells Groovy compiler to give us access to method's AST. @Before has two elements - service and method - which represent the service name and it's method to invoke before executing addSomething()
public class SomethingService {
    /**
     * Inserts a new value into table.
     */
    @Before(service = "aopService", method = "prologue")
    @After(service = "aopService", method = "epilogue")
    def addSomething(String id, String value) {
        def sql = new Sql(dataSource)
        sql.executeUpdate("INSERT INTO table VALUES(?, ?)", [
            id,
            value
        ])
    }
}
Also, BeforeASTTransformation class is linked to @Before annotation. The transformation class is the callback that lets us modify addSomething().
package com.ksi.transformation;

/**
 * {@link Before} declares which service method to call before invoking the
 * target method.
 */
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
@GroovyASTTransformationClass("com.ksi.transformation.BeforeASTTransformation")
public @interface Before {
    String service();

    String method();
}
Remember that transformation class is the callback Groovy compiler executes when annotated source unit is found. So we now have access to addSomething() as an instance of MethodNode. methodNode.getCode() gives us all statements in the method body. We'll just need to push a call to AopService.prologue() as the first statement. In addition to MethodNode, Groovy compiler gives us the annotation used with the target method as an instance of AnnotationNode. We can read the elements of @Before and now we know service and method to invoke. To invoke the service, all we need to do is express ApplicationHolder.getApplication().getMainContext().getBean(serviceName).methodName(parameters); in AST. Note that serviceName and methodName are elements of @Before annotation. So the AST we need is:
// Get ApplicationHolder
ClassNode applicationHolder = new ClassNode(org.codehaus.groovy.grails.commons.ApplicationHolder.class);

// ApplicationHolder.getApplication()
Expression application = new StaticMethodCallExpression(applicationHolder, "getApplication", EMPTY_ARGUMENTS);

// ApplicationHolder.getApplication().getMainContext()
Expression appContext = new MethodCallExpression(application, "getMainContext", EMPTY_ARGUMENTS);

// ApplicationHolder.getApplication().getMainContext().getBean(serviceName)
Expression service = new MethodCallExpression(appContext, "getBean", new ConstantExpression(serviceName));

// ApplicationHolder.getApplication().getMainContext().getBean(serviceName).method()
Expression call = new MethodCallExpression(service, methodName, new ArgumentListExpression(parameters));
After transformation here's how addSomething would look like. The hilighted line is inserted by transformation class:
public class SomethingService {
    /**
     * Inserts a new value into table.
     */
    def addSomething(String id, String value) {
        ApplicationHolder.getApplication().getMainContext().getBean("aopService").prologue(id, value);
        def sql = new Sql(dataSource)
        sql.executeUpdate("INSERT INTO table VALUES(?, ?)", [
            id,
            value
        ])
    }
}
Here's the complete AST transformation class that does all I've described above. Notice how addBeforeCall() inserts AopService.prologue() as the first statement in the method body. addSomething's parameters are passed to AopService.prologue() as well. This is the reason we used same function type signatures.
package com.ksi.transformation;

/**
 * Any method annotated with {@link Before} is transformed using this class by
 * being made to invoke method in {@link Before} annotation.
 */
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class BeforeASTTransformation implements ASTTransformation {

    Logger logger = Logger.getLogger(BeforeASTTransformation.class);

    /**
     * Implements transformation.
     *
     * @see org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[],
     *      org.codehaus.groovy.control.SourceUnit)
     */
    @Override
    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
        if (nodes == null || nodes.length == 0 || nodes[0] == null || nodes[1] == null) {
            return;
        }

        if (!(nodes[0] instanceof AnnotationNode) && !(nodes[1] instanceof MethodNode)) {
            return;
        }

        MethodNode methodNode = (MethodNode) nodes[1];

        ClassNode beforeNode = new ClassNode(com.ksi.transformation.Before.class);
        for (AnnotationNode annotationNode : methodNode.getAnnotations(beforeNode)) {
            addBeforeCall(annotationNode, methodNode);
            break;
        }
    }

    /**
     * Inserts a method call as the first line in intercepted method's body.
     *
     * @param annotationNode
     *            annotation on intercepted method
     * @param methodNode
     *            intercepted method
     */
    private void addBeforeCall(AnnotationNode annotationNode, MethodNode methodNode) {
        String serviceName = annotationNode.getMember("service").getText();
        String methodName = annotationNode.getMember("method").getText();
        Parameter[] parameters = methodNode.getParameters();

        BlockStatement methodBody = (BlockStatement) methodNode.getCode();
        List<Statement> statements = methodBody.getStatements();

        statements.add(0, invokeServiceAst(serviceName, methodName, parameters));
    }

    /**
     * Creates AST for the following call:
     *
     * 
     * ApplicationHolder.getApplication().getMainContext().getBean(serviceName).methodName(parameters);
     * 
     *
     * @param serviceName
     *            service bean name
     * @param methodName
     *            method in the service calss to invoke
     * @param parameters
     *            arguments from intercepted method.
     * @return
     */
    private Statement invokeServiceAst(String serviceName, String methodName, Parameter[] parameters) {
        ClassNode applicationHolder = new ClassNode(org.codehaus.groovy.grails.commons.ApplicationHolder.class);
        Expression application = new StaticMethodCallExpression(applicationHolder, "getApplication", EMPTY_ARGUMENTS);
        Expression appContext = new MethodCallExpression(application, "getMainContext", EMPTY_ARGUMENTS);
        Expression service = new MethodCallExpression(appContext, "getBean", new ConstantExpression(serviceName));
        Expression call = new MethodCallExpression(service, methodName, new ArgumentListExpression(parameters));
        return new ExpressionStatement(call);
    }
}

After advice

Invoking a method call after target method executes is similar but as methods can have multiple return points, we can't just insert a call as the last line in the method body. Instead we'll accomplish it this way:
  1. Create a new private method with same function type signature but with a different name. So in our case, we'll add a new method called $addSomething$ (notice the $ signs - you can use any another symbol Java allows) and remove addSomething method body and move it in to the newly added $addSomething$ method.
        def addSomething(String id, String value) {
        }
    
        /**
         * Newly inserted method.
         */
        private def $addSomething$(String id, String value){
            def sql = new Sql(dataSource)
            sql.executeUpdate("INSERT INTO table VALUES(?, ?)", [
                id,
                value
            ])
        }
    You can add a new method by simply creating a new instance of MethodNode like so:
    // parent is SomethingService class
    ClassNode parent = forwardingMethod.getDeclaringClass();
    
    // methodName is $addSomething$
    String methodName = "$" + forwardingMethod.getName() + "$";
    
    // The rest of the function type signature is same as that of addSomething()
    ClassNode returnType = forwardingMethod.getReturnType();
    Parameter[] parameters = forwardingMethod.getParameters();
    ClassNode[] exceptions = forwardingMethod.getExceptions();
    
    // Move method body
    Statement body = forwardingMethod.getCode();
    MethodNode forwardedTo = new MethodNode(methodName, Modifier.PRIVATE, returnType, parameters, exceptions, body);
    
    // Add new method to SomethingService
    parent.addMethod(forwardedTo);
  2. Have target method forward calls to newly added method. Also, capture the return value.
        def addSomething(String id, String value) {
            def __addSomething__ = $addSomething$(id, value)
        }
    
        /**
         * Newly inserted method.
         */
        private def $addSomething$(String id, String value){
            def sql = new Sql(dataSource)
            sql.executeUpdate("INSERT INTO table VALUES(?, ?)", [
                id,
                value
            ])
        }
    AST for this step:
    BlockStatement methodBody = new BlockStatement();
    
    // Replace method body with call to forwarded function.
    Expression left = new VariableExpression("__" + methodNode.getName() + "__");
    Token assign = Token.newPlaceholder(Types.ASSIGN);
    Expression right = new MethodCallExpression(new VariableExpression("this"), forwardTo.getName(),
            new ArgumentListExpression(methodNode.getParameters()));
    Expression assignment = new DeclarationExpression(left, assign, right);
    methodBody.addStatement(new ExpressionStatement(assignment));
  3. In the target method, insert method call to be invoked last. Adjust the return statement.
        def addSomething(String id, String value) {
            def __addSomething__ = $addSomething$(id, value)
            ApplicationHolder.getApplication().getMainContext().getBean("aopService").epilogue(id, value);
            __addSomething__
        }
    
        /**
         * Newly inserted method.
         */
        private def $addSomething$(String id, String value){
            def sql = new Sql(dataSource)
            sql.executeUpdate("INSERT INTO table VALUES(?, ?)", [
                id,
                value
            ])
        }
@After annnotation and it's transformation class are shown below.
package com.ksi.transformation;

/**
 * {@link After} declares which service method to call after invoking the target
 * method.
 */
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
@GroovyASTTransformationClass("com.ksi.AfterASTTransformation")
public @interface After {
    String service();

    String method();
}
package com.ksi.transformation;

/**
 * Any method annotated with {@link After} is transformed using this class by
 * being made to invoke method in {@link After} annotation.
 */
@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
public class AfterASTTransformation implements ASTTransformation {

    Logger logger = Logger.getLogger(AfterASTTransformation.class);

    /**
     * Implements transformation.
     *
     * @see org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[],
     *      org.codehaus.groovy.control.SourceUnit)
     */
    @Override
    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
        if (nodes == null || nodes.length == 0 || nodes[0] == null || nodes[1] == null) {
            return;
        }

        if (!(nodes[0] instanceof AnnotationNode) && !(nodes[1] instanceof MethodNode)) {
            return;
        }

        MethodNode methodNode = (MethodNode) nodes[1];

        ClassNode beforeNode = new ClassNode(com.ksi.transformation.After.class);
        for (AnnotationNode annotationNode : methodNode.getAnnotations(beforeNode)) {
            addAfterCall(annotationNode, methodNode);
            break;
        }
    }

    /**
     * Adds a new method and forwards calls to this new method. For example, if
     * the intercepted method is:
     *
     * @After(service = "interceptingService", method = "afterIntercepting")
     * def addNumbers(int x, int y) {
     *     x + y
     * }
     *
     * Then
     * 1. A new method $addNumbners is added.
     * 2. The intercepted method is modified as follows:
     *
     * def addNumbers(int x, int y) {
     *     def __addNumbers__ = $addNumbers$(x, y)
     *     interceptingService.afterIntercepting(x, y)
     *     __addNumbers__
     * }
     *
     * private def $addNumbers$(int x, int y) {
     * }
     *
     * @param annotationNode
     *            annotation on intercepted method
     * @param methodNode
     *            intercepted method
     */
    private void addAfterCall(AnnotationNode annotationNode, MethodNode methodNode) {
        // Add new method to forward call
        MethodNode forwardTo = addForwardToMethodAst(methodNode);

        BlockStatement methodBody = new BlockStatement();

        // Replace method body with call to forwarded function.
        Expression left = new VariableExpression("__" + methodNode.getName() + "__");
        Token assign = Token.newPlaceholder(Types.ASSIGN);
        Expression right = new MethodCallExpression(new VariableExpression("this"), forwardTo.getName(),
                new ArgumentListExpression(methodNode.getParameters()));
        Expression assignment = new DeclarationExpression(left, assign, right);
        methodBody.addStatement(new ExpressionStatement(assignment));

        // Add call to 'after' method given in After annotation
        String serviceName = annotationNode.getMember("service").getText();
        String methodName = annotationNode.getMember("method").getText();
        Parameter[] parameters = methodNode.getParameters();
        methodBody.addStatement(invokeServiceAst(serviceName, methodName, parameters));

        // Add return statement
        methodBody.addStatement(new ExpressionStatement(left));

        methodNode.setCode(methodBody);
    }

    /**
     * Adds a new private method with the same body as forwarding method.
     *
     * @param forwardingMethod
     *            the intercepted method.
     * @return a new method to which calls are forwarded.
     */
    private MethodNode addForwardToMethodAst(MethodNode forwardingMethod) {
        ClassNode parent = forwardingMethod.getDeclaringClass();
        String methodName = "$" + forwardingMethod.getName() + "$";
        ClassNode returnType = forwardingMethod.getReturnType();
        Parameter[] parameters = forwardingMethod.getParameters();
        ClassNode[] exceptions = forwardingMethod.getExceptions();
        Statement body = forwardingMethod.getCode();
        MethodNode forwardedTo = new MethodNode(methodName, Modifier.PRIVATE, returnType, parameters, exceptions, body);
        parent.addMethod(forwardedTo);
        return forwardedTo;
    }

    /**
     * Creates AST for the following call:
     *
     * 
     * ApplicationHolder.getApplication().getMainContext().getBean(serviceName).methodName(parameters);
     * 
     *
     * @param serviceName
     *            service bean name
     * @param methodName
     *            method in the service calss to invoke
     * @param parameters
     *            arguments from intercepted method.
     * @return
     */
    private Statement invokeServiceAst(String serviceName, String methodName, Parameter[] parameters) {
        ClassNode applicationHolder = new ClassNode(org.codehaus.groovy.grails.commons.ApplicationHolder.class);
        Expression application = new StaticMethodCallExpression(applicationHolder, "getApplication", EMPTY_ARGUMENTS);
        Expression appContext = new MethodCallExpression(application, "getMainContext", EMPTY_ARGUMENTS);
        Expression service = new MethodCallExpression(appContext, "getBean", new ConstantExpression(serviceName));
        Expression call = new MethodCallExpression(service, methodName, new ArgumentListExpression(parameters));
        return new ExpressionStatement(call);
    }
}

Additional Notes

Notice that in case of an exception, the method call expression AopService.epilogue() may not get invoked. If you want an always executing after advice, use a try/finally block like so:
    def addSomething(String id, String value) {
        try {
            $addSomething$(id, value)
        } finally {
            ApplicationHolder.getApplication().getMainContext().getBean("aopService").epilogue(id, value);
        }
    }

    /**
     * Newly inserted method.
     */
    $addSomething$(String id, String value){
        def sql = new Sql(dataSource)
        sql.executeUpdate("INSERT INTO table VALUES(?, ?)", [
            id,
            value
        ])
    }