aop的实现原理
AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
-
第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
-
第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能
-
作为对比,之前学习的代理是运行时生成新的字节码
简单比较的话:
-
aspectj 在编译和加载时,修改目标字节码,性能较高
-
aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
-
但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行
ajc 编译器实现增强
编写类,并在其中准备要增强的方法
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public static void foo() {
log.debug("foo()");
}
}
编写切面类
@Aspect // ⬅️注意此切面并未被 Spring 管理
public class MyAspect {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
@Before("execution(* com.sky.service.MyService.foo())")
public void before() {
log.debug("before()");
}
}
在pom文件中添加插件
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>8</source>
<target>8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<!-- use this goal to weave all your main classes -->
<goal>compile</goal>
<!-- use this goal to weave all your test classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
此时通过这个插件能在编译时能修改 class 文件实现增强,并没有使用代理对象
编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
注意
-
目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
-
一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器
学到了什么
1. aop 的原理并非代理一种, 编译器也能玩出花样
agent 类加载实现增强
修改要被增强的类(基于代理实现的话并不会增强目标方法调用的方法)
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
final public void foo() {
log.debug("foo()");
this.bar();
}
public void bar() {
log.debug("bar()");
}
}
注意几点
1. 目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
2. 运行时需要在 VM options 里加入 -javaagent:C:/Users/manyh/.m2/repository/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar
把其中 C:/Users/manyh/.m2/repository 改为你自己 maven 仓库起始地址
编写启动类
@SpringBootApplication
public class Spring09Application {
private static final Logger log = LoggerFactory.getLogger(A10.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Spring09Application.class, args);
MyService service = context.getBean(MyService.class);
// ⬇️MyService 并非代理, 但 foo 方法也被增强了, 做增强的 java agent, 在加载类时, 修改了 class 字节码
log.debug("service class: {}", service.getClass());
service.foo();
context.close();
}
}
此时agent类加载器会在类加载期间修改class的字节码,增强了方法
学到了什么
1. aop 的原理并非代理一种, agent 也能, 只要字节码变了, 行为就变了
AOP 实现之 proxy
jdk 动态代理
jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系
编写代码
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
ClassLoader loader = JdkProxyDemo.class.getClassLoader(); // 用来加载在运行期间动态生成的字节码
Foo proxy = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (p, method, args) -> {
System.out.println("before...");
// 目标.方法(参数)
// 方法.invoke(目标, 参数);
Object result = method.invoke(target, args);
System.out.println("after....");
return result;
});
// 调用代理
proxy.foo();
}
}
在运行期间修改字节码文件,可以发现基于jdk实现代理增强功能实现了
cglib 代理
-
cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
-
限制⛔:根据上述分析 final 类无法被 cglib 增强
此时需要引入aop起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写代码
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Target proxy = (Target) Enhancer.create(Target.class,
(MethodInterceptor) (p, method, args, methodProxy) -> {
System.out.println("before...");
// Object result = method.invoke(target, args); // 用方法反射调用目标
// methodProxy 它可以避免反射调用
// Object result = methodProxy.invoke(target, args); // 内部没有用反射, 需要目标 (spring)
Object result = methodProxy.invokeSuper(p, args); // 内部没有用反射, 需要代理
System.out.println("after...");
return result;
});
// 调用代理
proxy.foo();
}
}
运行结果和jdk代理一样
注: methodProxy.invoke和methodProxy.invokeSuper不是基于反射原理实现的