通过 aspectj 拦截拦截函数内的所有函数调用

Intercept all function calls inside a intercepted function by aspectj

我是 AspectJ 和思考的新手我想要实现的是下面的示例:

测试class :

    public class Sample {
    Home home = new Home();
    Account account = new Account();
    AccountAuthentication accountAuthentication = new AccountAuthentication();

    @Test
    public void loginInvalidCredentials(){
    home.clickOnAccount();
    account.login("Admin", "secret");
    accountAuthentication.waitForPage();
    Assert.assertTrue(true);
    }
}

我想记录这样的输出:

packageName.Sample.loginInvalidCredentials 
    packageName.Home.clickOnAccount();
    packageName.Account.login(userName = "Admin", Password ="secret");
    packageName.AccountAuthentication.waitForPage();
    packageName.Assert.assertTrue(value= true);

我已经使用 AspectJ

访问了函数 packageName.Sample.loginInvalidCredentials 的名称
@Aspect
public class AspectClass {
    @Around("execution(* *(..)) && @annotation(org.testng.annotations.Test)")
    public void around(ProceedingJoinPoint point) throws Throwable {
        Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
        String methodName = method.getName();
        System.out.println("Aspect called for method "+ point.getSignature().getDeclaringType().name +"."+methodName);

        try { 
             //TODO intercept each function call inside the method without any custom anotation and get the value of parameters as well
              joinPoint.proceed();
             } 
    }
}

提前致谢。

我假设

  • 你使用完整的 AspectJ 而不是像 Spring AOP 这样的东西(因为那样的话答案不适用),
  • 想要递归地记录方法调用,但只是那些直接从你的 @Test 方法调用的方法。例如,如果 Account.login(..) 将在内部调用 Account.checkPassword(..),则不应记录。那也是可能的,但是解决方案看起来会有所不同。
  • 您只想记录测试执行,而不做任何其他事情,因此不需要 @Around 建议,@Before 就足够了。

虚拟应用程序 类:

package de.scrum_master.app;

public class Account {
  public void login(String user, String password) {
    checkPassword(password);
  }

  public void checkPassword(String password) {}
}
package de.scrum_master.app;

public class AccountAuthentication {
  public void waitForPage() {}
}
package de.scrum_master.app;

public class Home {
  public void clickOnAccount() {
    doSomethingElse();
  }

  public void doSomethingElse() {}
}

标记注释:

我自己创建了这个,因为我懒得用我通常不使用的 TestNG 设置项目。

package org.testng.annotations;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface Test {}

示例应用程序:

package de.scrum_master.app;

import org.testng.annotations.Test;

public class Sample {
  Home home = new Home();
  Account account = new Account();
  AccountAuthentication accountAuthentication = new AccountAuthentication();

  @Test
  public void loginInvalidCredentials() {
    home.clickOnAccount();
    account.login("Admin", "secret");
    accountAuthentication.waitForPage();
  }

  public static void main(String[] args) {
    new Sample().loginInvalidCredentials();
  }
}

看点:

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 TestExecutionLogger {
  @Before("execution(* *(..)) && @annotation(org.testng.annotations.Test)")
  public void logTest(JoinPoint joinPoint) {
    System.out.println(joinPoint);
  }

  @Before("call(* *(..)) && withincode(@org.testng.annotations.Test * *(..))")
  public void logTestActions(JoinPoint joinPoint) {
    System.out.println("  " + joinPoint);
  }
}

控制台日志:

execution(void de.scrum_master.app.Sample.loginInvalidCredentials())
  call(void de.scrum_master.app.Home.clickOnAccount())
  call(void de.scrum_master.app.Account.login(String, String))
  call(void de.scrum_master.app.AccountAuthentication.waitForPage())

当然,如果您真的认为需要从信息丰富的 AspectJ 日志输出中去除普通方法名称,您可以改进方面:

  @Before("execution(* *(..)) && @annotation(org.testng.annotations.Test)")
  public void logTest(JoinPoint joinPoint) {
    System.out.println(joinPoint.getSignature());
  }

  @Before("call(* *(..)) && withincode(@org.testng.annotations.Test * *(..))")
  public void logTestActions(JoinPoint joinPoint) {
    System.out.println("  " + joinPoint.getSignature());
  }
void de.scrum_master.app.Sample.loginInvalidCredentials()
  void de.scrum_master.app.Home.clickOnAccount()
  void de.scrum_master.app.Account.login(String, String)
  void de.scrum_master.app.AccountAuthentication.waitForPage()

如果您有意删除方法的 return 类型或像您的示例中那样包含实际参数值,您可以进一步优化它,这很容易通过 JoinPoint.getArgs() 实现,但我保留了由你决定。你的问题是关于切入点,而不是关于如何从连接点中提取哪些信息。