Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Groovy method interception

In my Grails app I've installed the Quartz plugin. I want to intercept calls to every Quartz job class' execute method in order to do something before the execute method is invoked (similar to AOP before advice).

Currently, I'm trying to do this interception from the doWithDynamicMethods closure of another plugin as shown below:

def doWithDynamicMethods = { ctx ->
    // get all the job classes
    application.getArtefacts("Job").each { klass ->

        MetaClass jobMetaClass = klass.clazz.metaClass

        // intercept the methods of the job classes
        jobMetaClass.invokeMethod = { String name, Object args ->

            // do something before invoking the called method
            if (name == "execute") {
                println "this should happen before execute()"
            }

            // now call the method that was originally invoked
            def validMethod = jobMetaClass.getMetaMethod(name, args)

            if (validMethod != null) {
                validMethod.invoke(delegate, args)
            } else {
                jobMetaClass.invokeMissingMethod(delegate, name, args)
            }
        }
    }
}

So, given a job such as

class TestJob {
    static triggers = {
      simple repeatInterval: 5000l // execute job once in 5 seconds
    }

    def execute() {
        "execute called"
    }
}

It should print:

this should happen before execute()
execute called

But my attempt at method interception seems to have no effect and instead it just prints:

execute called

Perhaps the cause of the problem is this Groovy bug? Even though the Job classes don't explicitly implement the org.quartz.Job interface, I suspect that implicitly (due to some Groovy voodoo), they are instances of this interface.

If indeed this bug is the cause of my problem, is there another way that I can do "before method interception"?

like image 259
Abasourdi Avatar asked May 28 '13 08:05

Abasourdi


2 Answers

Because all the job classes are Spring beans you can solve this problem using Spring AOP. Define an aspect such as the following (adjust the pointcut definition so that it matches only your job classes, I've assumed they are all in a package named org.example.job and have a class name that ends with Job).

@Aspect
class JobExecutionAspect {

  @Pointcut("execution(public * org.example.job.*Job.execute(..))")
  public void executeMethods() {}

  @Around("executeMethods()")
  def interceptJobExecuteMethod(ProceedingJoinPoint jp) {
    // do your stuff that should happen before execute() here, if you need access
    // to the job object call jp.getTarget()

    // now call the job's execute() method
    jp.proceed() 
  }
}

You'll need to register this aspect as a Spring bean (it doesn't matter what name you give the bean).

like image 157
Dónal Avatar answered Nov 20 '22 18:11

Dónal


You can have your customized JobListener registered in the application to handle logics before execute() is triggered. You can use something like:-

public class MyJobListener implements JobListener {
    public void jobToBeExecuted(JobExecutionContext context) {
        println "Before calling Execute"
    }

    public void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException) {}

    public void jobExecutionVetoed(JobExecutionContext context) {}
}

Register the customized Job Listener to Quartz Scheduler in Bootstrap:-

Scheduler scheduler = ctx.getBean("quartzScheduler") //ctx being application context
scheduler.getListenerManager().addJobListener(myJobListener, allJobs())

resources.groovy:-

beans = {
    myJobListener(MyJobListener)
}
  • One benefit I see here using this approach is that we don't need the second plugin used for method interception any more.
  • Second, we can register the listener to listen all jobs, specific jobs, and jobs in a group. Refer Customize Quartz JobListener and API for JobListener, TriggerListener, ScheduleListener for more insight.
  • Obviously, AOP is another approach if we do want want to use Quartz API.
like image 4
dmahapatro Avatar answered Nov 20 '22 18:11

dmahapatro