我正在尝试使用LoadTime Weaving用Maven和AspectJ制作一个简单的应用程序
我有一个方面,它以注释为目标,并计算方法的执行时间是否比预期的长。但是,当它调用getSLAAnnotation()来获取joinpoint方法时,它会抛出NullPointerException。不过,它确实返回了joinpoint签名。我认为这可能与我得到的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: pageStepTestPage.java
[ERROR] [AppClassLoader@18b4aac2] info successfully verified type aspectPackage.SLAAspect exists. Originates from aspectPackageSLAAspect.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: aspectPackageSLAAspect.java
[ERROR] [AppClassLoader@18b4aac2] info successfully verified type aspectPackage.SLAAspect exists. Originates from aspectPackageSLAAspect.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("nnnnERROR CON EL ENVIRONMENTnnnn");
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
)完全缺失,或者您依赖它们作为非公共依赖项的传递依赖项,这是Maven反模式。我在本地修复了这个问题,创建了缺少的类,如SLA
注释和EnvironmentConsumer
,添加了一个包含Timeout
变量和TestNG测试的.env
文件。现在我可以编译并运行该项目了。所有这些都是你的工作。因为你是个新手,我的早茶需要一个谜题,所以这次我为你做了。这是你的罚球。下次,请你自己做。非常感谢。
顺便说一句,你还忘了发布你的NullPointerException
,包括堆栈跟踪。
Surefire错误地记录[ERROR]
消息
至于Maven Surefire将AspectJ weaverinfo
消息记录为[ERROR]
,您可以忽略这一点。Surefire可能认为它们是错误,因为在测试开始运行之前,它不希望有任何日志输出。我之前和Surefire维护人员讨论过这个问题,他们并不真正理解Java代理,但这是另一天的话题。
关于getSLAAnnotation(Pointcut)
我可以说的是,在简单的情况下,即截获的方法直接用@SLA(tiempoEsperado = 2)
之类的东西进行注释,该方法不会抛出任何错误。即使带注释的方法来自一个超类,它也能按预期工作。这样的方法是不必要的,正如我将在这个长答案的末尾解释的那样,因为AspectJ有一种更优雅的方式来从截获的方法或类中获取注释。但让我们把它留到以后。
超时逻辑错误
在测试您的方面时,我看到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
。这是缓慢和不必要的,因为您不使用来自不同时区的时间。同样丑陋的是,您首先将持续时间转换为字符串,然后再将其解析回分区日期时间。你为什么把一件简单的事情搞得如此复杂?
您还应该只读取设置文件一次,而不是每次触发方面建议时,并使设置保持静态。
这个简化怎么样(请你自己把我的英语错误信息翻译成西班牙语)?
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
));
}
线程不安全地使用实例字段
但即使是现在,还有一个问题。请注意,通过实例字段在通知之前和之后之间传输状态是不安全的。假设该方面由多个线程并行调用,这在业务应用程序中是非常正常的。您要么需要使用ThreadLocal
字段,要么只需将前后建议对替换为一个前后建议:
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)
方法。只需使用适当的方面语法将注释绑定到advice方法,添加一个SLA
方法参数,并在@annotation
切入点内使用其名称,而不是完全限定的类名。现在,在所有优化之后,您的完整方面看起来是这样的:
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示例项目
我在这里解释的一切都在我在GitHub上的示例项目中。我还添加了一个TestNG测试来验证方面行为。