在 Spring 应用中使用 AspectJ

第一个示例

假设你是一名应用程序开发人员,任务是诊断系统中一些性能问题的原因。我们不打算使用分析工具,而是打开一个简单的性能分析切面,让我们可以快速获取一些性能指标。然后,我们可以立即对该特定区域应用更细粒度的分析工具。

此处提供的示例使用 XML 配置。你还可以通过 Java 配置 配置和使用 @AspectJ。具体来说,你可以使用 @EnableLoadTimeWeaving 注解作为 的替代方案(有关详细信息,请参阅下文)。

以下示例显示了性能分析切面,它并不花哨。它是一个基于时间的性能分析器,使用 @AspectJ 风格的切面声明:

Java

Kotlin

package com.xyz;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Pointcut;

import org.springframework.util.StopWatch;

import org.springframework.core.annotation.Order;

@Aspect

public class ProfilingAspect {

@Around("methodsToBeProfiled()")

public Object profile(ProceedingJoinPoint pjp) throws Throwable {

StopWatch sw = new StopWatch(getClass().getSimpleName());

try {

sw.start(pjp.getSignature().getName());

return pjp.proceed();

} finally {

sw.stop();

System.out.println(sw.prettyPrint());

}

}

@Pointcut("execution(public * com.xyz..*.*(..))")

public void methodsToBeProfiled(){}

}

package com.xyz

import org.aspectj.lang.ProceedingJoinPoint

import org.aspectj.lang.annotation.Aspect

import org.aspectj.lang.annotation.Around

import org.aspectj.lang.annotation.Pointcut

import org.springframework.util.StopWatch

import org.springframework.core.annotation.Order

@Aspect

class ProfilingAspect {

@Around("methodsToBeProfiled()")

fun profile(pjp: ProceedingJoinPoint): Any? {

val sw = StopWatch(javaClass.simpleName)

try {

sw.start(pjp.getSignature().getName())

return pjp.proceed()

} finally {

sw.stop()

println(sw.prettyPrint())

}

}

@Pointcut("execution(public * com.xyz..*.*(..))")

fun methodsToBeProfiled() {

}

}

我们还需要创建一个 META-INF/aop.xml 文件,以告知 AspectJ 织入器我们希望将 ProfilingAspect 织入到我们的类中。这种文件约定,即 Java classpath 上存在一个(或多个)名为 META-INF/aop.xml 的文件,是标准的 AspectJ。以下示例显示了 aop.xml 文件:

建议仅织入特定的类(通常是应用程序包中的类,如上面 aop.xml 示例所示),以避免诸如 AspectJ dump 文件和警告之类的副作用。从效率的角度来看,这也是一种最佳实践。

现在我们可以转到配置的 Spring 特定部分。我们需要配置一个 LoadTimeWeaver(稍后解释)。这个加载时织入器是负责将一个或多个 META-INF/aop.xml 文件中的切面配置织入到应用程序类中的基本组件。好消息是它不需要大量配置(还有一些你可以指定的选项,但这些将在后面详细介绍),如下例所示:

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="

http://www.springframework.org/schema/beans

https://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context

https://www.springframework.org/schema/context/spring-context.xsd">

class="com.xyz.StubEntitlementCalculationService"/>

现在所有必需的工件(切面、META-INF/aop.xml 文件和 Spring 配置)都已就位,我们可以创建以下带有 main(..) 方法的驱动程序类来演示 LTW 的实际应用:

Java

Kotlin

package com.xyz;

// imports

public class Main {

public static void main(String[] args) {

ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

EntitlementCalculationService service =

ctx.getBean(EntitlementCalculationService.class);

// the profiling aspect is 'woven' around this method execution

service.calculateEntitlement();

}

}

package com.xyz

// imports

fun main() {

val ctx = ClassPathXmlApplicationContext("beans.xml")

val service = ctx.getBean(EntitlementCalculationService.class)

// the profiling aspect is 'woven' around this method execution

service.calculateEntitlement()

}

我们还有最后一件事要做。本节开头确实说过,可以使用 Spring 在每个 ClassLoader 的基础上选择性地打开 LTW,这是事实。但是,对于此示例,我们使用(Spring 提供的)Java 代理来打开 LTW。我们使用以下命令运行前面显示的 Main 类:

java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main

-javaagent 是一个标志,用于指定和启用 代理来检测在 JVM 上运行的程序。Spring Framework 附带了这样一个代理,即 InstrumentationSavingAgent,它打包在 spring-instrument.jar 中,在前面的示例中作为 -javaagent 参数的值提供。

Main 程序的执行输出如下一个示例所示。(我已在 calculateEntitlement() 实现中引入了 Thread.sleep(..) 语句,以便分析器实际捕获的不仅仅是 0 毫秒(01234 毫秒不是 AOP 引入的开销)。以下清单显示了我们运行分析器时得到的输出:

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234

------ ----- ----------------------------

ms % Task name

------ ----- ----------------------------

01234 100% calculateEntitlement

由于此 LTW 是通过完整的 AspectJ 实现的,我们不仅限于建议 Spring bean。以下对 Main 程序的轻微修改会产生相同的结果:

Java

Kotlin

package com.xyz;

// imports

public class Main {

public static void main(String[] args) {

new ClassPathXmlApplicationContext("beans.xml");

EntitlementCalculationService service =

new StubEntitlementCalculationService();

// the profiling aspect will be 'woven' around this method execution

service.calculateEntitlement();

}

}

package com.xyz

// imports

fun main(args: Array) {

ClassPathXmlApplicationContext("beans.xml")

val service = StubEntitlementCalculationService()

// the profiling aspect will be 'woven' around this method execution

service.calculateEntitlement()

}

请注意,在前面的程序中,我们如何引导 Spring 容器,然后完全在 Spring 之外创建 StubEntitlementCalculationService 的新实例。性能分析建议仍然被织入。

诚然,这个例子很简单。然而,Spring 中 LTW 支持的基础知识已在前面的例子中全部介绍,本节的其余部分将详细解释每个配置和用法的“为什么”。

本示例中使用的 ProfilingAspect 可能很基础,但它非常有用。它是一个很好的开发时切面示例,开发人员可以在开发过程中使用,然后轻松地从部署到 UAT 或生产的应用程序构建中排除。

Copyright © 2022 篮球世界杯_世界杯亚洲区名额 - cdbnfc.com All Rights Reserved.