Difference between @target and @within (Spring AOP)

10,606

Solution 1

You are noticing no difference because Spring AOP, while using AspectJ syntax, actually only emulates a limited subset of its functionality. Because Spring AOP is based on dynamic proxies, it only provides interception of public, non-static method execution. (When using CGLIB proxies, you can also intercept package-scoped and protected methods.) AspectJ however can also intercept method calls (not just executions), member field access (both static and non-static), constructor call/execution, static class initialisation and a few more.

So let us construct a very simple AspectJ sample:

Marker annotation:

package de.scrum_master.app;

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

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}

Driver application:

package de.scrum_master.app;

@MyAnnotation
public class Application {
  private int nonStaticMember;
  private static int staticMember;

  public void doSomething() {
    System.out.println("Doing something");
    nonStaticMember = 11;
  }

  public void doSomethingElse() {
    System.out.println("Doing something else");
    staticMember = 22;
  }

  public static void main(String[] args) {
    Application application = new Application();
    application.doSomething();
    application.doSomethingElse();
  }
}

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 MyAspect {
  @Before("@within(de.scrum_master.app.MyAnnotation) && execution(public !static * *(..))")
  public void adviceAtWithin(JoinPoint thisJoinPoint) {
    System.out.println("[@within] " + thisJoinPoint);
  }

  @Before("@target(de.scrum_master.app.MyAnnotation) && execution(public !static * *(..))")
  public void adviceAtTarget(JoinPoint thisJoinPoint) {
    System.out.println("[@target] " + thisJoinPoint);
  }
}

Please note that I am emulating Spring AOP behaviour here by adding && execution(public !static * *(..)) to both pointcuts.

Console log:

[@within] execution(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
Doing something
[@within] execution(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
Doing something else

No surprise here. This is exactly what you would also see in Spring AOP. Now if you remove the && execution(public !static * *(..)) part from both pointcuts, in Spring AOP the output is still the same, but in AspectJ (e.g. also if you activate AspectJ LTW in Spring) it changes to:

[@within] staticinitialization(de.scrum_master.app.Application.<clinit>)
[@within] execution(void de.scrum_master.app.Application.main(String[]))
[@within] call(de.scrum_master.app.Application())
[@within] preinitialization(de.scrum_master.app.Application())
[@within] initialization(de.scrum_master.app.Application())
[@target] initialization(de.scrum_master.app.Application())
[@within] execution(de.scrum_master.app.Application())
[@target] execution(de.scrum_master.app.Application())
[@within] call(void de.scrum_master.app.Application.doSomething())
[@target] call(void de.scrum_master.app.Application.doSomething())
[@within] execution(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something
[@within] set(int de.scrum_master.app.Application.nonStaticMember)
[@target] set(int de.scrum_master.app.Application.nonStaticMember)
[@within] call(void de.scrum_master.app.Application.doSomethingElse())
[@target] call(void de.scrum_master.app.Application.doSomethingElse())
[@within] execution(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something else
[@within] set(int de.scrum_master.app.Application.staticMember)

When looking at this in detail you see that a lot more @within() joinpoints get intercepted, but also a few more @target() ones, e.g. the call() joinpoints mentioned before, but also set() for non-static fields and object initialization() happening before constructor execution.

When only looking at @target() we see this:

[@target] initialization(de.scrum_master.app.Application())
[@target] execution(de.scrum_master.app.Application())
[@target] call(void de.scrum_master.app.Application.doSomething())
[@target] execution(void de.scrum_master.app.Application.doSomething())
Doing something
[@target] set(int de.scrum_master.app.Application.nonStaticMember)
[@target] call(void de.scrum_master.app.Application.doSomethingElse())
[@target] execution(void de.scrum_master.app.Application.doSomethingElse())
Doing something else

For each of these aspect output lines we also see a corresponding @within() match. Now let's concentrate on what is not the same, filtering the output for differences:

[@within] staticinitialization(de.scrum_master.app.Application.<clinit>)
[@within] execution(void de.scrum_master.app.Application.main(String[]))
[@within] call(de.scrum_master.app.Application())
[@within] preinitialization(de.scrum_master.app.Application())
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something
[@within] get(PrintStream java.lang.System.out)
[@within] call(void java.io.PrintStream.println(String))
Doing something else
[@within] set(int de.scrum_master.app.Application.staticMember)

Here you see, in oder of appearance

  • static class initialisation,
  • static method execution,
  • constructor call (not execution yet!),
  • constructed object pre-initialisation,
  • member variable access in another class (System.out),
  • calling a method from another class (PrintStream.println(String)),
  • setting a static class member.

What do all of those pointcuts have in common? There is no target object, because either we are talking about static methods or members, static class initialisation, object pre-initialisation (no this defined yet) or calling/accessing stuff from other classes not bearing the annotation we are targeting here.

So you see that in AspectJ there are significant differences between the two pointcuts, in Spring AOP they are just not noticeable because of its limitations.

My advice for you is to use @target() if your intention is to intercept non-static behaviour within a target object instance. It will make switching to AspectJ easier if you ever decide to activate the AspectJ mode in Spring or even port some code to a non-Spring, aspect-enabled application.

Solution 2

The informations you have quoted are correct, however only @target pointcut designators require annotations with RUNTIME retention, while @within only requires CLASS retention.

Let's consider the following two simple annotations:

ClassRetAnnotation.java

package mypackage;

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

@Retention(RetentionPolicy.CLASS)
public @interface ClassRetAnnotation {}

RuntimeRetAnnotation.java

package mypackage;

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

@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeRetAnnotation {}

Now, if you define an aspect like the following one, there will be no exception at runtime:

@Component
@Aspect
public class MyAspect {

    @Before("@within(mypackage.ClassRetAnnotation)")
    public void within() { System.out.println("within"); }

    @Before("@target(mypackage.RuntimeRetAnnotation)")
    public void target() { System.out.println("target"); }
}

I hope this example helped to clarify the subtle difference you pointed.

Spring reference: https://docs.spring.io/spring/docs/5.0.x/spring-framework-reference/core.html#aop-pointcuts

Share:
10,606
Admin
Author by

Admin

Updated on June 05, 2022

Comments

  • Admin
    Admin almost 2 years

    Spring manual says:

    any join point (method execution only in Spring AOP) where the target object has an @Transactional annotation: @target(org.springframework.transaction.annotation .Transactional)

    any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation: @within(org.springframework.transaction.annotation .Transactional)

    But I not see any difference between them!

    I tried to Google it:

    One difference between the two is that @within() is matched statically, requiring the corresponding annotation type to have only the CLASS retention. Whereas, @target() is matched at runtime, requiring the same to have the RUNTIME retention. Other than that, within the context of Spring, here is no difference between the join points selected by two.

    So I tried to add custom annotation with CLASS retention, but Spring throw an Exception (because annotation must have RUNTIME retention)

  • kriegaex
    kriegaex almost 6 years
    I am failing to see how this answers the OP's question.
  • Robert Hume
    Robert Hume almost 6 years
    @kriegaex OP pointed out that Spring throws an Exception because annotation must have RUNTIME retention; I wanted to demonstrate that it is true only for @target poincut designator. I like your detailed answer very much, anyway I noticed that the context of the question is clearly limited to Spring AOP, so my answer is limited to the issue reported by the OP. Cheers.
  • kriegaex
    kriegaex almost 6 years
    You are right concerning the scope of the question and your answer. But because Spring AOP loans both pointcut descriptors from AspectJ and they behave identically (the exception being the slight difference in annotation retention), I think it was indeed necessary to expand the scope to get the big picture first before zeroing in on what's left of that big picture in Spring AOP. My final advice (no pun intended there) also contains a hint about why knowing all of this could be important to Spring AOP users: Many of them migrate to AspectJ later in order to overcome Spring AOP's limitations.
  • kriegaex
    kriegaex almost 6 years
    (continued) After they migrate they notice that aspects behave differently, but because they do not understand why and how easy it is to tame AspectJ aspects they give up quickly, reverting back to Spring AOP, using horrible workarounds for things which can be done beautifully and in simple ways with AspectJ.
  • Robert Hume
    Robert Hume almost 6 years
    @kriegaex I agree, and sure, they behave identically in Spring AOP! Thank you again, I just left my two cents with my answer.