Maven + AspectJ LTW [错误] [AppClassloader] 和 Joinpoint 目标为空
Maven + AspectJ LTW [ERROR] [AppClassloader] and Joinpoint target null
我正在尝试使用 LoadTime-Weaving 使用 Maven 和 AspectJ 制作一个简单的应用程序。
我有一个方面,它以注释为目标并计算方法的执行时间是否比预期的长。但是当它调用 getSLAAnnotation() 来获取连接点方法时,它会抛出 NullPointerException。不过,它确实 return 连接点签名。我相信这可能与我得到的 Maven 输出有关。 \
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[ERROR] [AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.9.7 built on Thursday Jun 24, 2021 at 16:14:45 PDT
[ERROR] [AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2
[ERROR] [AppClassLoader@18b4aac2] info using configuration /D:/RFS/prueba-aspectj/target/classes/META-INF/aop-ajc.xml
[ERROR] [AppClassLoader@18b4aac2] info using configuration file:/C:/Users/MT27745023/.m2/repository/io/qameta/allure/allure-testng/2.17.2/allure-testng-2.17.2.jar!/META-INF/aop-ajc.xml
[ERROR] [AppClassLoader@18b4aac2] info register aspect aspectPackage.SLAAspect
[ERROR] [AppClassLoader@18b4aac2] info register aspect io.qameta.allure.aspects.StepsAspects
[ERROR] [AppClassLoader@18b4aac2] info register aspect io.qameta.allure.aspects.AttachmentsAspects
[INFO] Running TestSuite
[ERROR] [AppClassLoader@18b4aac2] info processing reweavable type page.StepTestPage: page\StepTestPage.java
[ERROR] [AppClassLoader@18b4aac2] info successfully verified type aspectPackage.SLAAspect exists. Originates from aspectPackage\SLAAspect.java
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by before advice from 'aspectPackage.SLAAspect' (SLAAspect.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by after advice from 'aspectPackage.SLAAspect' (SLAAspect.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by before advice from 'io.qameta.allure.aspects.StepsAspects' (StepsAspects.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by afterThrowing advice from 'io.qameta.allure.aspects.StepsAspects' (StepsAspects.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by afterReturning advice from 'io.qameta.allure.aspects.StepsAspects' (StepsAspects.java)
[ERROR] [AppClassLoader@18b4aac2] info processing reweavable type aspectPackage.SLAAspect: aspectPackage\SLAAspect.java
[ERROR] [AppClassLoader@18b4aac2] info successfully verified type aspectPackage.SLAAspect exists. Originates from aspectPackage\SLAAspect.java
看点:
package aspectPackage;
import java.lang.reflect.Method;
import env.EnvironmentConsumer;
import io.github.cdimascio.dotenv.Dotenv;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import java.time.LocalTime;
import java.time.temporal.Temporal;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@Aspect
public class SLAAspect{
private DateTimeFormatter ISODateTimeFormatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private DateTimeFormatter DurationFormatter = java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss");
ZonedDateTime dtFechaInicial;
SLA ca;
@Before("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void beforeTiempos(JoinPoint jp) {
System.out.println("BEFORE SLAASPECT");
ca = getSLAAnnotation(jp);
String fechaI = ZonedDateTime.now().format(ISODateTimeFormatter);
dtFechaInicial = ZonedDateTime.parse(fechaI, ISODateTimeFormatter);
}
@After("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void afterLogger(JoinPoint jp) throws Exception{
String fechaF = ZonedDateTime.now().format(ISODateTimeFormatter);
System.out.println("AFTER SLAASPECT");
ZonedDateTime dtFechaFinal = ZonedDateTime.parse(fechaF,ISODateTimeFormatter);
Duration tiempoTotal = calcularTiempoEntreFechas(dtFechaInicial,dtFechaFinal);
System.out.println(ca.toString());
System.out.println(ca.tiempoEsperado());
Duration sla = Duration.ofSeconds(ca.tiempoEsperado());
int resultSla = calcularSla (tiempoTotal, sla);
String resultadoFormateado = LocalTime.MIDNIGHT.plus(tiempoTotal).format(DurationFormatter);
Dotenv settings = null;
Duration timeout = null;
try{
settings = EnvironmentConsumer.getInstance("settings");
timeout = Duration.ofSeconds(Long.parseLong(settings.get("Timeout")));
}catch(Exception e){
System.out.println("\n\n\n\nERROR CON EL ENVIRONMENT\n\n\n\n");
throw e;
}
if(resultSla != 1 && tiempoTotal.compareTo(timeout) != 0){
throw new Exception("SLA Exception: Se excedió del tiempo esperado, el step duró: " + resultadoFormateado + " segundos");
}else{
throw new Exception("Step timeout");
}
}
private Duration calcularTiempoEntreFechas(Temporal fechaInicial, Temporal fechaFinal) {
// Calcula el tiempo final de ejecucion
return Duration.between(fechaInicial, fechaFinal);
}
private int calcularSla(Duration tiempoT, Duration tiempoE) {
// Calcula si tarda mas del tiempo esperados
// System.out.println(tiempoE.compareTo(tiempoT));
return tiempoE.compareTo(tiempoT);
}
private SLA getSLAAnnotation(JoinPoint joinPoint) {
SLA ca = null;
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object target = joinPoint.getTarget();
Method method = target.getClass().
getMethod(signature.getMethod().getName(), signature.getMethod().getParameterTypes());
if (method.isAnnotationPresent(SLA.class)){
ca = method.getAnnotation(SLA.class);
}else if (joinPoint.getTarget().getClass().isAnnotationPresent(SLA.class)){
ca = joinPoint.getTarget().getClass().getAnnotation(SLA.class);
}
return ca;
}catch(Exception e) {
return ca;
}
}
}
这是pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ar.com.samsistemas</groupId>
<artifactId>aspectSLADemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<aspectj.version>1.9.7</aspectj.version>
<allure-testng.version>2.17.2</allure-testng.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
<suiteXmlFiles>
<suiteXmlFile>./testng.xml</suiteXmlFile>
</suiteXmlFiles>
<useSystemClassLoader>true</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>${allure-testng.version}</version>
</dependency>
<dependency>
<groupId>ar.com.samsistemas</groupId>
<artifactId>web-utils</artifactId>
<version>0.0.1m</version>
</dependency>
<!-- Environment properties consumer from personal utils-->
<dependency>
<groupId>ar.com.samsistemas</groupId>
<artifactId>environment-utils</artifactId>
<version>0.0.1b</version>
</dependency>
</dependencies>
</project>
和aop.xml
<aspectj>
<aspects>
<aspect name="aspectPackage.SLAAspect"/>
<weaver options="-verbose -showWeaveInfo">
<include within="*"/>
</weaver>
</aspects>
</aspectj>
前言
抱歉,这将是一个冗长的回答,因为您自己的方面代码有太多错误或问题,我忍不住建议如何修复它。但我们会一步一步地做到这一点。
如何提出好的问题
您的 POM 中的两个依赖项未公开可用,其他如 io.github.cdimascio:dotenv-java
完全缺失,或者您依赖它们作为 non-public 的传递依赖项,这是一个 Maven anti-pattern。我在本地修复了这个问题,创建了缺少的 classes,比如 SLA
注释和 EnvironmentConsumer
,添加了一个 .env
文件,其中包含一个 Timeout
变量和一个 TestNG 测试。现在我可以编译 运行 项目了。所有这一切都是你的工作。因为你是新手,我的早茶需要拼图,所以这次我为你做了。这是你的免费拍摄。下次,请你自己做。谢谢你。
顺便说一句,您还忘记了 post 您的 NullPointerException
包括堆栈跟踪。
Surefire 错误地记录了 [ERROR]
条消息
至于 Maven Surefire 将 AspectJ weaver info
消息记录为 [ERROR]
,您可以忽略它。可能 Surefire 认为它们是错误,因为在测试开始 运行ning 之前它不期望任何日志输出。我之前和 Surefire 的维护者讨论过这个问题,他们并不真正了解 Java 代理,但这是另一天的话题。
关于getSLAAnnotation(Pointcut)
我能说的是,在简单的情况下,即被拦截的方法直接用@SLA(tiempoEsperado = 2)
之类的注释,该方法不会抛出任何错误。即使注释方法来自超级 class,它也能按预期工作。这样的方法是不必要的,正如我将在这个长答案的末尾解释的那样,因为 AspectJ 有一种更优雅的方式来从拦截的方法或 class 中获取注释。但让我们保留它以备后用。
超时逻辑错误
在测试你的方面时,我看到总是抛出 java.lang.Exception: Step timeout
,这是一个错误。你必须改变错误的逻辑
if (resultSla != 1 && tiempoTotal.compareTo(timeout) != 0) {
throw new Exception("SLA Exception: Se excedió del tiempo esperado, el step duró: " + resultadoFormateado + " segundos");
}
else {
throw new Exception("Step timeout");
}
正确
if (resultSla < 0)
throw new Exception("Step timeout");
if (timeout.compareTo(tiempoTotal) < 0)
throw new Exception("SLA Exception: Se excedió del tiempo esperado, el step duró: " + resultadoFormateado + " segundos");
这与 AOP 完全无关,只是一个 Java 错误。
不必要地使用 Duration
和 ZonedDateTime
此外,我不明白您为什么在这种情况下使用 Duration
和 ZonedDateTime
。这是缓慢且不必要的,因为您没有处理来自不同时区的时间。同样丑陋的是,您首先将持续时间转换为字符串,只是稍后再将它们解析回 zoned date-times 。为什么要把一件简单的事情搞得这么复杂?
您还应该只读取一次设置文件,而不是每次触发方面建议时都读取,并将设置设置为静态。
这个简化怎么样(请自己把我的英文错误信息翻译成西班牙文)?
private static Dotenv settings = EnvironmentConsumer.getInstance("settings");
private static long timeout = Long.parseLong(settings.get("Timeout"));
private long dtFechaInicial;
private SLA sla;
@Before("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void beforeTiempos(JoinPoint jp) {
sla = getSLAAnnotation(jp);
dtFechaInicial = currentTimeMillis();
}
@After("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void afterLogger(JoinPoint jp) throws Exception {
long tiempoTotal = currentTimeMillis() - dtFechaInicial;
if (tiempoTotal > sla.tiempoEsperado() * 1000)
throw new Exception(String.format(
"Step timeout, method SLA of %d s exceeded, actual was %.2f s",
sla.tiempoEsperado(), tiempoTotal / 1000.0
));
if (tiempoTotal > timeout * 1000)
throw new Exception(String.format(
"Step timeout, global SLA of %d s exceeded, actual was %.2f s",
timeout, tiempoTotal / 1000.0
));
}
Thread-unsafe实例字段的使用
但现在还有一个问题。请注意,通过实例字段在通知前后传输状态不是 thread-safe。想象一下,切面被多个线程并行调用,这在业务应用程序中是完全正常的。您要么需要使用 ThreadLocal
字段,要么只需将 before/after 通知对替换为一个环绕通知:
private static Dotenv settings = EnvironmentConsumer.getInstance("settings");
private static long timeout = Long.parseLong(settings.get("Timeout"));
@Around("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public Object aroundTiempos(ProceedingJoinPoint jp) throws Throwable {
SLA sla = getSLAAnnotation(jp);
long dtFechaInicial = currentTimeMillis();
Object result = jp.proceed();
long tiempoTotal = currentTimeMillis() - dtFechaInicial;
if (tiempoTotal > sla.tiempoEsperado() * 1000)
throw new Exception(String.format(
"Step timeout, method SLA of %d s exceeded, actual was %.2f s",
sla.tiempoEsperado(), tiempoTotal / 1000.0
));
if (tiempoTotal > timeout * 1000)
throw new Exception(String.format(
"Step timeout, global SLA of %d s exceeded, actual was %.2f s",
timeout, tiempoTotal / 1000.0
));
return result;
}
隐藏应用程序异常的超时异常
请注意,现在仅当目标方法本身未抛出任何异常时才会报告超时。我认为在这种情况下,常规错误应该优先于超时。在您的原始解决方案中,即使目标方法抛出异常,您也会在 @After
建议中抛出超时异常,从而隐藏原始异常。这使得应用程序调试变得困难甚至不可能。所以我改了一下,让原来的异常通过。
如何正确获取 @SLA
注释
您可以摆脱 getSLAAnnotation(JoinPoint)
方法。只需将注释绑定到建议方法,使用适当的方面语法,添加一个 SLA
方法参数并在 @annotation
切入点内使用它的名称而不是完全限定的 class 名称。现在,所有优化后您的完整外观如下所示:
package aspectPackage;
import env.EnvironmentConsumer;
import io.github.cdimascio.dotenv.Dotenv;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import static java.lang.System.currentTimeMillis;
@Aspect
public class SLAAspect {
private static Dotenv settings = EnvironmentConsumer.getInstance("settings");
private static long timeout = Long.parseLong(settings.get("Timeout"));
@Around("execution(* *(..)) && @annotation(sla)")
public Object aroundTiempos(ProceedingJoinPoint jp, SLA sla) throws Throwable {
long dtFechaInicial = currentTimeMillis();
Object result = jp.proceed();
long tiempoTotal = currentTimeMillis() - dtFechaInicial;
if (tiempoTotal > sla.tiempoEsperado() * 1000)
throw new Exception(String.format(
"Step timeout, method SLA of %d s exceeded, actual was %.2f s",
sla.tiempoEsperado(), tiempoTotal / 1000.0
));
if (tiempoTotal > timeout * 1000)
throw new Exception(String.format(
"Step timeout, global SLA of %d s exceeded, actual was %.2f s",
timeout, tiempoTotal / 1000.0
));
return result;
}
}
这比您的原始版本更短、更易读且更易于维护。在此过程中,我们还消除了几个错误。
GitHub 示例项目
我在这里解释的所有内容都在我的 example project on GitHub. I also added a TestNG test 验证方面行为中。
我正在尝试使用 LoadTime-Weaving 使用 Maven 和 AspectJ 制作一个简单的应用程序。
我有一个方面,它以注释为目标并计算方法的执行时间是否比预期的长。但是当它调用 getSLAAnnotation() 来获取连接点方法时,它会抛出 NullPointerException。不过,它确实 return 连接点签名。我相信这可能与我得到的 Maven 输出有关。 \
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[ERROR] [AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.9.7 built on Thursday Jun 24, 2021 at 16:14:45 PDT
[ERROR] [AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2
[ERROR] [AppClassLoader@18b4aac2] info using configuration /D:/RFS/prueba-aspectj/target/classes/META-INF/aop-ajc.xml
[ERROR] [AppClassLoader@18b4aac2] info using configuration file:/C:/Users/MT27745023/.m2/repository/io/qameta/allure/allure-testng/2.17.2/allure-testng-2.17.2.jar!/META-INF/aop-ajc.xml
[ERROR] [AppClassLoader@18b4aac2] info register aspect aspectPackage.SLAAspect
[ERROR] [AppClassLoader@18b4aac2] info register aspect io.qameta.allure.aspects.StepsAspects
[ERROR] [AppClassLoader@18b4aac2] info register aspect io.qameta.allure.aspects.AttachmentsAspects
[INFO] Running TestSuite
[ERROR] [AppClassLoader@18b4aac2] info processing reweavable type page.StepTestPage: page\StepTestPage.java
[ERROR] [AppClassLoader@18b4aac2] info successfully verified type aspectPackage.SLAAspect exists. Originates from aspectPackage\SLAAspect.java
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by before advice from 'aspectPackage.SLAAspect' (SLAAspect.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by after advice from 'aspectPackage.SLAAspect' (SLAAspect.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by before advice from 'io.qameta.allure.aspects.StepsAspects' (StepsAspects.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by afterThrowing advice from 'io.qameta.allure.aspects.StepsAspects' (StepsAspects.java)
[ERROR] [AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void page.StepTestPage.pruebaStepAspect())' in Type 'page.StepTestPage' (StepTestPage.java:11) advised by afterReturning advice from 'io.qameta.allure.aspects.StepsAspects' (StepsAspects.java)
[ERROR] [AppClassLoader@18b4aac2] info processing reweavable type aspectPackage.SLAAspect: aspectPackage\SLAAspect.java
[ERROR] [AppClassLoader@18b4aac2] info successfully verified type aspectPackage.SLAAspect exists. Originates from aspectPackage\SLAAspect.java
看点:
package aspectPackage;
import java.lang.reflect.Method;
import env.EnvironmentConsumer;
import io.github.cdimascio.dotenv.Dotenv;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import java.time.LocalTime;
import java.time.temporal.Temporal;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@Aspect
public class SLAAspect{
private DateTimeFormatter ISODateTimeFormatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
private DateTimeFormatter DurationFormatter = java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss");
ZonedDateTime dtFechaInicial;
SLA ca;
@Before("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void beforeTiempos(JoinPoint jp) {
System.out.println("BEFORE SLAASPECT");
ca = getSLAAnnotation(jp);
String fechaI = ZonedDateTime.now().format(ISODateTimeFormatter);
dtFechaInicial = ZonedDateTime.parse(fechaI, ISODateTimeFormatter);
}
@After("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void afterLogger(JoinPoint jp) throws Exception{
String fechaF = ZonedDateTime.now().format(ISODateTimeFormatter);
System.out.println("AFTER SLAASPECT");
ZonedDateTime dtFechaFinal = ZonedDateTime.parse(fechaF,ISODateTimeFormatter);
Duration tiempoTotal = calcularTiempoEntreFechas(dtFechaInicial,dtFechaFinal);
System.out.println(ca.toString());
System.out.println(ca.tiempoEsperado());
Duration sla = Duration.ofSeconds(ca.tiempoEsperado());
int resultSla = calcularSla (tiempoTotal, sla);
String resultadoFormateado = LocalTime.MIDNIGHT.plus(tiempoTotal).format(DurationFormatter);
Dotenv settings = null;
Duration timeout = null;
try{
settings = EnvironmentConsumer.getInstance("settings");
timeout = Duration.ofSeconds(Long.parseLong(settings.get("Timeout")));
}catch(Exception e){
System.out.println("\n\n\n\nERROR CON EL ENVIRONMENT\n\n\n\n");
throw e;
}
if(resultSla != 1 && tiempoTotal.compareTo(timeout) != 0){
throw new Exception("SLA Exception: Se excedió del tiempo esperado, el step duró: " + resultadoFormateado + " segundos");
}else{
throw new Exception("Step timeout");
}
}
private Duration calcularTiempoEntreFechas(Temporal fechaInicial, Temporal fechaFinal) {
// Calcula el tiempo final de ejecucion
return Duration.between(fechaInicial, fechaFinal);
}
private int calcularSla(Duration tiempoT, Duration tiempoE) {
// Calcula si tarda mas del tiempo esperados
// System.out.println(tiempoE.compareTo(tiempoT));
return tiempoE.compareTo(tiempoT);
}
private SLA getSLAAnnotation(JoinPoint joinPoint) {
SLA ca = null;
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object target = joinPoint.getTarget();
Method method = target.getClass().
getMethod(signature.getMethod().getName(), signature.getMethod().getParameterTypes());
if (method.isAnnotationPresent(SLA.class)){
ca = method.getAnnotation(SLA.class);
}else if (joinPoint.getTarget().getClass().isAnnotationPresent(SLA.class)){
ca = joinPoint.getTarget().getClass().getAnnotation(SLA.class);
}
return ca;
}catch(Exception e) {
return ca;
}
}
}
这是pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ar.com.samsistemas</groupId>
<artifactId>aspectSLADemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<aspectj.version>1.9.7</aspectj.version>
<allure-testng.version>2.17.2</allure-testng.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
<suiteXmlFiles>
<suiteXmlFile>./testng.xml</suiteXmlFile>
</suiteXmlFiles>
<useSystemClassLoader>true</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>${allure-testng.version}</version>
</dependency>
<dependency>
<groupId>ar.com.samsistemas</groupId>
<artifactId>web-utils</artifactId>
<version>0.0.1m</version>
</dependency>
<!-- Environment properties consumer from personal utils-->
<dependency>
<groupId>ar.com.samsistemas</groupId>
<artifactId>environment-utils</artifactId>
<version>0.0.1b</version>
</dependency>
</dependencies>
</project>
和aop.xml
<aspectj>
<aspects>
<aspect name="aspectPackage.SLAAspect"/>
<weaver options="-verbose -showWeaveInfo">
<include within="*"/>
</weaver>
</aspects>
</aspectj>
前言
抱歉,这将是一个冗长的回答,因为您自己的方面代码有太多错误或问题,我忍不住建议如何修复它。但我们会一步一步地做到这一点。
如何提出好的问题
您的 POM 中的两个依赖项未公开可用,其他如 io.github.cdimascio:dotenv-java
完全缺失,或者您依赖它们作为 non-public 的传递依赖项,这是一个 Maven anti-pattern。我在本地修复了这个问题,创建了缺少的 classes,比如 SLA
注释和 EnvironmentConsumer
,添加了一个 .env
文件,其中包含一个 Timeout
变量和一个 TestNG 测试。现在我可以编译 运行 项目了。所有这一切都是你的工作。因为你是新手,我的早茶需要拼图,所以这次我为你做了。这是你的免费拍摄。下次,请你自己做。谢谢你。
顺便说一句,您还忘记了 post 您的 NullPointerException
包括堆栈跟踪。
Surefire 错误地记录了 [ERROR]
条消息
至于 Maven Surefire 将 AspectJ weaver info
消息记录为 [ERROR]
,您可以忽略它。可能 Surefire 认为它们是错误,因为在测试开始 运行ning 之前它不期望任何日志输出。我之前和 Surefire 的维护者讨论过这个问题,他们并不真正了解 Java 代理,但这是另一天的话题。
关于getSLAAnnotation(Pointcut)
我能说的是,在简单的情况下,即被拦截的方法直接用@SLA(tiempoEsperado = 2)
之类的注释,该方法不会抛出任何错误。即使注释方法来自超级 class,它也能按预期工作。这样的方法是不必要的,正如我将在这个长答案的末尾解释的那样,因为 AspectJ 有一种更优雅的方式来从拦截的方法或 class 中获取注释。但让我们保留它以备后用。
超时逻辑错误
在测试你的方面时,我看到总是抛出 java.lang.Exception: Step timeout
,这是一个错误。你必须改变错误的逻辑
if (resultSla != 1 && tiempoTotal.compareTo(timeout) != 0) {
throw new Exception("SLA Exception: Se excedió del tiempo esperado, el step duró: " + resultadoFormateado + " segundos");
}
else {
throw new Exception("Step timeout");
}
正确
if (resultSla < 0)
throw new Exception("Step timeout");
if (timeout.compareTo(tiempoTotal) < 0)
throw new Exception("SLA Exception: Se excedió del tiempo esperado, el step duró: " + resultadoFormateado + " segundos");
这与 AOP 完全无关,只是一个 Java 错误。
不必要地使用 Duration
和 ZonedDateTime
此外,我不明白您为什么在这种情况下使用 Duration
和 ZonedDateTime
。这是缓慢且不必要的,因为您没有处理来自不同时区的时间。同样丑陋的是,您首先将持续时间转换为字符串,只是稍后再将它们解析回 zoned date-times 。为什么要把一件简单的事情搞得这么复杂?
您还应该只读取一次设置文件,而不是每次触发方面建议时都读取,并将设置设置为静态。
这个简化怎么样(请自己把我的英文错误信息翻译成西班牙文)?
private static Dotenv settings = EnvironmentConsumer.getInstance("settings");
private static long timeout = Long.parseLong(settings.get("Timeout"));
private long dtFechaInicial;
private SLA sla;
@Before("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void beforeTiempos(JoinPoint jp) {
sla = getSLAAnnotation(jp);
dtFechaInicial = currentTimeMillis();
}
@After("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public void afterLogger(JoinPoint jp) throws Exception {
long tiempoTotal = currentTimeMillis() - dtFechaInicial;
if (tiempoTotal > sla.tiempoEsperado() * 1000)
throw new Exception(String.format(
"Step timeout, method SLA of %d s exceeded, actual was %.2f s",
sla.tiempoEsperado(), tiempoTotal / 1000.0
));
if (tiempoTotal > timeout * 1000)
throw new Exception(String.format(
"Step timeout, global SLA of %d s exceeded, actual was %.2f s",
timeout, tiempoTotal / 1000.0
));
}
Thread-unsafe实例字段的使用
但现在还有一个问题。请注意,通过实例字段在通知前后传输状态不是 thread-safe。想象一下,切面被多个线程并行调用,这在业务应用程序中是完全正常的。您要么需要使用 ThreadLocal
字段,要么只需将 before/after 通知对替换为一个环绕通知:
private static Dotenv settings = EnvironmentConsumer.getInstance("settings");
private static long timeout = Long.parseLong(settings.get("Timeout"));
@Around("execution(* *(..)) && @annotation(aspectPackage.SLA)")
public Object aroundTiempos(ProceedingJoinPoint jp) throws Throwable {
SLA sla = getSLAAnnotation(jp);
long dtFechaInicial = currentTimeMillis();
Object result = jp.proceed();
long tiempoTotal = currentTimeMillis() - dtFechaInicial;
if (tiempoTotal > sla.tiempoEsperado() * 1000)
throw new Exception(String.format(
"Step timeout, method SLA of %d s exceeded, actual was %.2f s",
sla.tiempoEsperado(), tiempoTotal / 1000.0
));
if (tiempoTotal > timeout * 1000)
throw new Exception(String.format(
"Step timeout, global SLA of %d s exceeded, actual was %.2f s",
timeout, tiempoTotal / 1000.0
));
return result;
}
隐藏应用程序异常的超时异常
请注意,现在仅当目标方法本身未抛出任何异常时才会报告超时。我认为在这种情况下,常规错误应该优先于超时。在您的原始解决方案中,即使目标方法抛出异常,您也会在 @After
建议中抛出超时异常,从而隐藏原始异常。这使得应用程序调试变得困难甚至不可能。所以我改了一下,让原来的异常通过。
如何正确获取 @SLA
注释
您可以摆脱 getSLAAnnotation(JoinPoint)
方法。只需将注释绑定到建议方法,使用适当的方面语法,添加一个 SLA
方法参数并在 @annotation
切入点内使用它的名称而不是完全限定的 class 名称。现在,所有优化后您的完整外观如下所示:
package aspectPackage;
import env.EnvironmentConsumer;
import io.github.cdimascio.dotenv.Dotenv;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import static java.lang.System.currentTimeMillis;
@Aspect
public class SLAAspect {
private static Dotenv settings = EnvironmentConsumer.getInstance("settings");
private static long timeout = Long.parseLong(settings.get("Timeout"));
@Around("execution(* *(..)) && @annotation(sla)")
public Object aroundTiempos(ProceedingJoinPoint jp, SLA sla) throws Throwable {
long dtFechaInicial = currentTimeMillis();
Object result = jp.proceed();
long tiempoTotal = currentTimeMillis() - dtFechaInicial;
if (tiempoTotal > sla.tiempoEsperado() * 1000)
throw new Exception(String.format(
"Step timeout, method SLA of %d s exceeded, actual was %.2f s",
sla.tiempoEsperado(), tiempoTotal / 1000.0
));
if (tiempoTotal > timeout * 1000)
throw new Exception(String.format(
"Step timeout, global SLA of %d s exceeded, actual was %.2f s",
timeout, tiempoTotal / 1000.0
));
return result;
}
}
这比您的原始版本更短、更易读且更易于维护。在此过程中,我们还消除了几个错误。
GitHub 示例项目
我在这里解释的所有内容都在我的 example project on GitHub. I also added a TestNG test 验证方面行为中。