Aspect-oriented programming using Spring AOP over Grails framework

Preface

Aspect-oriented programming (AOP) is a new concept and paradigm. It allows basically to add functionality to modules and classes without modifying the code, it is possible by configuring Pointcut, which specifies which pieces of code will be executed after dispatching of a specific event over a method or class.

Goals

We aim and assume that after read the article developers will be able to use aspect-oriented programming in their Grails projects.

Requirements

Grails 3. *

Dependencies:

Spring Aspects 5. *

AspectJ Weaver 1. *

1- Aspect-oriented programming approach

Very often our software solutions involve complex logic and in this case keeping clean and understandable code is quite important. One approach which help in that sense is AOP, which enables an easy way of modularization. AOP allows basically to add functionality to modules and classes without modify the code.

2- Bean setting using Spring

The first step is to configure Spring AOP, in the Spring resource file (conf/spring/resources.groovy). We need also specify which bean will work as aspect, in our case will be the Trace bean.

It can be configured in several ways, using for example annotation @Aspect in our Bean declaration. In this case we need to inject log dependencies which are provided for Grails, we do not need create any instance for ourselves.

// Aspect-Oriented

xmlns aop:"http://www.springframework.org/schema/aop"

trace(Trace){

    log = log

}

aop{

    config("proxy-target-class": true) {

        aspect(id: "traceService", ref: "trace")

    }

}

3- Implementing logging functionality

The main concepts that we need know are: Pointcut, Advices and JoinPoint. A Pointcut is essentially a way to match a method call with a functionality which we add new behavior, it needs to specify the expression and the signature. To execute the new behavior, we need specify some advice and associate it with a Pointcut.

Advice are declared using annotations like @Before and @After which specify when the code will be executed. Advice will take as parameter a JoinPoint, which contains information about the current call, like values of parameters, called method, etc.

The sample application is related with document management. The main functionalities are upload, convert to HTML and store documents from different formats. The code that we will show pretends to keep track actions over document.

Below you can find the Aspect bean called Trace:

@Aspect

class Trace {

    def log


    @Pointcut("execution(* my.company.myApp.document.DocumentService.add(..))")

    void createDocument(){}



    @Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")

    void updateDocument(Document document, User user){}



    @Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")

    void storeDocument(){}



    @Before("createDocument()")

    void create(JoinPoint jp){

        Document document = jp.args[0] as Document

        User user = jp.args[1] as User

        log.info "The document: ${document?.name} has been created by user: ${user?.username}"

    }



    @Before("updateDocument(document, user)")

    void update(Document document, User user){

        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"

    }

    

    @Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }
}

We have declared three Pointcut sentences, I will explain below some of them.

Updating documents:

@Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")
  
  void updateDocument(Document document, User user){}

I this case we specify which method we want to track. We can specify arguments in the Pointcut declaration, that can be convenient most of time.

Our application contains a method with the next:

void update(Document document, User user){

    Version version = new Version(state: State.UPDATED, user: user, date: new Date())

    document.addToVersions(version)

    document.save()   

}

Then after declaring our Pointcut we need to implement the method which will execute the required behavior. We want to add new behavior exactly before the method is executed and then track which user has updated the document.

@Before("updateDocument(document, user)")

    void update(Document document, User user){
      
        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"
    
    }

Storing documents:

In this new case we will use the same endpoint to catch a different method call:

@Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")
    
    void storeDocument(){}

In our system we have a Repository class which is responsible for working with our files repository, basically we have two kind of repository, HTML and Source. We want to track when a document is stored in it.

def addHtmlDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.html

    repository = new HtmlRepository(

            path: repositoryPath,

            converter: new DocumentFormat(format: Format.HTML),    

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}


def addSourceDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.source   

    repository = new SourceRepository(

            path: repositoryPath,

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}

Then our behavior:

@Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }

The new behavior will print a log with the name of the called method, arguments and instance description.

4- Conclusion

Aspect-Oriented programming is a new concept, by using it correctly we ensure that our code is kept clean and in this sense we can add functionality easier without modify existing classes and methods. Is necessary be careful because code will be related logically and we need to keep updated documentation about that, otherwise we can have unexpected behavior in our application.

5- References

https://docs.spring.io

Origin: www.linkedin.com

All articles