AspectJ: intercepting methods based on parameter values - java

I am using AspectJ to intercept a method called Request(String, String). For that I am using my own specified (marker) annotation. This is what the class looks like:
Class myclass {
public void Request(#Intercept String t, String u) {
// ...
}
}
The aspect that intercepts the #Intercept annotations:
#Aspect
class someAspect {
#Intercept
#Around("execution(public * * (#Interceptor (*), ..))")
public void capture(ProceedingJoinPoint pjp) {
// ...
}
}
However, my aspect is intercepting based on the annotated parameters. But I want the aspect to intercept the method request on specific values which the parameter t contains.
For example, if t == "t1", the method must be intercepted, otherwise not.
I was wondering if it is possible to do this in AspectJ (in combination with Spring AOP).

Actually there are a few issues with your code (I just reformatted it so it is at least readable):
In Java class names are usually in camel case, i.e. MyClass or SomeAspect instead of myclass or someAspect.
Method names in Java begin with lower case characters, i.e. request rather than Request.
You are mentioning two annotations #Intercept and #Interceptor. From now on I will assume that actually we are dealing with just one called #Intercept, okay?
You also annotate your aspect's advice with #Intercept, but I guess this is not intended, is it? It might lead to infinite recursion in another pointcut if the intercepting advice is intercepting itself...
As for your actual question, then answer is: sort of, but not statically within a pointcut (because parameters are only determined during runime) but dynamically, also during runtime. You have two options:
Option A: if-else within advice code
package de.scrum_master.app;
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 Intercept {}
package de.scrum_master.app;
public class Application {
public static void one(#Intercept String string) {}
public static void two(String string, String string2) {}
public static void three(#Intercept String string, int i, int j) {}
public static void main(String[] args) {
one("foo");
two("foo", "bar");
three("foo", 11, 22);
one("bingo");
two("bingo", "bongo");
three("bingo", 33, 44);
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class InterceptorAspect {
#Pointcut("execution(public * *(#de.scrum_master.app.Intercept (*), ..)) && args(text, ..)")
public static void normalPointcut(String text) {}
#Around("normalPointcut(text)")
public Object capture(ProceedingJoinPoint thisJoinPoint, String text) {
if (text != "bingo")
return thisJoinPoint.proceed();
System.out.println(thisJoinPoint);
for (Object arg : thisJoinPoint.getArgs())
System.out.println(" " + arg);
// Do something before calling the original method
Object result = thisJoinPoint.proceed();
// Do something after calling the original method
return result;
}
}
Option B: use if() pointcut
The if() pointcut is a static method returning a boolean value based on the dynamic condition you want to test. The refactored aspect would look like this:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class InterceptorAspect {
#Pointcut("if() && execution(public * *(#de.scrum_master.app.Intercept (*), ..)) && args(text, ..)")
public static boolean conditionalPointcut(String text) {
return text != "bingo";
}
#Around("conditionalPointcut(text)")
public Object capture(ProceedingJoinPoint thisJoinPoint, String text) {
System.out.println(thisJoinPoint);
for (Object arg : thisJoinPoint.getArgs())
System.out.println(" " + arg);
// Do something before calling the original method
Object result = thisJoinPoint.proceed();
// Do something after calling the original method
return result;
}
}
As you can see, the dynamic, parameter-based condition has moved into the pointcut which is no longer a void method with an empty body but a boolean method returning the result of the parameter check. Furthermore, the pointcut expressen got prepended by an if() && expression.
Console log:
The console output for both aspect variants is exactly the same:
execution(void de.scrum_master.app.Application.one(String))
foo
execution(void de.scrum_master.app.Application.three(String, int, int))
foo
11
22

Related

AspectJ: Pointcut to declare and retrieve an annotation of a method's parameter

I have read the following valuable links:
Spring AOP pointcut for annotated argument
How to write an Aspect pointcut based on an annotated parameter
AspectJ pointcut expression match parameter annotations at any position
Consider this request for a setter method
public void setSomething(#ParameterLevel(name="abc") String something){
this.something = something;
}
I have the following and works fine:
#Pointcut("execution(* *.*(#somepackage.ParameterLevel (*)))")
void parameterLevel01() {}
Now I want retrieve the #ParameterLevel annotation through a method's parameter such as the following:
#Pointcut("execution(* *.*(#somepackage.ParameterLevel (*)))")
void parameterLevel01(ParameterLevel parameterLevel) {} <--To be used directly in the advice method
The purpose is use the Annotation directly how a parameter in the advice method
Something similar such as:
#within(classLevel) for #ClassLevel in:
#ClassLevel
public class SomeClass {
...
}
#annotation(methodLevel) for #MethodLevel in:
#MethodLevel
public void somethingToDo(){
...
}
How accomplish this goal. Is possible? I am working with AspectJ 1.9.6
No matter if you use .., #MyAnnotation (*), .. or just #MyAnnotation (*), which only removes the ambiguity of possibly multiple matches, there is no direct way to bind a method argument annotation to an advice argument, only the method argument itself. This has not changed in AspectJ. You would have seen it mentioned in the release notes otherwise, because it would be a new feature.
So you will have to use the method from my other two answers which you have already linked to in your question, i.e. iterating over parameter types and annotations manually.
Somewhat off-topic, there is a very old Bugzilla ticket #233718 which is about binding multiple matched (annotated) parameters, but not about binding their annotations. It came up in a recent discussion I had with AspectJ maintainer Andy Clement. But even if this was implemented one day, it would not solve your problem.
I think you can take it from here and adapt my solution from the linked questions to your needs. Feel free to let me know if you have any follow-up questions about that, but it should be pretty straightforward. You might be able to optimise because you know the exact parameter position (think array index), if you feel so inclined, i.e. you don't need to iterate over all parameters.
Update: Here is a little MCVE for you. It is based on this answer and has been simplified to assume the annotation is always on the first parameter and the first parameter only.
Please learn what an MCVE is and provide one by yourself next time because it is your job, not mine. This was your free shot.
Marker annotation + driver application:
package de.scrum_master.app;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
#Retention(RUNTIME)
public #interface ParameterLevel {
String name();
}
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new Application().doSomething("foo");
}
public void doSomething(#ParameterLevel(name="abc") String string) {}
}
Aspect:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.SoftException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import de.scrum_master.app.ParameterLevel;
#Aspect
public class ParameterLevelAspect {
#Before("execution(public * *(#de.scrum_master.app.ParameterLevel (*))) && args(string)")
public void beforeAdvice(JoinPoint thisJoinPoint, String string) {
System.out.println(thisJoinPoint + " -> " + string);
MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
String methodName = signature.getMethod().getName();
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
Annotation[] annotations;
try {
annotations = thisJoinPoint.getTarget().getClass()
.getMethod(methodName, parameterTypes)
.getParameterAnnotations()[0];
} catch (NoSuchMethodException | SecurityException e) {
throw new SoftException(e);
}
ParameterLevel parameterLevel = null;
for (Annotation annotation : annotations) {
if (annotation.annotationType() == ParameterLevel.class) {
parameterLevel = (ParameterLevel) annotation;
break;
}
}
assert parameterLevel != null;
System.out.println(" " + parameterLevel + " -> " + parameterLevel.name());
}
}
Console log:
execution(void de.scrum_master.app.Application.doSomething(String)) -> foo
#de.scrum_master.app.ParameterLevel(name="abc") -> abc

Is it possible to have multiple pointcuts with different arguments in Spring AOP?

I have single advice method with multiple pointcuts. Is it possible to have different arguments?
#Around("execution(* com.admin.web.controller.*.*.*(javax.servlet.http.HttpServletRequest,com.admin.model.PortalRequestTransaction)) && args(request,portalRequestTransaction)"
+ " || execution(* com.admin.web.controller.BaselineImporterController.*(javax.servlet.http.HttpServletRequest,..)) && args(request)")
public Object auditAround(ProceedingJoinPoint joinPoint, HttpServletRequest request,
PortalRequestTransaction portalRequestTransaction) throws Throwable { // some code here }
For example, Is it possible to the first pointcut to have 2 arguments and for the second one to have only one argument and second one passed as null?
If this is not possible, what is the best solution?
One way to achieve this is using JoinPoint.getArgs() to get the method arguments as an object array. This involves reflection.
A sample code to achieve this would be as follows
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.springframework.stereotype.Component;
#Component
#Aspect
public class TestAspect {
#Pointcut("execution(* com.admin.web.controller.*.*.*(javax.servlet.http.HttpServletRequest,com.admin.model.PortalRequestTransaction))")
public void requestNTransaction() {}
#Pointcut("execution(* com.admin.web.controller.BaselineImporterController.*(javax.servlet.http.HttpServletRequest,..))")
public void requestAsFirstArg() {}
#Around("requestNTransaction() || requestAsFirstArg()")
public Object auditAround(ProceedingJoinPoint pjp) throws Throwable {
// get the signature of the method to be adviced
System.out.println(pjp.getSignature());
Object[] args = pjp.getArgs();
for(Object arg:args) {
// use reflection to identify and process the arguments
System.out.println(arg.getClass());
}
return pjp.proceed();
}
}
Note:
Individual methods serving as Pointcut signature . Spring documentation
ProceedingJoinPoint for #Around advice type
Yes, it is possible to have multiple point-cuts in your advice, and it's done with this generic syntax:
#AdviceType("execution(point-cut1) || execution(point-cut2) || execution(point-cut3)")
public void foo(){}
Note, that #AdviceType and point-cutX are conceptual names, and you should change them respectively with your advice and pointcut[s].

AOP and execute if annotation not present

I am using AOP to wrap an auditing framework around some services. I've come across an issue where we're auditing multiple events for the same action due to recursion. The quick solution it to mark the method as #NonAuditable and add it to my pointcut strategy. I am finding that the method still gets executed however.
Here's my existing strategy:
#Around(value="(" +
"execution( * my.class.services..*.*(..)) " +
") && "+
"#annotation(auditable)), argName="audit")
public Object audit(ProceedingJoinPoint call, Audit audit) {
...
...
}
How can I update my execution to say "only execute within the services package if it doesn't contain the #NonAuditable annotation?
I tried the following, which did not work:
#Around(value="(" +
"execution( * my.class.services..*.*(..)) " +
") && "+
"!#annotation(NonAuditable) && " +
"#annotation(auditable), argName="audit")
public Object audit(ProceedingJoinPoint call, Audit audit) {
...
...
}
UPDATE:
Here are some examples of some methods that gets audited
package my.class.services.UserService
import ...
...
#Auditable(message="Request for user", Context="Search")
public User getUser(long id){
User u = userRepository.getUser(id);
... // do work
}
\
package my.class.services.CarService
import ...
...
#Auditable(message="Request for traffic violations", Context="Search")
public List<Ticket> getTickets(long id){
List<Ticket> tix = dmvRepository.getUserTicketsById(id);
... // do work
}
#NonAuditable(message="Request for traffic violations", Context="Search")
public List<Ticket> getSpeedingTickets(long id){
List<Ticket> tickets = this.getTickets(id);
Collection filter = Collection.filter(...);
// do some other logic to just get speeding tickets.
return filter;
}
One problem I inherited is that getTickets is being called recursively by another method (getSpeedingTickets) and I am looking to be able to apply an Annotation (#NonAuditable) on that method to stop getTickets from being audited.
Okay, I thought about it again and think I have guessed what you mean. I think your situation is like this:
Annotations:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Auditable {}
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface NonAuditable {}
Driver application:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
Application application = new Application();
application.auditableAction("main");
application.inBetweenAction("main");
application.otherAction();
application.nonAuditableAction();
}
#Auditable
public void auditableAction(String caller) {
System.out.println(" auditableAction called by " + caller);
}
#NonAuditable
public void nonAuditableAction() {
auditableAction("nonAuditableAction");
inBetweenAction("nonAuditableAction");
}
public void otherAction() {
auditableAction("otherAction");
inBetweenAction("otherAction");
}
public void inBetweenAction(String caller) {
auditableAction(caller + " via inBetweenAction");
}
}
Now I assume you want to avoid auditing executions of the #Auditable method if called directly or indirectly by the #NonAuditable method. Correct? If so, the solution is to use a cflow() or cflowbelow() pointcut. Because such pointcuts cannot be evaluated statically but only dynamically during runtime, you might need to monitor the performance of your application after applying the aspect, but in many cases this is not a real problem. See for yourself. The solution looks as follows:
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class AuditAspect {
#Around(
"within(de.scrum_master.app..*) && " +
"execution(* *(..)) && " +
"#annotation(de.scrum_master.app.Auditable) && " +
"!cflow(#annotation(de.scrum_master.app.NonAuditable))"
)
public Object audit(ProceedingJoinPoint thisJoinPoint) throws Throwable {
System.out.println(thisJoinPoint);
return thisJoinPoint.proceed();
}
}
Console log:
execution(void de.scrum_master.app.Application.auditableAction(String))
auditableAction called by main
execution(void de.scrum_master.app.Application.auditableAction(String))
auditableAction called by main via inBetweenAction
execution(void de.scrum_master.app.Application.auditableAction(String))
auditableAction called by otherAction
execution(void de.scrum_master.app.Application.auditableAction(String))
auditableAction called by otherAction via inBetweenAction
auditableAction called by nonAuditableAction
auditableAction called by nonAuditableAction via inBetweenAction
Please note that nothing is logged before the last two lines.

AspectJ Handling of Multiple Matching Advices

I am using AspectJ in Java to log the calls to some methods. I've looked online but couldn't manage to find an answer to this:
What happens when two #Around advices match for a method?
Specifically, I am using two #Around advices, like this:
#Around("condition1() && condition2() && condition3()")
public Object around(ProceedingJoinPoint point) {
return around(point, null);
}
#Around("condition1() && condition2() && condition3() && args(request)")
public Object around(ProceedingJoinPoint point, Object request) {
...
result = (Result) point.proceed();
...
}
Will this result in point.proceed() being called twice (having the actual method called twice) if both of these advices match?
Your approach is highly problematic because you manually call one advice from another one. This is not how AOP should be applied. Please let AspectJ decide which advices to execute based on their respective pointcuts. The way you delegate from one advice to another you could even call an advice which would not match by itself. Example in plain AspectJ without Spring (works the same in Spring AOP, though):
Java driver application:
package de.scrum_master.app;
public class Application {
private static void doSomething() {
System.out.println("Doing something");
}
public static void main(String[] args) {
doSomething();
}
}
Aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class MyBogusAspect {
#Around("execution(* doSomething(..))")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println("matching advice called on joinpoint " + thisJoinPoint);
return nonMatchingAdvice(thisJoinPoint);
}
#Around("execution(* doSomethingElse(..))")
public Object nonMatchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println("non-matching advice called on joinpoint " + thisJoinPoint);
return thisJoinPoint.proceed();
}
}
Console log:
matching advice called on joinpoint execution(void de.scrum_master.app.Application.doSomething())
non-matching advice called on joinpoint execution(void de.scrum_master.app.Application.doSomething())
Doing something
Can you see how unhealthy your approach is? An advice which otherwise would not match is called by a matching one. This yields some really unexpected behaviour IMO. Please don't do it!!!
Now as for your original question about multiple matching advice, this is how you should do it:
Modified aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class MyBetterAspect {
#Around("execution(* doSomething(..))")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println(">>> matching advice on " + thisJoinPoint);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
#Around("execution(* doSomething(..))")
public Object anotherMatchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println(">>> another matching advice on " + thisJoinPoint);
Object result = thisJoinPoint.proceed();
System.out.println("<<< another matching advice on " + thisJoinPoint);
return result;
}
}
New console log:
>>> matching advice on execution(void de.scrum_master.app.Application.doSomething())
>>> another matching advice on execution(void de.scrum_master.app.Application.doSomething())
Doing something
<<< another matching advice on execution(void de.scrum_master.app.Application.doSomething())
<<< matching advice on execution(void de.scrum_master.app.Application.doSomething())
As you can see, AspectJ or Spring AOP wrap multiple matching advice like onion skins around joinpoints and only the innermost proceed() calls the actual joinpoint while the outer layers call the inner ones, making sure that each joinpoint is executed only once. There is no need for you trying to be smarter than the AOP framework, possibly causing damage (see my first example).
One more thing: If multiple aspects have matching pointcuts, you can influence their order of execution via #DeclarePrecedence in AspectJ, but within a single aspect you have no influence on the execution order or at least you should not rely on it. In Spring AOP you can use the #Order annotation in order to determine aspect precedence, but the order is also undefined for multiple advice from the same aspect, see also the Spring manual.
Update 2016-02-28, 18:30 CET, after some discussion in comments:
Okay, we extend the driver class a little bit so we can test some more:
package de.scrum_master.app;
public class Application {
private static void doSomething() {
System.out.println("Doing something");
}
private static String doSomethingElse(String text) {
System.out.println("Doing something else");
return text;
}
private static int doAnotherThing(int i, int j, int k) {
System.out.println("Doing another thing");
return (i + j) * k;
}
public static void main(String[] args) {
doSomething();
doSomethingElse("foo");
doAnotherThing(11, 22, 33);
}
}
Now, binding the first parameter in AspectJ is as easy as args(request, ..) which works for one or more parameters. The only exception is zero parameters, in which case the pointcut would not fire. So either I end up with something similar to what you did:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class BoundFirstParameterAspect {
#Pointcut("execution(* do*(..))")
public static void myPointcut() {}
#Around("myPointcut()")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
return anotherMatchingAdvice(thisJoinPoint, null);
}
#Around("myPointcut() && args(request, ..)")
public Object anotherMatchingAdvice(ProceedingJoinPoint thisJoinPoint, Object request) {
System.out.println(">>> another matching advice on " + thisJoinPoint);
Object result = thisJoinPoint.proceed();
System.out.println("<<< another matching advice on " + thisJoinPoint);
return result;
}
}
Which is makes the same advice fire twice and thus causes an overhead, even though the original method is only called once, but you can see the overhead in the log:
>>> another matching advice on execution(void de.scrum_master.app.Application.doSomething())
Doing something
<<< another matching advice on execution(void de.scrum_master.app.Application.doSomething())
>>> another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
>>> another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
Doing something else
<<< another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
<<< another matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
>>> another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
>>> another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
Doing another thing
<<< another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
<<< another matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
You can easily recognise how double advices are fired for each joinpoint.
Alternatively, you can bind the parameter during runtime, which is not very elegant and incurs a little runtime penalty, but works perfectly well:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class BoundFirstParameterAspect {
#Pointcut("execution(* do*(..))")
public static void myPointcut() {}
#Around("myPointcut()")
public Object matchingAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println(">>> matching advice on " + thisJoinPoint);
Object[] args = thisJoinPoint.getArgs();
Object request = args.length > 0 ? args[0] : null;
System.out.println("First parameter = " + request);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
}
This avoids double advice execution as well as code duplication and yields the following console output:
>>> matching advice on execution(void de.scrum_master.app.Application.doSomething())
First parameter = null
Doing something
<<< matching advice on execution(void de.scrum_master.app.Application.doSomething())
>>> matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
First parameter = foo
Doing something else
<<< matching advice on execution(String de.scrum_master.app.Application.doSomethingElse(String))
>>> matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
First parameter = 11
Doing another thing
<<< matching advice on execution(int de.scrum_master.app.Application.doAnotherThing(int, int, int))
Last, but not least, you can have two slightly different pointcuts - one with empty args() and one with args(request, ..) - both of which can delegate parameter handling, logging and exception handling to a helper method in order to avoid duplication, as I said in one of my comments:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class BoundFirstParameterAspect {
#Pointcut("execution(* do*(..))")
public static void myPointcut() {}
#Around("myPointcut() && args()")
public Object myAdvice(ProceedingJoinPoint thisJoinPoint) {
return myAdviceHelper(thisJoinPoint, null);
}
#Around("myPointcut() && args(request, ..)")
public Object myAdviceWithParams(ProceedingJoinPoint thisJoinPoint, Object request) {
return myAdviceHelper(thisJoinPoint, request);
}
private Object myAdviceHelper(ProceedingJoinPoint thisJoinPoint, Object request) {
System.out.println(">>> matching advice on " + thisJoinPoint);
System.out.println("First parameter = " + request);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
}
The console log should be exactly the same as the previous one.
Update 2:
Well, I just realised that the empty args() trick would also apply to your original idea and avoid double execution as well as the helper method:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class BoundFirstParameterAspect {
#Pointcut("execution(* do*(..))")
public static void myPointcut() {}
#Around("myPointcut() && args()")
public Object myAdvice(ProceedingJoinPoint thisJoinPoint) {
return myAdviceWithParams(thisJoinPoint, null);
}
#Around("myPointcut() && args(request, ..)")
public Object myAdviceWithParams(ProceedingJoinPoint thisJoinPoint, Object request) {
System.out.println(">>> matching advice on " + thisJoinPoint);
System.out.println("First parameter = " + request);
Object result = thisJoinPoint.proceed();
System.out.println("<<< matching advice on " + thisJoinPoint);
return result;
}
}
This is acceptable as well as elegant because it does not generate byte code twice per joinpoint. The two pointcuts are mutually exclusive, so this is a good thing to do. I recommend this solution.
Please see kriegaex's answer for more details. Leaving this here for completeness.
I ended up implementing a dummy project to test this. The answer is that the method will only be executed once. I've implemented the following:
Aspects.java:
package base;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class Aspects {
#Pointcut("#annotation(java.lang.Deprecated)")
public void hasErrorResponseMethod() {
}
#Around("hasErrorResponseMethod()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("In CHILD advice.");
return around(point, null);
}
#Around("hasErrorResponseMethod() && args(request)")
public Object around(ProceedingJoinPoint point, Object request) throws Throwable {
System.out.println("In PARENT advice with request " + request);
return point.proceed();
}
}
Methods.java:
package base;
public class Methods {
private static int COUNT = 1;
#Deprecated
public int method1(String param) {
System.out.println(">>> In method1! Param: " + param);
return COUNT++;
}
}
applicationContext.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<context:component-scan base-package="base" annotation-config="true" />
<aop:aspectj-autoproxy />
<bean id="logAspect" class="base.Aspects"/>
<bean id="methods" class="base.Methods"/>
<bean id="main" class="base.Main"/>
</beans>
Main.java:
package base;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Methods methods = (Methods) context.getBean("methods");
System.out.println("<<< Result: " + Methods.method1("REQUEST_VALUE"));
}
}
The output is the following:
In PARENT advice with request REQUEST_VALUE
In CHILD advice.
In PARENT advice with request null
>>> In method1! Param: REQUEST_VALUE
<<< Result: 1
As you can see, the method is only called once, by the what-appears-to-be-more-particular advice. It would still be great to know how AspectJ decides which one of the two will call it.

How to correctly use Spring AOP to select the execution of a method annotated with a specific annotation?

I am studying Spring AOP and I have the following doubt related to a quetion found on my study material.
So consider the following pointcut: execution(#com.myapp.MyCustomAnnotation void *(..)). What exactly means?
It give me the following answer that it seems to me a litle strange (using my knoledge on how AOP works). It say that:
This will select joint points representing voiud method that are
annotated by #com.myapp.MyCustomAnnotation annotation.
Ok so what it means that using AOP I can specify when it is performed a specific method that is annotated with a specific annotation? Is it right or am I missing something?
So, if the previous assertion is correct, it means that (for example) can I also specify something like: "when it is performed a controller method annotated by #RequestMapping("/listAccounts")? (that means, when the controller handle the HttpRequest towards the /listAccounts resource doing something like:
execution(#RequestMapping("/listAccounts") * *(..))
Can I do something like this or not?
Spring is using the AspectJ Pointcut Expression Language (https://eclipse.org/aspectj/doc/next/adk15notebook/annotations-pointcuts-and-advice.html)
applying your pointcut expression means intercepting all method-calls with any name and parameter list having a 'void' return type as long as the method is annotated with #com.myapp.MyCustomAnnotation.
It is not possible to match Join-Points using Annotation-Parameters though, so your second Pointcut-Expression is invalid.
You can't explicitly specify the arguments to the annotation in the pointcut like that. Rather, you could set a pointcut to capture all methods with #RequestMapping annotation, then retrieve the annotation from the method and check that the value matches the endpoint. For example:
#PointCut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}
#Before("requestMapping()")
public void handleRequestMapping(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String mapping = method.getAnnotation(RequestMapping.class).value()[0];
if (mapping.equals("/listAccounts") {
// do something
}
}
André R.'s answer is incorrect because it is possible to limit annotation matching to parameter values, but there is a limitation: It only works with simple types like strings, integers and classes, not arrays of strings or so. But in your specific example the Spring annotation #RequestMapping's value type is String[], see Javadoc. Let us assume you have this situation:
Annotation:
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(value={ElementType.METHOD,ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
int value();
String name() default "";
Class<?> type() default Object.class;
}
Driver application:
Here we have an application with several methods annotated by the Spring annotation #RequestMapping and by our home-made annotation #MyAnnotation:
package de.scrum_master.app;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
public class Application {
#RequestMapping("/listAccounts")
#MyAnnotation(11)
public void doSomething() {}
public void doSomethingElse() {}
#RequestMapping(value = {"/listAccounts","/another/method"}, name = "Newton")
#MyAnnotation(value = 22, type = String.class)
public void foo() {}
#RequestMapping(value = "/listAccounts", method = RequestMethod.POST, name = "Einstein")
#MyAnnotation(value = 11, name = "John Doe", type = String.class)
public void bar() {}
#RequestMapping(value = "/listCustomers", method = RequestMethod.GET, name = "Einstein")
#MyAnnotation(value = 22, name = "Jane Doe")
public void zot() {}
#RequestMapping(value = "/listAccounts", produces = {"application/json","application/xml"}, consumes = "text/html", name = "Newton")
public void baz() {}
public static void main(String[] args) {
Application application = new Application();
application.doSomething();
application.doSomethingElse();
application.foo();
application.bar();
application.zot();
application.baz();
}
}
Annotation matching aspect:
package de.scrum_master.aspect;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.web.bind.annotation.RequestMapping;
import de.scrum_master.app.MyAnnotation;
#Aspect
public class AnnotationParameterMatcher {
// Match *all* methods annotated by #RequestMapping
#Before("execution(* *(..)) && #annotation(requestMapping)")
public void logRequestMapping(JoinPoint thisJoinPoint, RequestMapping requestMapping) {
// Print joinpoint and annotation
System.out.println(thisJoinPoint + " -> " + requestMapping);
}
// Match *all* methods annotated by #RequestMapping
#Before("execution(* *(..)) && #annotation(requestMapping)")
public void logMatchingValue(JoinPoint thisJoinPoint, RequestMapping requestMapping) {
// Only print if value array contains "/listAccounts"
if (Arrays.asList(requestMapping.value()).contains("/listAccounts"))
System.out.println(" value contains '/listAccounts'");
}
// Match *all* methods annotated by #RequestMapping and bind 'name' parameter
#Before("execution(* *(..)) && #annotation(RequestMapping(name))")
public void logName(JoinPoint thisJoinPoint, String name) {
System.out.println(" name = '" + name + "'");
}
// Match methods annotated by #MyAnnotation with value=11
#Before("execution(#MyAnnotation(value=11) * *(..))")
public void logName(JoinPoint thisJoinPoint) {
System.out.println(" #MyAnnotation(value=11) detected");
}
// Match methods annotated by #MyAnnotation with 3 specific parameter values
#Before("execution(#MyAnnotation(value=11, name=\"John Doe\", type=String.class) * *(..)) && #annotation(myAnnotation)")
public void logName(JoinPoint thisJoinPoint, MyAnnotation myAnnotation) {
System.out.println(" " + myAnnotation);
}
}
Console output:
execution(void de.scrum_master.app.Application.doSomething()) -> #org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[], name=, produces=[], params=[], value=[/listAccounts], consumes=[])
value contains '/listAccounts'
name = ''
#MyAnnotation(value=11) detected
execution(void de.scrum_master.app.Application.foo()) -> #org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[], name=Newton, produces=[], params=[], value=[/listAccounts, /another/method], consumes=[])
value contains '/listAccounts'
name = 'Newton'
execution(void de.scrum_master.app.Application.bar()) -> #org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[POST], name=Einstein, produces=[], params=[], value=[/listAccounts], consumes=[])
value contains '/listAccounts'
name = 'Einstein'
#MyAnnotation(value=11) detected
#de.scrum_master.app.MyAnnotation(name=John Doe, type=class java.lang.String, value=11)
execution(void de.scrum_master.app.Application.zot()) -> #org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[GET], name=Einstein, produces=[], params=[], value=[/listCustomers], consumes=[])
name = 'Einstein'
execution(void de.scrum_master.app.Application.baz()) -> #org.springframework.web.bind.annotation.RequestMapping(headers=[], method=[], name=Newton, produces=[application/json, application/xml], params=[], value=[/listAccounts], consumes=[text/html])
value contains '/listAccounts'
name = 'Newton'

Categories