介绍
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LoadTimeWeavingConfiguration.class)
public @interface EnableLoadTimeWeaving {
/**
* Whether AspectJ weaving should be enabled.
*/
AspectJWeaving aspectjWeaving() default AspectJWeaving.AUTODETECT;
/**
* AspectJ weaving enablement options.
*/
enum AspectJWeaving {
/**
* Switches on Spring-based AspectJ load-time weaving.
*/
ENABLED,
/**
* Switches off Spring-based AspectJ load-time weaving (even if a
* "META-INF/aop.xml" resource is present on the classpath).
*/
DISABLED,
/**
* Switches on AspectJ load-time weaving if a "META-INF/aop.xml" resource
* is present in the classpath. If there is no such resource, then AspectJ
* load-time weaving will be switched off.
*/
AUTODETECT;
}
}
说明:
作用:
用于切换不同场景下实现增强。
属性:
aspectjWeaving:是否开启LTW的支持。
ENABLED 开启LTW
DISABLED 不开启LTW
AUTODETECT 如果类路径下能读取到META‐INF/aop.xml文件,则开启LTW,否则关闭
使用场景:
在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入、类加载期织入和运行期织入。编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中;而类加载期织入则指通过特殊的类加载器,在类字节码加载到JVM时,织入切面;运行期织入则是采用CGLib工具或JDK动态代理进行切面的织入。
AspectJ提供了两种切面织入方式,第一种通过特殊编译器,在编译期,将AspectJ语言编写的切面类织入到Java类中,可以通过一个Ant或Maven任务来完成这个操作;第二种方式是类加载期织入,也简称为LTW(Load Time Weaving)
官网说明
假设您是一位负责诊断系统中某些性能问题的原因的应用程序开发人员。与其使用分析工具,不如使用一个简单的分析方面,使我们能够快速获得一些性能 Metrics。然后,我们可以立即在该特定区域应用更细粒度的分析工具。
Note
此处提供的示例使用 XML 配置。您还可以将Java configuration配置和使用@AspectJ。具体来说,您可以使用@EnableLoadTimeWeaving
Comments 替代<context:load-time-weaver/>
(有关详细信息,请参见below)。
下面的示例显示了配置方面,它不是花哨的-它是基于时间的探查器,它使用@AspectJ 样式的方面声明:
package foo;
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 * foo..*.*(..))")
public void methodsToBeProfiled(){}
}
我们还需要创建一个META-INF/aop.xml
文件,以通知 AspectJ 编织者我们要将ProfilingAspect
编织到类中。此文件约定,即在 JavaClasspath 上名为META-INF/aop.xml
的文件,是标准 AspectJ。下面的示例显示aop.xml
文件:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="foo.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="foo.ProfilingAspect"/>
</aspects>
</aspectj>
现在,我们可以 continue 进行配置中特定于 Spring 的部分。我们需要配置一个LoadTimeWeaver
(稍后说明)。此加载时织布器是必不可少的组件,负责将一个或多个META-INF/aop.xml
文件中的方面配置编织到应用程序的类中。好处是,它不需要很多配置(您可以指定一些其他选项,但是稍后会详细介绍),如以下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- a service object; we will be profiling its methods -->
<bean id="entitlementCalculationService"
class="foo.StubEntitlementCalculationService"/>
<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>
现在,所有必需的构件(方面,META-INF/aop.xml
文件和 Spring 配置)都就位了,我们可以使用main(..)
方法创建以下驱动程序类,以演示实际的 LTW:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService
= (EntitlementCalculationService) ctx.getBean("entitlementCalculationService");
// the profiling aspect is 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
我们还有最后一件事要做。本节的引言确实说过,可以使用 Spring 以ClassLoader
为基础选择性地打开 LTW,这是事实。但是,在此示例中,我们使用 Java 代理(Spring 随附)打开 LTW。我们使用以下命令运行前面显示的Main
类:
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
-javaagent
是用于指定和启用代理来检测在 JVM 上运行的程序的标志。 Spring 框架附带了这样的代理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
程序的以下细微变化会产生相同的结果:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService =
new StubEntitlementCalculationService();
// the profiling aspect will be 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
注意,在前面的程序中,我们如何引导 Spring 容器,然后完全在 Spring 上下文之外创建StubEntitlementCalculationService
的新实例。剖析建议仍会被应用。
诚然,这个例子很简单。但是,在前面的示例中已经介绍了 Spring 对 LTW 支持的基础,本节的其余部分详细解释了每一位配置和用法的“原因”。
示例代码
@Configuration
@ComponentScan("com.dalianpai.spring5.aop")
@EnableLoadTimeWeaving(aspectjWeaving=EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class SpringConfiguration {
}
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="com.dalianpai.spring5..*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="com.dalianpai.spring5.aop.utils.LoadTimeWeavingAspect"/>
</aspects>
</aspectj>
@Aspect
//@Component
public class LoadTimeWeavingAspect {
/**
* 切入点表达式
*/
@Pointcut("execution(* com.dalianpai.spring5.aop.service.impl.*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
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());
}
}
}