Difference between @target and @within (Spring AOP)

只愿长相守 提交于 2020-05-25 07:24:47

问题


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)


回答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.




回答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



来源:https://stackoverflow.com/questions/51124771/difference-between-target-and-within-spring-aop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!