AspectJ designator #args() not working in Spring AOP - java

I am learning Spring and I searched a lot about how to properly use #args() AspectJ designator but I am still not clear completely. What I know about it is that it limits joint-point matches to the execution of methods whose arguments are annoted with the given annotation types. This does not seem to work in my case.
So here goes my files:
Human.java
#Component
public class Human {
int sleepHours;
public int sleep(String sleepHours) {
this.sleepHours = Integer.parseInt(sleepHours);
System.out.println("Humans sleep for " + this.sleepHours + " hours.");
return this.sleepHours+1;
}
}
Sleepable.java - Sleepable annotation
package com.aspect;
public #interface Sleepable {
}
SleepingAspect.java - Aspect
#Component
#Aspect
public class SleepingAspect {
#Pointcut("#args(com.aspect.Sleepable)")
public void sleep(){};
#Before("sleep()")
public void beforeSleep() {
System.out.println("Closing eyes before sleeping");
}
#AfterReturning("sleep()")
public void afterSleep() {
System.out.println("Opening eyes after sleep");
}
}
MainApp.java
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Human human = (Human) context.getBean("human");
#Sleepable
String sleepHours = "8";
human.sleep(sleepHours);
}
}
Output
Humans sleep for 8 hours.
Expected Output
Closing eyes before sleeping
Humans sleep for 8 hours.
Opening eyes after sleep

You have several mistakes in your code:
Spring AOP can only intercept method calls upon Spring components. What you are trying to intercept is an annotation on a local variable. Not even the much more powerful AspectJ can intercept anything concerning local variables, only read/write access to class members. Thus, what you are trying to do is impossible. And by the way, it is bad application design. Why would anyone want to rely on method internals when trying to apply cross-cutting behaviour? Method internals are subject to frequent refactoring. Suggestion: Put your annotation on method public int sleep(String sleepHours).
Your annotation is invisible during runtime because your forgot to add a meta annotation like #Retention(RetentionPolicy.RUNTIME).
#args is the wrong pointcut type. It captures method arguments the types of which are annotated. You want to use #annotation(com.aspect.Sleepable) instead.
I think you should not try a copy & paste cold start with Spring AOP but read the Spring AOP manual first. Everything I explained here can be found there.
Update: So according to you comments you were just practicing and trying to make up an example for #args(). Here is one in plain AspectJ. You can easily use it in similar form in Spring AOP. The #Before advice shows you how to match on an argument with an annotation on its class, the #After advice also shows how to bind the corresponding annotation to an advice argument.
Annotation + class using it:
package com.company.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {}
package com.company.app;
#MyAnnotation
public class MyClass {}
Driver application:
package com.company.app;
public class Application {
public static void main(String[] args) {
new Application().doSomething(new MyClass(), 11);
}
public String doSomething(MyClass myClass, int i) {
return "blah";
}
}
As you can see, here we use the annotated class MyClass in a method argument. This can be matched with #args() in the following aspect.
Aspect:
package com.company.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import com.company.app.MyAnnotation;
#Aspect
public class MyAspect {
#Before("#args(com.company.app.MyAnnotation, ..)")
public void myBeforeAdvice(JoinPoint thisJoinPoint) {
System.out.println("Before " + thisJoinPoint);
}
#After("#args(myAnnotation, ..)")
public void myAfterAdvice(JoinPoint thisJoinPoint, MyAnnotation myAnnotation) {
System.out.println("After " + thisJoinPoint + " -> " + myAnnotation);
}
}
Console log:
Before call(String com.company.app.Application.doSomething(MyClass, int))
Before execution(String com.company.app.Application.doSomething(MyClass, int))
After execution(String com.company.app.Application.doSomething(MyClass, int)) -> #com.company.app.MyAnnotation()
After call(String com.company.app.Application.doSomething(MyClass, int)) -> #com.company.app.MyAnnotation()
Of course, call() joinpoints are unavailable in Spring AOP, so there you would only see two lines of log output.

Related

How do I perform something on each method call of an annotated method?

I want to write an annotation in Java, which executes something before and after execution of the annotated method, similar to what can be done in Spring with aspects.
I've already tried a Spring aspect, but it only works for Beans (as this guy here mentioned) and I want to stay independent from the Spring framework.
A simple class that writes a String to the console:
public class Main {
public static void main(String[] args) {
say("How are you?");
}
#Hello
private static void say(String s) {
System.out.println(s);
}
}
The associated annotation:
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 Hello {}
And I need something like (deduced from the Spring aspect)
public Object process(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("Hello");
Object proceed = null;
try {
proceed = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("world!");
return proceed;
}
I want the following output:
Hello
How are you?
world!
Edit:
I created the following Aspect (without annotations), but it does not work
#lombok.extern.java.Log
public aspect Log {
pointcut methodExecuted():execution(* **(..));
void around(): methodExecuted() {
log.info("Hello");
proceed();
log.info("world!");
}
}
Where is my mistake?
Assuming that you successfully compiled your aspect with the AspectJ compiler, it should work with the code you used, only that it would log all method executions, i.e. also main(..), so you would se the aspect's output twice before and after "How are you?". If you don't see anything probably you made a mistake in setting up your build system.
You should change the pointcut to actually limit logging to annotated methods: execution(* *(..)) && #annotation(Hello). Furthermore if your around advice has a void return type, logging will not work with non-void methods. So you should rather use a return type of Object and actually return the result of proceed().
I also strongly urge you to not just blindly wield a powerful tool like AspectJ but also study some documentation before you do. It is quite obvious that you have not done so or only very cursorily. Then you get the effect of being a not-so-capable-user with a tool. ;-)
Here is my MCVE:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(METHOD)
public #interface Hello {}
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
say("How are you?");
}
#Hello
private static void say(String s) {
System.out.println(s);
}
}
package de.scrum_master.aspect;
import de.scrum_master.app.Hello;
public aspect LoggingAspect {
pointcut methodExecuted() : execution(* *(..)) && #annotation(Hello);
Object around(): methodExecuted() {
System.out.println("Hello");
Object result = proceed();
System.out.println("world!");
return result;
}
}
Console log:
Hello
How are you?
world!

AspectJ pointcut && is behaving like ||

I have the following advice:
#Before("execution(* com.myapp..*.*(..)) && !execution(* com.myapp.cms.workflow..*.*(..))")
public void logBefore(JoinPoint joinPoint) {
log.info("Calling " + joinPoint.getSignature().getName());
}
When I add the second condition to the pointcut:
&& !execution(* com.myapp.cms.workflow..*.*(..))
it logs every method call from every package.
I want the advice only to apply if its in the myapp package but not under the workflow package. Can anyone advise what I've done wrong?
AspectJ 1.6.8
it logs every method call from every package
No, it does not. It logs every method from package com.myapp and all its subpackages only, except for everything inside and below com.myapp.cms.workflow. If this is not what you want, maybe you should change your pointcut.
BTW, why are you using an outdated AspectJ version from 2009? It only supports Java 6 which is long out of support.
Update:
As you seem to not believe me, here is proof that your statement is wrong.
Java classes according to your example:
package com.myapp;
public class Foo {
public String convert(Integer number) {
return number.toString();
}
}
package com.myapp.cms.workflow;
public class Workflow {
public void doSomething() {}
}
package de.scrum_master.app;
import com.myapp.Foo;
import com.myapp.cms.workflow.Workflow;
public class Application {
// Should not be logged
public static void main(String[] args) {
// Should be logged
new Foo().convert(11);
// Should not be logged
new Workflow().doSomething();
}
}
Aspect according to your example:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class LogAspect {
#Before("execution(* com.myapp..*.*(..)) && !execution(* com.myapp.cms.workflow..*.*(..))")
public void logBefore(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
I compiled with Java 8 and AspectJ 1.8.13. I even tried from the console with AspectJ 1.6.8 and Java 1.6.0_45, the result is exactly the same.
Console log:
execution(String com.myapp.Foo.convert(Integer))
Ergo: Everything works as expected. Either you have not shown me your real pointcut or you forgot to remove another aspect logging everything from the classpath or whatever. AspectJ is not the problem. The problem sits in front of the computer, I would assume.

AOP on plain java application

I have successfully use AOP with Spring applications, but surprisingly I stuck on a simple java project. now I'm trying to implement very simple AOP java application, but it doesn't work. Here are basic classes:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class MySimpleLoggerAspect {
#Around("#annotation(TimeableMetric)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("myTrace:before call ");
Object retVal = null;
try {
retVal = joinPoint.proceed();
} finally {
System.out.println("myTrace:after call ");
}
return retVal;
}
}
public class SampleClass {
#TimeableMetric
public String doService(String in){
System.out.println("inside Service");
return in;
}
}
public class Tester {
public static void main(String[] args) {
System.out.println(new SampleClass().doService("Hello World"));
}
}
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, ElementType.TYPE})
public #interface TimeableMetric {
}
As you can see it is very simple app with 4 classes. IntelliJ detects AOP advise properly, but it is ignored when I running the app. I'm sure there is a samll mistake which I just can't detect.
Please help!
The code is okay, for me the console log looks like this:
myTrace:before call
myTrace:before call
inside Service
myTrace:after call
myTrace:after call
Hello World
Probably you are used from Spring AOP to get only one interception for the pointcut #annotation(TimeableMetric), but in opposite to Spring AOP only knowing execution() pointcuts, AspectJ also supports call() pointcuts. So if you change your pointcut to #annotation(TimeableMetric) && execution(* *(..)), the log becomes:
myTrace:before call
inside Service
myTrace:after call
Hello World
As for the question how to get the aspects applied, you need to
compile the application with the AspectJ compiler ajc and then
run it with the AspectJ runtime aspectjrt.jar on the classpath.

How to write AspectJ AOP expression for this method?

I have tried many combinations but I am unable to invoke callback before execution of following method
#SomeAnnotation(...)
#Override
public void someMethod(Serializable id) {
}
I have tried many combinations similar to
#Before("execution(#com.full.name.of.SomeAnnotation * com.full.name.of.Class.someMethod(java.io.Serializable))")
public void beforeMethod() {
System.out.println("I am here.");
}
If I write a more generic expession, it hits the beforeMethod but I am unable to target a single specific method. What am I missing here?
Okay guys, let me prove that the pointcut actually works as written by the original poster Haris Hasan.
Sample annotation:
package com.full.name.of;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface SomeAnnotation {
int id();
String name();
}
Sample class using the annotation:
package com.full.name.of;
import java.io.Serializable;
public class Class {
#SomeAnnotation(id = 1, name = "John Doe")
public void someMethod(Serializable s) {}
public static void main(String[] args) {
new Class().someMethod("x");
}
}
Sample aspect with Haris Hasan's exact pointcut:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class SampleAspect {
#Before("execution(#com.full.name.of.SomeAnnotation * com.full.name.of.Class.someMethod(java.io.Serializable))")
public void yetAnotherPointcut(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
Console output:
execution(void com.full.name.of.Class.someMethod(Serializable))
Copy and paste everything exactly as is, then run it. Quod erat demonstrandum.
The expression below should work:
#Before("com.full.name.of.Class.someMethod(..) && args(java.io.Serializable)")
Correction by kriegaex:
#Before("execution(* com.full.name.of.Class.someMethod(*)) && args(java.io.Serializable)")
A few points worth considering: Is the method being advised public? Are you using cglib proxies or jdk proxies? Does your class implement any interface and is the method being advised declared in the interface contract?
Also do take a look at what spring doc has to say about writing good pointcuts
Hope this helps.
Why do you not just use
#Before("execution(* com.full.name.of.Class.someMethod(java.io.Serializable))")
That specifically targets the method you want, you do not need to put the annotation to specify the target

AspectJ - Multiple #annotation Pointcut

I can't make a pointcut with "||" operator and multiple annotations. I'm trying to create a Pointcut for some JBehave annotations (#Given, #Then, #When).
This works fine:
#Pointcut("#annotation(given)")
public void jBehaveGivenPointcut(Given given) { }
and if I create and advice arount it, it also works.
What would be the syntax for making the Pointcut for the three annotations? Since I had used the logical OR operator in other pointcuts I would assume it is something like:
#Pointcut("#annotation(given) || #annotation(then) || #annotation(when) ")
public void jBehaveGivenPointcut(Given given, Then then, When when) { }
but it doesn't work, I get an inconsist binding exception. I tried other combinations but couldn't find the one that does the trick.
What you want cannot be done this way because, as the error message says, the annotation binding is inconsistent: You cannot bind all three annotations at the same time because they are in (possibly mutually exclusive) OR parts of the pointcut, i.e. usually only of of them can be bound (unless you assign multiple annotations to the same method). Your expectation might be that AspectJ can deal with this inconsistency by just assigning null to the other two if one is bound, but this is not how the compiler works right now.
I can offer a workaround which involves reflection instead of using the #annotation() binding.
Driver application:
package de.scrum_master.app;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
public class Application {
public static void main(String[] args) {
doGiven("foo");
doSomething("bar");
doWhen(11);
doSomethingElse(22);
doThen();
}
#Given("an input value") public static void doGiven(String string) {}
#When("I do something") public static void doWhen(int i) {}
#Then("I should obtain a result") public static boolean doThen() { return true; }
public static void doSomething(String string) {}
public static void doSomethingElse(int i) {}
}
Aspect:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
#Aspect
public class JBehaveInterceptor {
#Pointcut("execution(#org.jbehave.core.annotations.* * *(..))")
public void jBehavePointcut() {}
#Before("jBehavePointcut()")
public void jBehaveAdvice(JoinPoint.StaticPart thisJoinPointStaticPart) {
Method method = ((MethodSignature) thisJoinPointStaticPart.getSignature()).getMethod();
for (Annotation jBehaveAnnotation : method.getAnnotations()) {
if (jBehaveAnnotation.annotationType().getPackage().getName().equals("org.jbehave.core.annotations"))
System.out.println(thisJoinPointStaticPart + " -> " + jBehaveAnnotation);
}
}
}
As you can see, the pointcut only intercepts methods annotated by org.jbehave.core.annotations.* which narrows down pointcut matching considerably - to more than just #Given, #When, #Then, but maybe that is even what you want because JBehave offers more annotations than just those.
In the advice we loop over all method annotations because there might be more than just the JBehave ones. If any annotation package name matches the corresponding JBehave package, we do something (in this case print the annotation to standard output).
I hope this solves your problem. I cannot extend the AspectJ language for you, this is the best I can think of. Anyway, this yields the following output:
execution(void de.scrum_master.app.Application.doGiven(String)) -> #org.jbehave.core.annotations.Given(priority=0, value=an input value)
execution(void de.scrum_master.app.Application.doWhen(int)) -> #org.jbehave.core.annotations.When(priority=0, value=I do something)
execution(boolean de.scrum_master.app.Application.doThen()) -> #org.jbehave.core.annotations.Then(priority=0, value=I should obtain a result)
This works:
#Component
#Aspect
#RequiredArgsConstructor
public class PermissionAspect {
#Pointcut("#annotation(org.springframework.web.bind.annotation.GetMapping)")
public void getMapping(){}
#Pointcut("#annotation(org.springframework.web.bind.annotation.PostMapping)")
public void postMapping(){}
#Pointcut("#annotation(org.springframework.web.bind.annotation.PutMapping)")
public void putMapping(){}
#Pointcut("#annotation(org.springframework.web.bind.annotation.DeleteMapping)")
public void deleteMapping(){}
#Around("(execution(* xx.*.controller.*.*(..))) && (getMapping() || postMapping() || putMapping() || deleteMapping())")
Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

Categories