9

I am using Spring 3.1.2.RELEASE with cglib load-time weaving and I am trying to get advice to work with a method that has custom annotations and annotated parameters.

Advice:

@Aspect
public class MyAdvice
{
   @Around("execution(@com.mycompany.locking.Lock * *(@com.mycompany.locking.LockVal(*), ..)) " +
   "&& args(batch) && @args(propertyToLock)"
   public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch, LockVal propertyToLock) throws Throwable { 
        //Do stuff.... 
        pjp.proceed();
   }
}

Here is the class that I am testing:

public interface UpdateManager
{
   public void processUpdate(MyBatchObject batch);
}


public class UpdateManagerImpl implements UpdateManager
{
   @Lock
   public void processUpdate(@LockVal("lockValue") MyBatchObject batch)
   {
      //Do stuff...
   }
}

The problem is that I can't get the advice to execute. If I remove the @args and args conditions in the pointcut, the advice fires, but then I have to dig through the ProceedingJoinPoint to get the parameter that I need.

Why isn't the advice firing? Did I do something wrong?

Edit: The following pointcut DOES WORK as a standalone program with Spring:

@Aspect
public class MyAdvice
{
   @Around("execution(@com.mycompany.locking.Lock * *(@com.mycompany.locking.LockVal(*), ..)) " +
   "&& args(batch)"
   public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch) throws Throwable { 
        //Do stuff.... 
        pjp.proceed();
   }
}

However, it does NOT work under JBoss 6 using load-time weaving. I suppose my question should be, then, why does it work as a standalone program but not under JBoss 6?

kriegaex
  • 63,017
  • 15
  • 111
  • 202
Brad
  • 2,261
  • 3
  • 22
  • 32
  • I know this one is old, but still listed as unanswered. Would you please accept and upvote my answer if it seems appropriate? Thanks. – kriegaex Jun 09 '14 at 12:34
  • I've moved on from that project now, but I can say that code never worked, no matter how I ran it. As I recall, it had something to do with the flat classloader + cglib in JBoss 6, which differed from previous versions. – Brad Jul 20 '14 at 04:38

2 Answers2

3

Update: I forgot to mention that @args() is not meant to match a parameter's annotation, but a parameter type's annotation, which is not what you want and which thus I do not use here.


You cannot bind a parameter's annotation via args(), only the parameter itself. This means that you can only access the parameter's annotation via reflection. You need to determine the method signature, create a Method object from it and then iterate over the method parameters' annotations. Here is a full code sample:

package com.mycompany.locking;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {}
package com.mycompany.locking;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface LockVal {
    String value() default "";
}
package com.mycompany;

public class MyBatchObject {}
package com.mycompany;

public interface UpdateManager {
    public void processUpdate(MyBatchObject batch);
}
package com.mycompany;

import com.mycompany.locking.Lock;
import com.mycompany.locking.LockVal;

public class UpdateManagerImpl implements UpdateManager {
    @Lock
    @Override
    public void processUpdate(@LockVal("lockValue") MyBatchObject batch) {
        System.out.println("Processing update");
    }

    public static void main(String[] args) {
        UpdateManager updateManager =  new UpdateManagerImpl();
        updateManager.processUpdate(new MyBatchObject());
    }
}
package com.mycompany.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import com.mycompany.MyBatchObject;
import com.mycompany.locking.LockVal;

@Aspect
public class MyAspect {
    @Pointcut("execution(@com.mycompany.locking.Lock * *(@com.mycompany.locking.LockVal (*), ..)) && args(batch)")
    public void lockedMethod(MyBatchObject batch) {}

    @Around("lockedMethod(batch)")
    public Object lockAndProceed(ProceedingJoinPoint pjp, MyBatchObject batch) throws Throwable {
        System.out.println(pjp);
        System.out.println(batch);
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Class<?> clazz = methodSignature.getDeclaringType();
        Method method = clazz.getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        LockVal propertyToLock;
        for (Annotation ann : method.getParameterAnnotations()[0]) {
            if(LockVal.class.isInstance(ann)) {
                propertyToLock = (LockVal) ann;
                System.out.println(propertyToLock.value());
            }
        }
        return pjp.proceed();
    }
}

When I run UpdateManagerImpl.main, I see the following output, just as expected:

execution(void com.mycompany.UpdateManagerImpl.processUpdate(MyBatchObject))
com.mycompany.MyBatchObject@86f241
lockValue
Processing update

Disclaimer: I am not a Spring guy, I just tested this with plain AspectJ, not Spring AOP.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • May I please ask, what is @args meant for, then? I am not sure that I understand the difference between the annotation on the parameter and the annotation on the parameter type. – Brad May 18 '13 at 16:44
  • Also, I attempted to remove the `@args` out of the pointcut altogether, but the pointcut still is not executed. Something about the `args()` condition is causing it to fail, because when I remove both of those conditions, the pointcut does execute as expected. – Brad May 18 '13 at 16:46
  • Just use my code, it works. As for `@args`, if you catch a parameter `MyType foo`, `@args` only matches an annotation declared on class `MyType`, not on the method parameter. – kriegaex May 18 '13 at 21:40
  • I tried using this code as you suggested, but the pointcut didn't execute. If I remove the `args(batch)` condition, it does. – Brad May 19 '13 at 14:03
  • Then you did not use all of my code or you misadjusted it to your code. I have posted the full example with annotations, imports and package names on purpose, so you can see the full picture. If you do not make any mistakes in adjusting or if there is not a fundamental difference between Spring AOP and AspectJ, you should be fine. – kriegaex May 19 '13 at 14:49
  • The code you posted does work IF you remove the `args(batch)` condition on the pointcut and remove the corresponding parameters. The code also works if I run it as a standalone program instead of under JBoss 6. This is fundamentally my question - why is this? – Brad May 19 '13 at 16:16
  • I tried this with SpringBoot but I don't get the MyAspect printout. Any suggestion to how to make this work with Spring? – datree May 03 '17 at 15:57
1

This is not a solution, but should take you a step further:

I am assuming you made a typo in your annotations, you probably meant @Aspect and not @Advice?

The suggestion that I have would be to try out these:

a. Separate out into named point cuts and the advice that you want to apply on the pointcut:

@PointCut("execution(@com.mycompany.locking.Lock * *(@com.mycompany.locking.LockVal(*), ..)) && args(batch) && @args(propertyToLock)")
public void mypointcut(Object batch, LockVal propertyToLock){}

@Around("mypointcut(batch, propertyToLock)"
public Object lockAndProceed(ProceedingJoinPoint pjp, Object batch, LockVal propertyToLock) throws Throwable { 
    //Do stuff.... 
    pjp.proceed();
}   

b. It could be that either args expression or @args expression is causing the issue - try keeping one and removing other and seeing which combination works.

c. If this does not narrow things down, one more option could be to explicitly add an argNames expression also, it could be that the argument names are being cleaned out and not being matched up by name at runtime:

 @PointCut("execution(@com.mycompany.locking.Lock * *(@com.mycompany.locking.LockVal(*), ..)) && args(batch) && @args(propertyToLock) && argNames="batch,test1,test2")
public void mypointcut(Object batch, LockVal propertyToLock){}
Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125
  • You cannot bind a parameter's annotation via `args()`, only the parameter itself. See my own answer. – kriegaex May 18 '13 at 13:35
  • I tried a) and b) without success. I'm working on testing c) now - thanks for the suggestion! – Brad May 18 '13 at 16:46
  • 1
    When I attempted the pointcut: `execute(@com.mycompany.locking.Lock * *(@com.mycompany.locking.LockVal (*))) && args(batch) && argNames(\"batch\")` I received the error `error at ::0 multiple bindings0, BindingTypePattern(java.lang.Object, 0)` – Brad May 18 '13 at 18:18