前提介绍
当我们聊到 Spring 框架的项目实际开发中,用的强大的功能之一就是(面向切面编程)的这门 AOP 技术。如果使用得当,它的最大的作用就是侵入性比较少并且简化我们的工作任务(节省大量的重复性编码),最为重要的一点是,它可以让我们在不改变原有代码的情况下,织入我们的逻辑,尤其是在我们没有源代码的时候,而且当我们恢复之前的逻辑的时候,只需要去掉代理就可以了。
AOP 的动态代理
Spring AOP 的常规的实现方式为 cglib 和 jdk 动态代理。两者均可实现,只是性能上略有差异,此处不再详述。
无论是 JDK 的动态代理和 Cglib 实现的 Spring 代理机制都有一些的局限性,那就是生成的代理对象在内部调用方法时,AOP 功能无效,因为毕竟不是代理对象的调用而是 this 的调用。
他们都只能对 public、protected 类型的成员方法生效。
当然,也是有变通的方案解决,比如将 bean 当做属性注入到自身,然后所有方法调用都通过这个属性来调用。或者通过 AopContext.currentProxy 的方式去获取代理对象。但是这些解决方案,在开发过程中开发者很容易因为疏忽导致出现问题。
所以,如果需要一种更加强大和易用的 aop 实现方案,那就是字节码编织技术 aspectj。通过修改字节码,可以实现对所有方法进行切面,包括(final、private、static 类型的方法),功能强大。并且 spring 支持 aspectj 方式的 aop。
AOP 技术类型
在介绍强大的 Aspectj 的技术之前,我们先进行对 AOP 技术实现基础进行分类,通过为目标类织入切面的方式,实现对目标类功能的增强。按切面被织如到目标类中的时间划分
编译时:使用特殊的编译器在编译期将切面织入目标类,这种比较少见,因为需要特殊的编译器的支持。例如,AspectJ 编译器,很少有到,目前我还没有用到。
加载时:通过字节码编辑技术在类加载期将切面织入目标类中,它的核心思想是:在目标类的 class 文件被 JVM 加载前,通过自定义类加载器或者类文件转换器将横切逻辑织入到目标类的 class 文件中,然后将修改后 class 文件交给 JVM 加载。这种织入方式可以简称为 LTW(LoadTimeWeaving),AspectJ 的 LoadTimeWeaving (LTW)
运行时:运行期通过为目标类生成动态代理的方式实现 AOP 就属于运行期织入,这也是 Spring AOP 中的默认实现,并且提供了两种创建动态代理的方式:JDK 自带的针对接口的动态代理和使用 CGLib 动态创建子类的方式创建动态代理。
这里我们介绍是 Spring 整合 AspectJ 的 LTW 机制。属于动态加载织入。
LTW 可以解决的问题
LTW 的原理
类加载期通过字节码编辑技术将切面织入目标类,这种方式叫做 LTW(Load Time Weaving)。
使用 JDK5 新增的 java.lang.instrument 包,在类加载时对字节码进行转换,从而实现 AOP 功能。
JDK 的代理功能让代理器访问到 JVM 的底层组件,借此向 JVM 注册类文件转换器,在类加载时对类文件的字节码进行转换 ClassFileTransformer 接口。具体方向可以研究一下 java agent 技术即可。
Spring 中实现 LTW
Spring 中默认通过运行期生成动态代理的方式实现切面的织入,实现 AOP 功能,但是 Spring 也可以使用 LTW 技术来实现 AOP,并且提供了细粒度的控制,单个 ClassLoader 范围内实施类文件转换。
Spring 中的 org.springframework.instrument.classloading.LoadTimeWeaver 接口定义了为类加载器添加 ClassFileTransfomer 的抽象
Spring 的 LTW 支持 AspectJ 定义的切面,既可以是直接使用 AspectJ 语法定义的切面,也可以是使用 @AspectJ 注解,通过 java 类定义的切面。
Spring LTW 通过读取 classpath 下 META-INF/aop.xml 文件,获取切面类和要被切面织入的目标类的相关信息,通过 LoadTimeWeaver 在 ClassLoader 加载类文件时将切面织入目标类中。
Spring 中可以通过 LoadTimeWeaver 将 Spring 提供的 ClassFileTransformer 注册到 ClassLoader 中。
在类加载期,注册的 ClassFileTransformer 读取类路径下 META-INF/aop.xml 文件中定义的切面类和目标类信息,在目标类的 class 文件真正被 VM 加载前织入切面信息,生成新的 Class 文件字节码,然后交给 VM 加载。
因而之后创建的目标类的实例,就已经实现了 AOP 功能。
maven 依赖和插件
spring-AOP 和 aspectJ
spring 的 maven 配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>5.2.5</version>
</dependency>
复制代码
springboot 的配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
</dependencies>
复制代码
其中都会有 maven aspectjWeaver 包
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
复制代码
spring 全注解方式
@Configuration
//开启Aspectj运行时编织
@EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED)
//开启非spring容器bean的依赖注入支持
@EnableSpringConfigured
@ComponentScan(basePackages = "demo")
public class AppConfig extends WebMvcConfigurationSupport {}
复制代码
springboot 全注解方式
@SpringBootApplication
@EnableLoadTimeWeaving
@EnableSpringConfigured
@EnableAsync(mode = AdviceMode.ASPECTJ)
复制代码
切面类
@Aspect
//让spring可以将依赖注入到切面对象中
@Configurable
public class SampleAspectj {
@Autowired
private RestBean rbean;
@Around(value = "@annotation(rpc) ")
public void aop(ProceedingJoinPoint joinPoint,Rpc rpc) {
rbean.rwar();
}
}
复制代码
对象切点类
@Configurable
public class TestOriginObject {
// 依赖注入
@Autowired
public TestService testService;
// spring 的切面
@Async
public void print() {
System.out.println("TestOriginObject print thread " + Thread.currentThread().toString());
}
// 自定义的切面
@Rpc
public void print1() {
System.out.println("TestOriginObject print1");
}
}
复制代码
@Component
public class TestService {
@Async
@Profile
public void asyncPrint() {
System.out.println("TestService print thread " + Thread.currentThread().toString());
}
public void print() {
asyncPrint();
}
private static void test04() {
log.info("------------test04-----------");
}
private void test03() {
log.info("------------test03-----------");
}
public void test02() {
log.info("------------test02-----------");
}
}
复制代码
aop.xml 配置
放到/src/main/resources/META-INF 目录下
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-showWeaveInfo -XnoInline -Xset:weaveJavaxPackages=true -Xlint:ignore -verbose -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
<!-- 只对指定包下的类进行编织 -->
<include within="demo..*"/>
</weaver>
<aspects>
<!-- 使用指定的切面类进行编织 -->
<aspect name="test.SampleAspectj"/>
</aspects>
</aspectj>
复制代码
@SpringBootApplication
@EnableLoadTimeWeaving
@EnableSpringConfigured
@EnableAsync(mode = AdviceMode.ASPECTJ)
public class AppApplication {
public static void main(String[] args) {
// 初始化 spring context
ApplicationContext context = SpringApplication.run(AppApplication.class, args);
// 创建 POJO,此时 TestService 会被注入到 POJO 中
TestOriginObject pojo = new TestOriginObject();
System.out.println("inject bean " + pojo.testService);
TestService testService = context.getBean(TestService.class);
// 正常调用切面
testService.asyncPrint();
// 切面的内部调用
testService.print();
// 非 spring 管理的类切面调用,spring 定义的切面
pojo.print();
// 非 spring 管理的类切面调用,自定义的切面
pojo.print1();
testService.test02();
testService.test03();
testService.test04();
}
}
复制代码
说明
@EnableAspectJAutoProxy 也会启动运行时代理植入方式。
启动 VM 参数
-javaagent:path\spring-instrument-5.1.6.RELEASE.jar
-javaagent:path\aspectjweaver-1.9.2.jar
复制代码
可以采用 Maven 方式进行打包进入执行
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
-javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar"
<!-- -Dspring.profiles.active=test-->
</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<agent>
${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
</agent>
<agent>
${settings.localRepository}/org/springframework/spring-instrument/${spring.version}/spring-instrument-${spring.version}.jar
</agent>
</configuration>
</plugin>
</plugins>
</build>
复制代码
LTW 官方文档
AspectJ:https://www.eclipse.org/aspectj/doc/released/devguide/index.html
Spring AOP using AspectJ: (5.10.4):https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-using-aspectj
public class Run {
public static void main(String[] args) {
//关键代码,用于在程序中开启agent
//通过bytebuddy拿到当前jvm的Instrumentation实例
Instrumentation instrumentation = ByteBuddyAgent.install();
//激活Aspectj的代理对象
Agent.agentmain("", instrumentation);
//激活spring代理对象
InstrumentationSavingAgent.agentmain("", instrumentation);
//启动spring容器
//...
}
}
复制代码
评论