一方面,在同一连接点上结合操作前、周围和后建议时,建议优先级不明确 [英] Unclear advice precedence when combining before-, around- and after-advice operating on same joinpoint in one aspect

查看:22
本文介绍了一方面,在同一连接点上结合操作前、周围和后建议时,建议优先级不明确的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑这个简单的 Java 代码

Please consider this simple Java-code

public class Application {

  public void m(int i) {
   System.out.println("M with argument " + i );
  }

  public static void main(String[] arg) {
   Application t = new Application();
   t.m(25);  
  }
}

我已经定义了以下方面来操作这个类:

I have defined the following Aspect to operate on this class:

public aspect Basics {
  public void output(String tag, Object o) {
    System.out.println(tag + ": " + o);
  }

  pointcut callM(int i): call(void Application.m(int)) && args(i);

  before(int i): callM(i) {
    output("before M", i);  
  }

  void around(int i): callM(i) {
      output("around-advice", i);
      proceed(1);
      output("after proceed", i);
  }

  after(int i): callM(i) {
    output("After M", i);  
  }
}

需要注意的是,around-advice 将传递给方法 M 的参数值更改为 1.运行此代码会生成以下输出:

It's important to note that the around-advice changes the value of the argument that's passed to method M to 1. Running this code generates the following output:

before M: 25
around-advice: 25
M with argument 1
after proceed: 25
After M: 25

除了最后一行之外,整个输出和我预期的一样.我希望最后一行打印1"而不是25".有人可以向我解释为什么会这样吗?

The entire output is as I would have expected, except for the last line. I expected the last line to print '1' instead of '25'. Can someone explain to me why this is the case?

在自己寻找答案时,我尝试更改建议的顺序,但这最终只会使混乱变得更大.如果我将 after-advice 首先放在代码中,然后是 before-advice,然后将 around-advice 放在最后(即 (1)after-(2)before-(3)around),我得到以下输出:

While looking for an answer myself, I tried to change the ordering of the advices but this only made the confusion bigger in the end. If I put the after-advice first in the code, followed by the before-advice and then put the around-advice last (i.e. (1)after-(2)before-(3)around), I got the following output:

before M: 25
around-advice: 25
M with argument 1
After M: 1
after proceed: 25

对我来说,这是唯一完全合理的输出.

To me, this is the only output that makes perfect sense.

但是,如果我先放后建议,然后是周围建议,而将前建议放在最后(即(1)after-(2)around-(3)before),我会得到以下输出如果我考虑到先前排序的输出,这对我来说也没什么意义:

However, if I put the after-advice first, followed by the around-advice while putting the before-advice last (i.e.(1)after-(2)around-(3)before), I get the following output which also makes little sense to me if I take the output of the previous orderings into account:

around-advice: 25
before M: 1
M with argument 1
After M: 1
after proceed: 25

在这种情况下,'i' 绑定到 1 时触发 before-advice.我猜测这是因为首先触发了 around-advice(因为排序),而 before-advice 实际上是由周围建议正文中对继续"的调用触发.但是,遵循此逻辑并不能解释在此问题中首先讨论的排序中生成的输出.

In this case, the before-advice gets triggered with 'i' bound to 1. My guess is that this is due to the around-advice being triggered first (because of the ordering) and that the before-advice is actually triggered by the call to 'proceed' in the body of the around-advice. Following this logic, however, doesn't explain the output that was generated in the ordering that was discussed first in this question.

最后,以这样一种方式改变顺序,我们首先有before-advice,然后是after-advice,然后是around-advice(即(1)before-(2)after-(3))around) 根据 Eclipse 的 AspectJ 插件无效,因为这会生成循环建议优先级".

Finally, changing the ordering in such a way that we first have the before-advice, followed by the after-advice, and then followed by the around-advice (i.e. (1)before-(2)after-(3)around) is not valid according to the AspectJ-plugin of Eclipse because this generates a 'circular advice precedence'.

有人可以解释一下同一方面内不同建议之间使用的优先级,以解释上述所有行为吗?

Can someone give me an explanation of the precedence being used between different advices within the same aspect that explains all the behaviour above?

我一直在阅读关于这个主题的这里但我认为解释是不确定的/与实现不匹配.它说

I have been reading on the subject here but I think the explanation is inconclusive/doesn't match the implementation. It says

一条around通知控制是否通过调用proceed来运行较低优先级的通知.继续调用将运行具有下一个优先级的建议,或者如果没有进一步的建议,则在连接点下进行计算.

A piece of around advice controls whether advice of lower precedence will run by calling proceed. The call to proceed will run the advice with next precedence, or the computation under the join point if there is no further advice.

如果我理解正确,这意味着在这个问题中首先讨论的输出(即 (1)before-(2)around-(3)after-ordering)应该在最后一行有1"而不是比25".

If I understand correctly, this means that the output that was discussed first in this question (i.e. (1)before-(2)around-(3)after-ordering) should have had '1' in the last line rather than '25'.

推荐答案

其实@XGouchet 的第一张草图并不完全正确,因为before 建议没有出现在around,但在 around 执行之前已经完成.让我使用带有花括号的伪代码表示法来表达这种词法范围":

Actually @XGouchet's first sketch is not quite correct because the before advice does not occur within the scope of around, but has finished before around is executed. Let me use pseudo code notation with curly braces expressing this kind of "lexical scope":

场景 A:词法排序 before ->周围 ->之后:

Scenario A: lexical ordering before -> around -> after:

before(25)
around(25 → 1) {
    joinpoint(1)
}
after(25)

场景 B:词法排序 after ->之前 ->周围:

Scenario B: lexical ordering after -> before -> around:

before(25)
around(25 → 1) {
    joinpoint(1)
    after(1)
}

场景 C:词法排序 after ->周围 ->之前:

Scenario C: lexical ordering after -> around -> before:

around(25 → 1) {
    before(1)
    joinpoint(1)
    after(1)
}

引用确定优先级段落来自 AspectJ 手册关于方面优先级的章节:

Quote from Determining precedence paragraph from the AspectJ manual's chapter on aspect precedence:

如果两条通知定义在同一个方面,那么有两种情况:

If the two pieces of advice are defined in the same aspect, then there are two cases:

  • 如果任一通知之后,那么在方面中后面出现的那个优先于前面出现的那个.立>
  • 否则,那么在方面中较早出现的那个优先于出现在后面的那个.
  • If either are after advice, then the one that appears later in the aspect has precedence over the one that appears earlier.
  • Otherwise, then the one that appears earlier in the aspect has precedence over the one that appears later.

现在还要记住方面的行为方式以及在某些情况下优先级的真正含义.还引用了来自AspectJ 手册关于方面优先级的章节:

Now also bear in mind how aspects behave and what precedence really means under certain circumstances. Also quoting the Effects of precedence paragraph from the AspectJ manual's chapter on aspect precedence:

在特定的连接点,通知按优先级排序.

At a particular join point, advice is ordered by precedence.

  • 一条around通知控制是否通过调用继续运行较低优先级的通知.继续调用将运行具有下一个优先级的建议,或者如果没有进一步的建议,则在连接点下进行计算.
  • 一段before通知可以通过抛出异常来阻止优先级较低的通知运行.但是,如果它正常返回,则下一个优先级的通知或连接点下的计算(如果没有进一步的通知)将运行.
  • 运行返回后通知将运行下一个优先级的通知,如果没有进一步的通知,则运行连接点下的计算.然后,如果该计算正常返回,则通知正文将运行.
  • 在抛出后运行通知将运行下一个优先级的通知,如果没有进一步的通知,则运行连接点下的计算.然后,如果该计算抛出适当类型的异常,则通知正文将运行.
  • Running after 通知将运行下一个优先级的通知,如果没有进一步的通知,则运行连接点下的计算.然后通知正文将运行.
  • A piece of around advice controls whether advice of lower precedence will run by calling proceed. The call to proceed will run the advice with next precedence, or the computation under the join point if there is no further advice.
  • A piece of before advice can prevent advice of lower precedence from running by throwing an exception. If it returns normally, however, then the advice of the next precedence, or the computation under the join point if there is no further advice, will run.
  • Running after returning advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation returned normally, the body of the advice will run.
  • Running after throwing advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation threw an exception of an appropriate type, the body of the advice will run.
  • Running after advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then the body of the advice will run.

简单的英语:

  • 一个环绕建议

  • 其他周围before较低优先级的建议以及
  • after更高优先级的建议.

为什么会这样?想象一下 around 通知是一对 beforeafter 通知(可以修改方法参数和返回值,可以处理异常,可以在 beforeafter 代码之间共享状态).因此,与单独的 beforeafter 通知相同的委托规则适用如下.

Why is that so? Imagine the around advice to be a pair of before and after advices on steroids (can modify method parameters and return value, can handle exceptions, can share state between before and after code). Consequently, the same delegation rules as for separate before and after advices apply as follows.

before 通知在委托或继续之前首先执行.

A before advice executes first before delegating or proceeding.

after 通知在执行之前首先委托.特别是(可能有点违反直觉),这意味着:

An after advice delegates first before executing. In particular (and maybe somewhat counter-intuitively), this means:

  • 如果 after 通知的优先级高于 around 通知,那么如果 around 通知在继续之前修改了任何方法参数,after 通知也受到修改的影响,因为 around 通知已经在 after 通知之前使用修改后的参数进入目标方法开始.

  • If an after advice has higher precedence than an around advice, then if the around advice modifies any method arguments before proceeding, the after advice is affected by the modification too, because the around advice already proceeded to the target method with the modified arguments before the after advice kicks in.

然而,如果较低优先级around通知修改了返回值,相同的更高优先级after (returning)通知不受影响,因为after (返回) 通知在 around 通知的后处理代码之前执行,因为它具有更高的优先级.

However, the same higher precedence after (returning) advice is unaffected if the lower precedence around advice modifies the return value because the after (returning) advice is executed before the around advice's post-proceed code due to its higher precedence.

相反,如果 after 通知的优先级低于 around 通知,那么如果 around 通知修改了任何方法参数在继续之前,after 通知不受修改的影响,因为它在 around 通知的上下文之外运行,没有被它包裹.

Conversely, if an after advice has lower precedence than an around advice, then if the around advice modifies any method arguments before proceeding, the after advice is unaffected by the modification because it runs outside the context of the around advice, it is not being wrapped by it.

然而,如果更高优先级的around通知修改返回值,同样的低优先级after (returning)通知会受到影响,因为它在后者之后运行已经跑完了.

However, the same lower precedence after (returning) advice is affected if the higher precedence around advice modifies the return value because it runs after the latter has finished running already.

现在这是一个扩展示例,说明我刚刚写的内容:

Now here is an extended example illustrating what I just wrote:

驱动程序应用:

package de.scrum_master.app;

public class Application {
    public void doSomething(int i) {
        System.out.println("Doing something with " + i);
    }

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

方面,变体 1:

package de.scrum_master.aspect;

import de.scrum_master.app.Application;

public aspect IntraAspectPrecedence {

    pointcut methodCall(int i) :
        call(void Application.doSomething(int)) && args(i);

    void around(int i): methodCall(i) {
        System.out.println("around1 (pre-proceed) -> " + i);
        proceed(11);
        System.out.println("around1 (post-proceed) -> " + i);
    }

    void around(int i): methodCall(i) {
        System.out.println("around2 (pre-proceed) -> " + i);
        proceed(22);
        System.out.println("around2 (post-proceed) -> " + i);
    }

    before(int i): methodCall(i) {
        System.out.println("before1 -> " + i);
    }

    before(int i): methodCall(i) {
        System.out.println("before2 -> " + i);
    }

    after(int i): methodCall(i) {
        System.out.println("after1 -> " + i);
    }

    after(int i): methodCall(i) {
        System.out.println("after2 -> " + i);
    }

}

伪代码,变体 1:

around1(99 → 11) {
    around2(11 → 22) {
        before1(22) {
            before2(22) {
                joinpoint(22) {}
            }
        }
    }
}
after2(99) {
    after1(99) {}
}

控制台输出,变体 1:

请注意after1"打印在after2"之前即使它具有较低的优先级,因为 after 首先委托,然后执行,如上所述.

Please note that "after1" is printed before "after2" even though it has lower precedence because after delegates first, then executes, as explained above.

around1 (pre-proceed) -> 99
around2 (pre-proceed) -> 11
before1 -> 22
before2 -> 22
Doing something with 22
around2 (post-proceed) -> 11
around1 (post-proceed) -> 99
after1 -> 99
after2 -> 99

方面,变体 2:

基本上和之前一样,只有第一个 after 通知具有最高优先级.

Basically it is the same as before, only the first after advice has the highest precedence.

package de.scrum_master.aspect;

import de.scrum_master.app.Application;

public aspect IntraAspectPrecedence {

    pointcut methodCall(int i) :
        call(void Application.doSomething(int)) && args(i);

    after(int i): methodCall(i) {
        System.out.println("after1 -> " + i);
    }

    void around(int i): methodCall(i) {
        System.out.println("around1 (pre-proceed) -> " + i);
        proceed(11);
        System.out.println("around1 (post-proceed) -> " + i);
    }

    void around(int i): methodCall(i) {
        System.out.println("around2 (pre-proceed) -> " + i);
        proceed(22);
        System.out.println("around2 (post-proceed) -> " + i);
    }

    before(int i): methodCall(i) {
        System.out.println("before1 -> " + i);
    }

    before(int i): methodCall(i) {
        System.out.println("before2 -> " + i);
    }

    after(int i): methodCall(i) {
        System.out.println("after2 -> " + i);
    }

}

伪代码,变体 2:

这一次,因为 after1 比两个 around 通知具有更高的优先级,它在 joinpoint 返回之后立即执行,因此在发生任何事情之前proceed() 在包装 around 通知之后.

This time, because after1 has higher precedence than both around advices, it is executed right after the joinpoint returns and thus before whatever happens after proceed() in the wrapping around advices.

(请注意:我对这个解释不满意,也许有更好的表达方式.也许答案可以改写,使用一个伪前/后建议对,而不是每个周围建议包装比喻.)

around1(99 → 11) {
    around2(11 → 22) {
        before1(22) {
            before2(22) {
                joinpoint(22) {}
            }
        }
        after1(22) {}
    }
}
after2(99) {}

控制台输出,变体 2:

around1 (pre-proceed) -> 99
around2 (pre-proceed) -> 11
before1 -> 22
before2 -> 22
Doing something with 22
after1 -> 22
around2 (post-proceed) -> 11
around1 (post-proceed) -> 99
after2 -> 99

这篇关于一方面,在同一连接点上结合操作前、周围和后建议时,建议优先级不明确的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆