我正在嘗試使用 LoadTime-Weaving 使用 Maven 和 AspectJ 制作一個簡單的應用程式。
我有一個以注釋為目標的方面,并計算方法的執行時間是否比預期的長。但是當它呼叫 getSLAAnnotation() 來獲取連接點方法時,它會拋出 NullPointerException。它確實回傳了連接點簽名。我相信這可能與我得到的 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>
uj5u.com熱心網友回復:
前言
抱歉,這將是一個冗長的答案,因為您自己的方面代碼有很多錯誤或問題,我忍不住建議如何修復它。但我們將一步一步地做到這一點。
如何提出好的問題
您的 POM 中的兩個依賴項不公開可用,其他類似io.github.cdimascio:dotenv-java完全丟失,或者您依賴它們作為非公開依賴項的傳遞依賴項,這是 Maven 反模式。SLA我在本地修復了該問題,創建了諸如注釋和之類的缺失類EnvironmentConsumer,添加了一個.env包含Timeout變數的檔案以及一個 TestNG 測驗。現在我可以編譯并運行該專案了。所有這一切都是你的作業。因為你是新手,我需要一個關于我的早茶的謎題,所以這次我為你做了。這是你的免費拍攝。下次請自己做。謝謝你。??
順便說一句,您還忘記發布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)方法。只需將注解系結到通知方法,使用正確的方面語法,添加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 測驗來驗證方面的行為。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/431663.html
