英文:
2 advices colliding on the same fucntion
问题
嘿,
我开始学习 Aspectj,并且我已经建立了两个切面。这两个切面都有一个切点,匹配同一个函数,而且两个切面都有一个环绕通知,在这个切点上执行某些操作。
然而,只有一个通知会被“执行”,我不明白为什么。
让我给你展示:
第二个切面:
@Aspect
public class secondAspect {
@Pointcut("call( * main.*(..))")
public void pointCutThatMatchesEveryFunctionFromMain(){
}
@Around("pointCutThatMatchesEveryFunctionFromMain()")
public void adviceOnPointCutThatMatchesEveryFunctionFromMain(JoinPoint jp){
System.out.println("调用了一个方法");
System.out.println(jp.getTarget().toString());
}
}
第一个切面:
@Aspect
public class firstAspect {
@Pointcut("call( public * main.*(..)) && args(x,y)")
public void pointCutOnMainClassIntArgs(int x, int y) {
}
@Pointcut("call( public * main.*(..)) && args(x,y)")
public void pointCutOnMainClassFloatArgs(float x, float y) {
}
@Around("pointCutOnMainClassIntArgs(x,y)")
public void doSomethingOnThisPointCut(JoinPoint pjp, int x, int y) {
System.out.println(String.format("方法名是 %s,参数为 %d %d", pjp.getTarget().toString(), x, y));
}
@Around("pointCutOnMainClassFloatArgs(x,y)")
public void doSomethingOnThisPointCutWithFloatArgs(JoinPoint pjp, float x, float y) {
System.out.println(String.format("方法名是 %s,参数为 %f %f", pjp.getTarget().toString(), x, y));
}
}
要修改的类:
public class main {
public static void main(String[] args) {
main maine = new main();
maine.calculate(2,3);
maine.calculate(2.0f,5.0f);
}
public void calculate(int x, int y){
System.out.println(x+y);
}
public void calculate(float x, float y){
System.out.println(x+y);
}
}
只有 secondAspect
的通知被执行。我不明白为什么。
英文:
Hei,
I started learning Aspectj and I have built 2 aspects. Both aspects have a pointcut that match the same function, and both aspects have an around advice that will do something on that pointcut.
However only one advice will be "executed", and I do not understand why.
let me show you:
@Aspect
public class secondAspect {
@Pointcut("call( * main.*(..))")
public void pointCutThatMatchesEveryFunctionFromMain(){
}
@Around("pointCutThatMatchesEveryFunctionFromMain()")
public void adviceOnPointCutThatMatchesEveryFunctionFromMain(JoinPoint jp){
System.out.println("A method was called");
System.out.println(jp.getTarget().toString());
}
}
that is the second aspect
@Aspect
public class firstAspect {
@Pointcut("call( public * main.*(..)) && args(x,y)")
public void pointCutOnMainClassIntArgs(int x, int y) {
}
@Pointcut("call( public * main.*(..)) && args(x,y)")
public void pointCutOnMainClassFloatArgs(float x, float y) {
}
@Around("pointCutOnMainClassIntArgs(x,y)")
public void doSomethingOnThisPointCut(JoinPoint pjp, int x, int y) {
System.out.println(String.format("The method name is %s and was called with parameteres %d %d", pjp.getTarget().toString(), x, y));
}
@Around("pointCutOnMainClassFloatArgs(x,y)")
public void doSomethingOnThisPointCutWithFloatArgs(JoinPoint pjp, float x, float y) {
System.out.println(String.format("The method name is %s and was called with parameteres %f %f", pjp.getTarget().toString(), x, y));
}
}
the first aspect
public class main {
public static void main(String[] args) {
main maine = new main();
maine.calculate(2,3);
maine.calculate(2.0f,5.0f);
}
public void calculate(int x, int y){
System.out.println(x+y);
}
public void calculate(float x, float y){
System.out.println(x+y);
}
}
and this is the class that i want to modify.
Only the secondAspect's adivce is executed. And I don't get it why.
答案1
得分: 1
短答案是: 将你的建议类型从 @Around
更改为 @Before
,然后它就会生效,控制台输出将变为:
方法已调用
main@5f341870
方法名称是 main@5f341870,并且使用参数 2 3 进行了调用
方法名称是 main@5f341870,并且使用参数 2,000000 3,000000 进行了调用
5
方法已调用
main@5f341870
方法名称是 main@5f341870,并且使用参数 2,000000 5,000000 进行了调用
7.0
注意: 如果你检查上面的日志输出,你会注意到你的建议 doSomethingOnThisPointCutWithFloatArgs
也匹配了具有 int
参数的方法,这可能不是你的意图。你需要比 main.*(..)
更精确,最好在你的切入点中使用 main.*(int, int)
和 main.*(float, float)
。
长答案是: 你应该阅读一个 AspectJ 教程。例如,@Around
建议需要:
- 一个
ProceedingJoinPoint
方法参数,而不是简单的JoinPoint
。 - 显式调用连接点的
proceed()
方法,以实际调用拦截的目标方法。你没有这样做,这就是为什么只有一个随机方面(由 AspectJ 找到的第一个)被执行,而第二个则没有,因为你设计的建议代码从未继续执行到目标方法。 - 返回
Object
或更具体的非void
类型,如果你想匹配返回值不是void
的方法。你需要返回proceed()
的结果或你希望作为目标方法结果返回的其他内容。
一些建议:
- 请遵循 Java 编码准则,不要以小写字母开头命名类或切面。
- 尤其不要使用类名
main
,然后是方法main
,由于命名冲突还有一个局部变量maine
。我想这再丑陋不过了。 - 当你打印一个对象,比如
System.out.println(jp.getTarget().toString())
,toString()
是多余的,因为在打印对象时,这个方法将总是被隐式调用。 - 当你有多个切面定位相同的连接点并希望强制按特定顺序调用它们时,学习如何使用
@DeclarePrecedence
。 - 我建议摆脱单独的
@Pointcut
定义,并在内联中定义你的切入点,除非你想重用一个切入点。 - 如果你想知道在 AspectJ 中发生了什么,为什么不打印出完整的连接点,而不是只打印“方法已调用”?这有助于极大地理解发生了什么。
- 学习
call()
和execution()
之间的语义区别:前者拦截所有调用者(即方法调用的源),后者无论调用从何处发起,都拦截调用本身。在这里查看更多信息:这里,以及 AspectJ 手册中的更多信息。 - 尽量避免将你的类和切面放入默认包中。这不是良好的风格。此外,你会注意到 AspectJ 切入点对包名非常敏感。
- 而不是将
PrintStream.println
和String.format
结合在一起,直接使用PrintStream.printf
。
这个怎么样?
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
Application application = new Application();
application.calculate(2, 3);
application.calculate(2.0f, 5.0f);
}
public void calculate(int x, int y) {
System.out.println(x + y);
}
public void calculate(float x, float y) {
System.out.println(x + y);
}
}
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 FirstAspect {
@Before("execution(public !static * *(int, int)) && args(x, y) && target(targetInstance)")
public void publicNonStaticMethodsWith2IntParameters(JoinPoint joinPoint, int x, int y, Object targetInstance) {
System.out.printf("[FA] %s -> %s [%s, %s]%n", joinPoint, targetInstance, x, y);
}
@Before("execution(public !static * *(float, float)) && args(x, y) && target(targetInstance)")
public void publicNonStaticMethodsWith2FloatParameters(JoinPoint joinPoint, float x, float y, Object targetInstance) {
System.out.printf("[FA] %s -> %s [%s, %s]%n", joinPoint, targetInstance, x, y);
}
}
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;
@Aspect
public class SecondAspect {
@Before("execution(public !static * *(..)) && target(targetInstance)")
public void publicNonStaticMethods(JoinPoint joinPoint, Object targetInstance) {
System.out.printf("[SA] %s -> %s %s%n", joinPoint, targetInstance, Arrays.deepToString(joinPoint.getArgs()));
}
}
[FA] execution(void de.scrum_master.app.Application.calculate(int, int)) -> de.scrum_master.app.Application@6c3708b3 [2, 3]
[SA] execution(void de.scrum_master.app.Application.calculate(int, int)) -> de.scrum_master.app.Application@6c3708b3 [2, 3]
5
[SA] execution(void de.scrum_master.app.Application.calculate(float, float)) -> de
<details>
<summary>英文:</summary>
**The short answer is:** Change your advice type from `@Around` to `@Before`, then it works and the console output will turn into:
```none
A method was called
main@5f341870
The method name is main@5f341870 and was called with parameteres 2 3
The method name is main@5f341870 and was called with parameteres 2,000000 3,000000
5
A method was called
main@5f341870
The method name is main@5f341870 and was called with parameteres 2,000000 5,000000
7.0
Caveat: If you inspect the above log output, you will notice that your advice doSomethingOnThisPointCutWithFloatArgs
also matches the method with the int
parameters, which probably was not your intention. You need to be more precise than main.*(..)
and better use main.*(int, int)
vs. main.*(float, float)
in your pointcuts.
The long answer is: You should read an AspectJ tutorial. For example, an @Around
advice needs
- a
ProoceedingJoinPoint
method parameter instead of a simpleJoinPoint
, - to explicitly call the joinpoint's
proceed()
method in order to actually call the intercepted target method. You did not do that, which is the explanation which only one random aspect (the first one found by AspectJ) was executed, but not the second because you designed your advice code in such a way that you never proceeded to the target method. - to return
Object
or a more specific non-void
type if you want to match methods returning something other thanvoid
. You either need to return the result ofproceed()
or something else you wish to be returned as the result of the target method.
Some more suggestions:
- Please follow Java coding guidelines and do not start class or aspect names with lower-case letters.
- Specifically do not use a class name
main
, then a methodmain
and due to the naming clash a local variablemaine
. I guess it does not get much uglier than that. - When you print an object like
System.out.println(jp.getTarget().toString())
, thetoString()
is superfluous because this method will always be called implicitly when printing objects. - When you have multiple aspects targeting the same joinpoints and wish to enforce a specific order in which they are called, learn how to use
@DeclarePrecedence
. - I recommend to get rid of separate
@Pointcut
definitions and define your pointcuts inline unless you wish to re-use a pointcut. - If you want to know what is going on in AspectJ, why not print the full joinpoint instead of "a method was called"? It helps understand what is going on tremendously.
- Learn the semantic difference between
call()
andexecution()
: While the former intercepts all callers (i.e. the sources of method calls), the latter intercepts the calls themselves no matter where they originate from. See here and the AspectJ manual for more information. - Try to avoid putting your classes and aspects into the default package. This is not good style. Also you will notice that AspectJ pointcuts can be quite sensitive to package names.
- Instead of combining
PrintStream.println
andString.format
, just usePrintStream.printf
.
How about this?
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
Application application = new Application();
application.calculate(2, 3);
application.calculate(2.0f, 5.0f);
}
public void calculate(int x, int y) {
System.out.println(x + y);
}
public void calculate(float x, float y) {
System.out.println(x + y);
}
}
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 FirstAspect {
@Before("execution(public !static * *(int, int)) && args(x, y) && target(targetInstance)")
public void publicNonStaticMethodsWith2IntParameters(JoinPoint joinPoint, int x, int y, Object targetInstance) {
System.out.printf("[FA] %s -> %s [%s, %s]%n", joinPoint, targetInstance, x, y);
}
@Before("execution(public !static * *(float, float)) && args(x, y) && target(targetInstance)")
public void publicNonStaticMethodsWith2FloatParameters(JoinPoint joinPoint, float x, float y, Object targetInstance) {
System.out.printf("[FA] %s -> %s [%s, %s]%n", joinPoint, targetInstance, x, y);
}
}
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;
@Aspect
public class SecondAspect {
@Before("execution(public !static * *(..)) && target(targetInstance)")
public void publicNonStaticMethods(JoinPoint joinPoint, Object targetInstance) {
System.out.printf("[SA] %s -> %s %s%n", joinPoint, targetInstance, Arrays.deepToString(joinPoint.getArgs()));
}
}
[FA] execution(void de.scrum_master.app.Application.calculate(int, int)) -> de.scrum_master.app.Application@6c3708b3 [2, 3]
[SA] execution(void de.scrum_master.app.Application.calculate(int, int)) -> de.scrum_master.app.Application@6c3708b3 [2, 3]
5
[SA] execution(void de.scrum_master.app.Application.calculate(float, float)) -> de.scrum_master.app.Application@6c3708b3 [2.0, 5.0]
[FA] execution(void de.scrum_master.app.Application.calculate(float, float)) -> de.scrum_master.app.Application@6c3708b3 [2.0, 5.0]
7.0
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论