Spring AspectJ, pointcut before method execution where method OR class is annotated - spring

I'm trying to get the value of an annotation via Spring Aop AspectJ-style, where the annotation can be on the class OR the method. I tried a lot of different things, but I can only get it to work when the annotation is on the method. I'd really like to annotate ONCE on the class - but advice all the methods of the class - and access the value of the class annotation in the advice. Here's where I've ended up:
Annotation:
#Inherited
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
String value() default "";
}
Aspect:
#Aspect
public class MyAspect {
#Pointcut("execution(#com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
public void atExecution() { }
#Before("atExecution() && #annotation(myAnnotation)")
public void myAdvice(JoinPoint joinPoint, MyAnnotation myAnnotation) {
...
}
}
Any thoughts? Thanks.

Short answer
While you can formulate a pointcut that will match both directly annotated methods and methods of annotated types at the same time, you cannot make a pointcut and/or advice where you bind the value of the annotation (i.e. use the annotation value in the advice code).
#Aspect
public class MyAspect {
#Pointcut("execution(#com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
public void atExecutionOfAnnotatedMethod() {}
#Pointcut("execution(* (#com.myco.MyAnnotation com.myco.somepackage..*).*(..))")
public void atExecutionOfMethodsOfAnnotatedClass() {}
#Before("atExecutionOfAnnotatedMethod() && #annotation(myAnnotation)")
public void myAdviceForMethodAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println("myAdviceForMethodAnnotation: " + myAnnotation.value());
}
#Before("atExecutionOfMethodsOfAnnotatedClass() && #this(myAnnotation)")
public void myAdviceForTypeAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println("myAdviceForTypeAnnotation: " + myAnnotation.value());
}
// /* the following pointcut will result in "inconsistent binding" errors */
// #Pointcut("(atExecutionOfAnnotatedMethod() && #annotation(myMethodAnnotation)) || (atExecutionOfMethodsOfAnnotatedClass() && #this(myTypeAnnotation))")
// public void combinedPointcut(MyAnnotation myMethodAnnotation, MyAnnotation myTypeAnnotation) {}
}
Some detail
To combine the two separate pointcuts (atExecutionOfAnnotatedMethod and atExecutionOfMethodsOfAnnotatedClass) we would have to use the OR (||) construct. Since the OR construct doesn't guarantee that either of the two annotation bindings will be present at advice execution, they will both result in a compile error (inconsistent binding).
You can still handle both cases in separate advices, you may also delegate the actual advice code to a common method to avoid duplication. In that case you'll need to take care of the case where both the type and the method is annotated with #MyAnnotation because that would match both pointcuts and would result in your method doubly advised by both advices, hence your common advice handling code will execute twice.
Combining the two
If you need to combine the two cases while defending against doubly advising the target code, you need to set up a precedence between the method level annotation and the class level annotation. Based on the principle of specificity, I'd suggest to go on the route where the method level annotation takes precedence over the class level one. Your aspect would look like this:
#Aspect
public class MyCombinedAspect {
#Pointcut("execution(#com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
public void atExecutionOfAnnotatedMethod() {}
#Pointcut("execution(* (#com.myco.MyAnnotation com.myco.somepackage..*).*(..))")
public void atExecutionOfMethodsOfAnnotatedClass() {}
#Before("atExecutionOfAnnotatedMethod() && !atExecutionOfMethodsOfAnnotatedClass() && #annotation(myAnnotation)")
public void myAdviceForMethodAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
handleBeforeExecution(joinPoint, myAnnotation);
}
#Before("atExecutionOfMethodsOfAnnotatedClass() && !atExecutionOfAnnotatedMethod() && #this(myAnnotation)")
public void myAdviceForTypeAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
handleBeforeExecution(joinPoint, myAnnotation);
}
#Before("atExecutionOfMethodsOfAnnotatedClass() && atExecutionOfAnnotatedMethod() && #annotation(myMethodAnnotation)")
public void myAdviceForDoublyAnnotated(JoinPoint joinPoint, MyAnnotation myMethodAnnotation) {
handleBeforeExecution(joinPoint, myMethodAnnotation);
}
protected void handleBeforeExecution(JoinPoint joinPoint, MyAnnotation myAnnotation) {
System.out.println(myAnnotation.value());
}

Related

advice is not executing if the annotations contains parameters

I'm trying to get some advice to execute.
When I use annotation without parameters its do execute but when the annotation includes parameters it's not.
#Aspect
class a{
#Pointcut("execution(#com.annotations.AnnotationName* *(..))")
void someMethod() {}
#Around("someMethod()")
public Object aroundSomeMethod(ProceedingJoinPoint pjp) throws Throwable
{
// some code
}
}
Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface AnnotationName
{
public String someString();
public boolean someBoolean();
}
The use of the annotation:
#AnnotationName(
someString= "string",
someBoolean = false
)
private void mycode()
{//code }
Following aspect code would advice a target method annotated with #AnnotationName
#Component
#Aspect
public class SomeMethodAspect {
#Pointcut("#annotation(annotationName) && within(so.qn69016852..*)")
private void someMethod(AnnotationName annotationName) {}
#Around("someMethod(annotationName)")
public Object aroundSomeMethod(ProceedingJoinPoint pjp,AnnotationName annotationName) throws Throwable
{
System.out.println(annotationName.someString());
System.out.println(annotationName.someBoolean());
return pjp.proceed();
}
}
Couple of corrections/observations .
Spring AOP cannot advice a private method of a Spring bean. The mycode() method should be in a bean and ideally public. ( Refer )
The Aspect should also be a spring bean. This can be achieved by annotating the aspect with #Component
Remember to limit the scope : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#writing-good-pointcuts
You may also go through this answer from #kriegaex to understand why an #annotation has a global scope.
Update :
The code shared by OP also works with modifying a typo ( a space between the AnnotationName and * in the pointcut expression ) . The observations shared earlier holds good here as well.
#Component
#Aspect
public class SomeMethodAspect {
#Pointcut("execution(#so.qn69016852.anno.AnnotationName * so.qn69016852..*.*(..))")
private void someMethod() {}
#Around("someMethod() && #annotation(annotationName)")
public Object aroundSomeMethod(ProceedingJoinPoint pjp,AnnotationName annotationName) throws Throwable
{
System.out.println(annotationName.someBoolean());
System.out.println(annotationName.someString());
return pjp.proceed();
}
}

How can I create a Pointcut or Around for extended classes with shared interface?

I have an abstract service class.
abstract class AbstractService<T> {
public void saveNew(T entity) {
}
}
And I have two more abstract classes extends AbstractService and implement a shared interface.
abstract class MoreAbstractService2<T extends Some2>
extends AbstractService<T>
implements SharedInterface {
}
abstract class MoreAbstractService3<T extends Some3>
extends AbstractService<T>
implements SharedInterface {
}
Now I want to validate the entity argument on these two extending services' saveNew(T) method.
How can I define a #Pointcut and (or) an #Around for following conditions?
extends the AbstractService class
implements the SharedInterface interface
you can use within as following:
within(com.somepackage.Super+)
where com.somepackage.Super is the fully qualified base class name and + means "all subclasses". Other pointcut is
execution(* com.somepackage.Super+.*(..))
R.G's solution has a few disadvantages:
The pointcut matches too many joinpoints.
Thus it needs reflection in order to filter out the unnecessary ones.
I am going to show you a stand-alone AspectJ solution (no Spring, I am not a Spring user), but the aspect would look just the same in Spring, you only need to make it a #Component or declare a #Bean factory in your configuration. But the same applies to all the classes you want to intercept, of course.
Because I prefer a full MCVE with all necessary dependency classes in order for you to be able to copy, compile and run it, and because I also added negative test cases (sub-classes only extending the abstract base class or only implementing the interface), this is a lot of code. So please bear with me:
Abstract classes, interface and helper classes:
package de.scrum_master.app;
public abstract class AbstractService<T> {
public void saveNew(T entity) {
System.out.println("Saving new entity " + entity);
}
}
package de.scrum_master.app;
public class Some2 {}
package de.scrum_master.app;
public class Some3 {}
package de.scrum_master.app;
public abstract class MoreAbstractService2<T extends Some2>
extends AbstractService<T>
implements SharedInterface {}
package de.scrum_master.app;
public abstract class MoreAbstractService3<T extends Some3>
extends AbstractService<T>
implements SharedInterface {}
package de.scrum_master.app;
public interface SharedInterface {
void doSomething();
}
Driver application (AspectJ + POJOs, not Spring):
This driver class contains some static inner classes subclassing the given base classes and/or implementing the shared interface. Two are used for positive tests (should be intercepted), two for negative tests (should not be intercepted). Each class also contains an additional method as another negative test case which should not be matched - better safe than sorry.
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
// Should be intercepted
InterceptMe1 interceptMe1 = new InterceptMe1();
interceptMe1.saveNew(new Some2());
interceptMe1.doSomething();
interceptMe1.additional();
printSeparator();
// Should be intercepted
InterceptMe2 interceptMe2 = new InterceptMe2();
interceptMe2.saveNew(new Some3());
interceptMe2.doSomething();
interceptMe2.additional();
printSeparator();
// Should NOT be intercepted
DontInterceptMe1 dontInterceptMe1 = new DontInterceptMe1();
dontInterceptMe1.saveNew(new Some2());
dontInterceptMe1.additional();
printSeparator();
// Should NOT be intercepted
DontInterceptMe2 dontInterceptMe2 = new DontInterceptMe2();
dontInterceptMe2.additional();
printSeparator();
}
private static void printSeparator() {
System.out.println("\n----------------------------------------\n");
}
static class InterceptMe1 extends MoreAbstractService2<Some2> {
#Override
public void doSomething() {
System.out.println("Doing something in MoreAbstractService2<Some2>");
}
public void additional() {
System.out.println("Additional method in MoreAbstractService2<Some2>");
}
}
static class InterceptMe2 extends MoreAbstractService3<Some3> {
#Override
public void doSomething() {
System.out.println("Doing something in MoreAbstractService3<Some3>");
}
public void additional() {
System.out.println("Additional method in MoreAbstractService3<Some3>");
}
}
static class DontInterceptMe1 extends AbstractService<Some2> {
public void additional() {
System.out.println("Additional method in AbstractService<Some2>");
}
}
static class DontInterceptMe2 implements SharedInterface {
#Override
public void doSomething() {
System.out.println("Doing something in SharedInterface"); }
public void additional() {
System.out.println("Additional method in SharedInterface");
}
}
}
Aspect:
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 EntityValidationAspect {
#Before(
"execution(* saveNew(*)) && " +
"args(entity) && " +
"target(de.scrum_master.app.SharedInterface) && " +
"target(de.scrum_master.app.AbstractService)"
)
public void validateEntity(JoinPoint thisJoinPoint, Object entity) {
System.out.println("-> Pre-save entity validation: " + entity);
}
}
As you can see, the aspect uses two target() pointcuts which must both match. It also specifically targets any saveNew method with a single argument saveNew(*), binding that argument as an advice method parameter via args().
For demonstration's sake I do not validate anything (I don't know how you want to do that) but just print the entity. Thus, a #Before advice is sufficient. If in case of negative validation you want to throw an exception, this advice type is also okay. If you need to do more, such as manipulate the entity's state or replace it before passing it on to the target method, call an alternative target method instead or none at all, return a specific result (in case of non-void methods, here not applicable), handle exceptions from the target method etc., you ought to use an #Around advice instead.
Console log:
-> Pre-save entity validation: de.scrum_master.app.Some2#28a418fc
Saving new entity de.scrum_master.app.Some2#28a418fc
Doing something in MoreAbstractService2<Some2>
Additional method in MoreAbstractService2<Some2>
----------------------------------------
-> Pre-save entity validation: de.scrum_master.app.Some3#5305068a
Saving new entity de.scrum_master.app.Some3#5305068a
Doing something in MoreAbstractService3<Some3>
Additional method in MoreAbstractService3<Some3>
----------------------------------------
Saving new entity de.scrum_master.app.Some2#1f32e575
Additional method in AbstractService<Some2>
----------------------------------------
Additional method in SharedInterface
----------------------------------------
Et voilĂ  - the aspect does exactly what you asked for, as far as I understand your requirement. :-) More specifically, it does not get triggered in the third case when saveNew is being called, but the class does not implement the interface.
Following code can be used for the validation mentioned.
The point cut is to intercept on the execution of a specific method for the subclasses of AbstractService and the code logic is to only validate if SharedInterface is a superinterface of the target bean.
The use of isAssignableFrom() is required as the interfaces proxied by the AOP proxy does not include SharedInterface. As per my understanding , a pointcut expression to match the second criteria will not be possible for the same reason and hence handled the requirement in the code logic.
Hope this helps
#Aspect
#Component
public class ValidationAspect {
#Pointcut("execution(* package.to.AbstractService+.saveNew(..))")
public void isAbstractServiceType() {
}
#Around("isAbstractServiceType() && args(entity) && target(bean)")
public void validateEntityArugments(ProceedingJoinPoint pjp, Object entity, Object bean) throws Throwable {
if (SharedInterface.class.isAssignableFrom(bean.getClass())) {
System.out.println(entity);
// ..validate
}
pjp.proceed();
}
}

Spring AOP introductions not working with && this(IntroducedInterface)

I have following aspect
#Aspect
public class AspectClass{
#DeclareParents(value="com.mac.model.*",defaultImpl=Impl.class)
public IntroduceInterface inter;
#Pointcut("execution(* com.mac.Employee.display(..))")
public void empPointcut(){}
#Before("empPointCut() && this(introduceInterface)")
public void BeforeAdvice(JoinPoint jp,IntroduceInterface inf){
inf.introMethod();
}
}
and I am trying to replicate code from spring docs which is like below:
#Aspect
public class UsageTracking {
#DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
#Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
but its not working its giving error :
IllegalArgumentException error at ::0 formal unbound in pointcut
Its a simple spring application .What could be reason its not working?
Name in here
#Before("empPointCut() && this(_name_in_here_)")
should be the same as in
public void BeforeAdvice(JoinPoint jp,IntroduceInterface _name_in_here_){
So this should work fine:
#Aspect
public class AspectClass{
#DeclareParents(value="com.mac.model.*",defaultImpl=Impl.class)
public IntroduceInterface inter;
#Pointcut("execution(* com.mac.Employee.display(..))")
public void empPointcut(){}
#Before("empPointCut() && this(inf)")
public void BeforeAdvice(JoinPoint jp,IntroduceInterface inf){
inf.introMethod();
}
}
Annotation object is available to the advice via the annotation parameter. So both names (method parameter name and annotation declared inside pointcut expression) should be same.
Please take a look at these two sections from spring documentation - Passing parameters to advice.
From Spring doc:
The proxy object ( this), target object ( target), and annotations ( #within, #target, #annotation, #args) can all be bound in a similar fashion. The following example shows how you could match the execution of methods annotated with an #Auditable annotation, and extract the audit code.
First the definition of the #Auditable annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Auditable {
AuditCode value();
}
And then the advice that matches the execution of #Auditable methods:
#Before("com.xyz.lib.Pointcuts.anyPublicMethod() && #annotation(**auditable**)")
public void audit(Auditable **auditable**) {
AuditCode code = auditable.value();
// ...
}

How to limit an aspect to a bean specified with a name?

I have the following interface:
public interface Performance {
public void perform();
}
Implemented by the following class:
#Component
public class Woodstock implements Performance {
public void perform() {
System.out.println("Woodstock.perform()");
}
}
The aspect I want to apply is this one:
#Aspect
public class Audience {
#Pointcut("execution(* concert.Performance.perform(..)) "
+ "&& bean('woodstock')"
)
public void performance() {}
#Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
The configuration file for the program is declaring the following beans:
#Bean
public Audience audience(){
return new Audience();
}
#Bean
public Performance woodstock(){
return new Woodstock();
}
And the test I'm running is this:
#Autowired
ApplicationContext context;
#Autowired
#Qualifier("woodstock")
Performance performance;
#Test
public void test(){
printBeanNames();
performance.perform();
}
Now, in the Audience class I specified in the annotation that I want to apply the aspect for the perform() method from the interface concert.Performance (execution(* concert.Performance.perform(..))) and I am explicitly limiting the application of the aspect to the bean whose name is "woodstock" (&& bean('woodstock')).
But when I run it I see that the aspect is not applied to the woodstock bean, although that bean is the one being used (it prints Woodstock.perform())
If instead of bean() I use !bean() then it does work, so it seems that it cannot consider that bean as the one named "woodstock".
Is there something wrong in the code above?
The special Spring AOP bean() pointcut works like bean(myBean) or bean(*Bean), there are no inline single or double quotes around the bean name according to the Spring AOP documentation (scroll down a little bit in this chapter).

Java method introspection with Spring AOP

I use spring-aop to make some treatments on my services methods. The methods on wich the treatment must occur are annotated with #History. Moreover, the annotation can have some params like "comment". Here is on exemple :
#Service
public class MyServiceImpl implements IMyService {
#Override
#History(comment = "my comment")
public void myMethod() {...}
}
public interface IMyService {
void create();
}
And, I have a aspect defined like this :
#Aspect
#Component
public class MyHistoryAspect {
#AfterReturning(pointcut = "execution(* my.service.package.*(..)) && #annotation(history)", returning = "result")
public void myTreatment(JoinPoint joinPoint, History history, Object result) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
...
}
}
Now, my problem : when I use reflection to find out the value of "comment" in my aspect, I can't find it. The reason : the method is the method signature of IMyService, not the method signature of MyServiceImpl. And if I put my annotation on the interface instead of the service, my Aspect is never reached.
Am I missing something or is it the normal behavior of spring aop ?
Thank you

Resources