第一个示例
假设你是一名应用程序开发人员,任务是诊断系统中一些性能问题的原因。我们不打算使用分析工具,而是打开一个简单的性能分析切面,让我们可以快速获取一些性能指标。然后,我们可以立即对该特定区域应用更细粒度的分析工具。
此处提供的示例使用 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 或生产的应用程序构建中排除。