AOP on plain java application - java

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.

Related

Spring Boot AOP nested custom annotations not called [duplicate]

This question already has answers here:
Why does self-invocation not work for Spring proxies (e.g. with AOP)?
(2 answers)
Closed 23 days ago.
I have created a simple custom annotation in Spring Boot that logs something and it's working but only for the first annotation, the nested one are not called
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Traceable {
}
Annotation processor (aspect)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class TraceableAspect {
#Around("#annotation(Traceable)")
public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Inside Aspect");
Object result = joinPoint.proceed();
System.out.println(result);
return result;
}
}
Example of a controller used to test
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
#RequestMapping("/test")
public class ControllerTest {
#GetMapping("/get")
#Traceable
public String get(){
test1();
test2();
return "Hi";
}
#Traceable
public void test1(){
String str = "1";
System.out.println(str);
}
#Traceable
public Object test2(){
String str = "2";
System.out.println(str);
test1();
return null;
}
}
The console output here is:
Inside Aspect
1
2
1
Hi
but I think it's worng, it has to be like this:
Inside Aspect
Inside Aspect
1
Inside Aspect
2
Inside Aspect
1
Hi
It seems that only the first #Traceable is processed, all the other ones are ignored. How to handle this? Thanks
To understand why you're getting the results you see, you have to understand how Spring implements handling of most behavioral annotations: using proxies. That means that only method calls that come through the proxy get the annotation behavior. That's the typical scenario when one object calls a reference it has to another object; the reference is actually to the proxy, which intercepts the call and wraps the behavior (in this case, logging) around it. However, when calling methods within the same instance, there is no proxy in play (Spring doesn't/can't replace this with a reference to the proxy), and thus no annotation behavior.
There are a few ways to work around that, some ideas are discussed in this SO Q&A. There are also some answers here with options to work around the proxying limitation.

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.

AspectJ designator #args() not working in Spring AOP

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.

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

Categories