Hot questions for Using AspectJ in spring annotations

Top Java Programmings / AspectJ / spring annotations

Question:

I have the following interface:

/**
 * Annotation for methods, whose execution should be logged.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Loggable {

    /**
     * Log severity level of 'before' and 'after' log statements.
     */
    enum Level {
        DEBUG,
        INFO,
        WARN,
        ERROR
    }

    /**
     * Defines the severity which should be used when logging the method arguments.
     */
    Level level() default Level.FATAL;
}

I also have the following class:

/**
 * Class for logging input and output parameters of any method with annotation @Loggable.
 */
@Aspect
public final class LoggingAspect {
    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * @param jp - ProceedingJointPoint
     * @param loggable - Loggable
     * @return returns the next executable point to proceed in target
     * @throws Throwable - throws exception when proceeding with joint point
     */
    @Around("execution(* *(..)) && @annotation(loggable)")
    public Object loggingAroundMethod(@Nonnull final ProceedingJoinPoint jp,
                                      @Nonnull final Loggable loggable) throws Throwable {
        final String signature = jp.getTarget().getClass().getName() + '.' + jp.getSignature().getName();
        final List<Object> arguments = Arrays.asList(jp.getArgs());

        final Object result;
        try {
            doLog(loggable.level(), "[BEFORE] {}{}", signature, arguments);
            result = jp.proceed();
            doLog(loggable.level(), "[AFTER] {}{} result={}", signature, arguments, result);
        } catch (Exception e) {
            log.error("[AFTER] {}{} exception={}", signature, arguments, e);
            throw e;
        }

        return result;
    }

        /**
         * Logs the message with appropriate log level.
         * @param level - level to log
         * @param format - format for logging
         * @param arguments - arguments for logging
         */
        private void doLog(@Nonnull final Loggable.Level level, @Nonnull final String format, final Object... arguments) {
            switch (level) {
                case DEBUG:
                    log.debug(format, arguments);
                    return;

                case INFO:
                    log.info(format, arguments);
                    return;

                case WARN:
                    log.warn(format, arguments);
                    return;

                case ERROR:
                    break;

                default:
                    log.error("Unable to appropriately handle given log level={}", level);
            }
            log.error(format, arguments);
        }
    }

And here is my XML:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
       default-autowire="no">
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <bean class="path.to.my.package.LoggingAspect" />
</beans>

Now, when I add the @Loggable annotation to an existing method that is being called elsewhere in my program, everything shows up correctly in my logs as expected. The working annotations with method looks something like this:

@Loggable
public boolean testString(String test) {
  return test.equals("foo"); 
} 

However, when I try to add the annotation to a helper method rather than the method that is already being called in my program, no logs show up. So now the code that doesn't work looks something like this:

public boolean testString(String test) {
  return testStringHelper(test);  
} 

@Loggable
public boolean testStringHelper(String test) {
  return test.equals("foo"); 
}

Can anyone offer insight into why the first scenario works, but the second scenario with the helper method doesn't? As an aside, the helper methods are all public. Also, if I add a regular log statement inside the helper method, it does show up in my logs. It's just the annotation that doesn't work with the helper method for some reason.


Answer:

Spring can only advise methods of Spring beans that have been injected into other Spring beans. If a bean calls one of its own methods, then the advice will not be executed.

Spring AOP proxies are explained in the docs.

Question:

I want to create a custom annotation to skip method execution

This is my annotation code, with the validator class

@Target({ METHOD , FIELD , PARAMETER } )
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy={MyValidator .class})
public @interface MyAnnotation {

    String message() default "DEFAULT_FALSE";

    Class<?>[] groups() default{};

    Class<? extends Payload>[] payload() default{};

}

I tried it with validator. This is how my validator looks like

public class MyValidator implements ConstraintValidator<MyAnnotation, String >{

    @Override
    public void initialize(MyAnnotation arg0) {

    }

    @Override
    public boolean isValid(String arg0, ConstraintValidatorContext arg1) {

        if(str=="msg"){
            return true;
        }
        return false;
    }

}

And this is how I want to use -- I want to use the annotation on method level and to skip the method execution.

I don't know if it is possible.. Please help.

public class Test {



    public static void main(String[] args) {
    Test t = new Test();
        boolean valid=false;

         valid=t.validate();
        System.out.println(valid);

    }

@MyAnnotation(message="msg")
    public boolean validate(){

     // some code to return true or false
    return true;


    }
}

Answer:

You should use AOP for that. Create a AspectJ project, and try something like this:

MyAnnotation.java:

package moo.aspecttest;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface MyAnnotation
{
    public String value();
}

MyAspectClass.java:

package moo.aspecttest;

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.reflect.MethodSignature;

@Aspect
public class MyAspectClass
{

    @Around("execution(* *(..))")
    public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable
    {
        Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
        String name = method.getName();
        MyAnnotation puff = method.getAnnotation(MyAnnotation.class);
        if (puff != null) {
            System.out.println("Method " + name + " annotated with " + puff.value() + ": skipped");
            return null;
        } else {
            System.out.println("Method " + name + " non annotated: executing...");
            Object toret = point.proceed();
            System.out.println("Method " + name + " non annotated: executed");
            return toret;
        }
    }
}

MyTestClass.java:

package moo.aspecttest;

public class MyTestClass
{

    @MyAnnotation("doh")
    public boolean validate(String s) {
        System.out.println("Validating "+s);
        return true;
    }

    public boolean validate2(String s) {
        System.out.println("Validating2 "+s);
        return true;
    }

    public static void main(String[] args)
    {
        MyTestClass mc = new MyTestClass();

        mc.validate("hello");
        mc.validate2("cheers");

        }
    }
}

output generated when you run it:

Method main non annotated: executing...
Method validate annotated with doh: skipped
Method validate2 non annotated: executing...
Validating2 cheers
Method validate2 non annotated: executed
Method main non annotated: executed

I used a very generic aroundAdvice, but you can use a beforeAdvice, if you want. Indeed, I think that point is clear.

Question:

I have a controller:

@Authorised(id = "{personId}")
@RequestMapping(value = {"{personId}"}, method = GET)
public void test(@PathVariable PersonId personId) {
    System.out.println(personId); //gets personId
}

Annotation:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Authorised {
    String id() default "";
}

Pointcut:

@Pointcut("@annotation(Authorised)")
private void AuthorisedMethod() {}

And the method that has to get {personId} value not string "{personId}":

@Before("AuthorisedMethod()")
public void checkIfIsCurrentlyAuthenticated(JoinPoint joinPoint) throws NoSuchMethodException {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    String methodName = signature.getMethod().getName();
    Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
    Parameter[] parameters = signature.getMethod().getParameters();
    Authorised annotations = joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes).getAnnotation(Authorised.class);
    String id = annotations.id();
    System.out.println(id); // prints: "{personId}"
    // do the chekcing
    throw new UnauthenticatedUserException();
}

Can it be achieved and how?

UPDATE: But what if method argument parameter number don't match with the pointcut args()? I mean that what if specific method has parameter @PathVariable PersonId personId and few more, but poincut needs to know only PersonId personId?

Like @statut said you have to write args() like that: args(personId,..)


Answer:

You can modify @Before() annotation to have PersonId value and pass this value to aspect, for example

@Before("AuthorisedMethod() && args(personId)")
public void checkIfIsCurrentlyAuthenticated(JoinPoint joinPoint, PersonId personId) throws NoSuchMethodException {}

To test it I had the following Aspect:

@Aspect
@Component
public class SomeAspect {

    @Pointcut("@annotation(Authorised)")
    private void AuthorisedMethod() {
    }

    @Before("AuthorisedMethod() && args(personId)")
    public void checkIfIsCurrentlyAuthenticated(JoinPoint joinPoint, PersonId personId) throws NoSuchMethodException {
        System.out.println("aspect " + personId.getId());
    }

}

Configuration class:

@Configuration
@ComponentScan(basePackages = {"test"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {
}

Test component:

@Component
public class Test {

    @Authorised(id = "{personId}")
    public void test(PersonId personId) {
        System.out.println("component " + personId.getId()); //gets personId
    }
}

And testNG's runner:

@ContextConfiguration(classes = Config.class)
public class TestRunner extends AbstractTestNGSpringContextTests {

    @Autowired
    test.Test test;

    @Test
    public void testName() {
        test.test(new PersonId("id"));
    }

}

When I run it, I get printed "aspect id" from aspect and "component id" from invoked method.

Question:

Here's the existing code:

@Transactional
//  <-- Want to Pointcut to here, after Transactional is done, before method execution
public String getUsername(int userId) {
     ...
}

Trying to pointcut to the commented line, here's what I tried so far:

@Around("@annotation(org.springframework.transaction.annotation.Transactional) " +
        " && !target(org.springframework.transaction.interceptor.TransactionAspectSupport)")
public Object runQueryWithinTransaction(ProceedingJoinPointjp) throws Throwable {
     ...
}

Also tried setting the order of my aspect to LowestPrecedence. Despite all of this, I still see my Aspect being called before Transactional's "invokeWithinTransaction" method. Tried "@After" but that was executed after my "getUsername" method. Tried "@Before", for no reason, but that obviously didn't help.

What should I be doing instead?


Answer:

Solved this by setting precedence in my aspect like so:

@Aspect
@DeclarePrecedence("org.springframework.transaction.aspectj.AnnotationTransactionAspect, *")
public class MyAspect {
    ....
}

Thanks to @kriegaex, in case you aren't using AspectJ and regular Spring AOP, you should be trying to use @Ordered and @Order annotations. @Order on Spring Transactional annotation can be set in @EnableTransactionManagement annotation, and set a lower precedence (or none at all, since lowest precedence is default) for your own Aspect.