AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能。
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
-
第一种是通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
-
第二种是通过 agent 在加载目标类时,修改目标类的字节码,织入增强功能
-
作为对比,之前学习的代理是运行时生成新的字节码
简单比较的话:
-
aspectj 在编译和加载时,修改目标字节码,性能较高
-
aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
-
但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行
这里开拓一下视野,在实际开发中,我们还是经常使用代理为主。
一、AOP 实现之 ajc 编译器
需要导入的依赖和插件
注意事项:
- 版本选择了 java 8, 因为目前的 aspectj-maven-plugin 1.14.0 最高只支持到 java 16
- 一定要用 maven 的 compile 来编译, idea 不会调用 ajc 编译器
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<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>
</plugins>
</build>
主程序类
@SpringBootApplication
public class A09Application {
private static final Logger log = LoggerFactory.getLogger(A09Application.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A09Application.class, args);
MyService service = context.getBean(MyService.class);
log.debug("service class: {}", service.getClass());
service.foo();
context.close();
}
}
增强类
@Aspect // 注意此切面并未被 Spring 管理
public class MyAspect {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
@Before("execution(* com.itheima.service.MyService.foo())")
public void before() {
log.debug("before()");
}
}
被增强类
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public static void foo() {
log.debug("foo()");
}
}
结果:MyService的类型是原始目标,而不是代理
[main] com.itheima.A09Application : service class: class com.itheima.service.MyService
[main] com.itheima.aop.MyAspect : before()
[main] com.itheima.service.MyService : foo()
这里的增强并不是spring做的增强,因为切面类并未被管理,是通过ajc编译器实现的。
查看MyService的class文件,发现foo()调用了增强类的方法。
那我们去掉与spring相关的东西,重新再测试
public class A09Application {
private static final Logger log = LoggerFactory.getLogger(A09Application.class);
public static void main(String[] args) {
new MyService().foo();
}
}
发现还是被增强了,原因:修改的是class文件,自然可以生效
[main] DEBUG com.itheima.aop.MyAspect - before()
[main] DEBUG com.itheima.service.MyService - foo()
总结
-
编译器也能修改 class 实现增强
-
编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
二、AOP 实现之 agent 类加载
类加载时可以通过 agent 修改 class 实现增强
需要导入的依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
主程序类
@SpringBootApplication
public class A10Application {
private static final Logger log = LoggerFactory.getLogger(A10Application.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A10Application.class, args);
MyService service = context.getBean(MyService.class);
log.debug("service class: {}", service.getClass());
service.foo();
}
}
增强类
@Aspect // 注意此切面并未被 Spring 管理
public class MyAspect {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
@Before("execution(* com.itheima.service.MyService.foo())")
public void before() {
log.debug("before()");
}
}
被增强类
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public static void foo() {
log.debug("foo()");
}
}
运行时需要在 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 仓库起始地址类加载阶段,在class文件看不到。
结果:MyService的类型是原始目标,而不是代理
[main] com.itheima.A09Application : service class: class com.itheima.service.MyService
[main] com.itheima.aop.MyAspect : before()
[main] com.itheima.service.MyService : foo()
由于是在类加载阶段(即运行期间)被增强,在class文件看不到,我们需要借助Arthas工具查看。可以进入官网下载jar包。
具体操作如下:
反编译命令:
jad com.itheima.service.MyService
发现MyService在运行期间被增强
三、AOP 实现之 proxy
代理类与普通类的差别:
- 普通类:java原代码 -> 字节码 -> 类加载 -> 使用
- 代理类:运行期间直接生成代理类的字节码
1、JDK动态代理
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
}
// jdk 只能针对接口代理
public static void main(String[] param) throws IOException {
//目标对象
Target target = new Target();
//用来加载在运行期间动态生成的字节码
ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
//参数二:代理类要实现的接口 参数三:代理类调用代理类方法时执行的行为
Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before...");
//反射调用目标方法
Object result = method.invoke(target);
System.out.println("after...");
return result; //让代理类返回目标方法执行的结果
}
});
proxy.foo();
}
}
结果:
before...
target foo
after...
注意:
jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系
2、Cglib代理
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, new MethodInterceptor() {
@Override
public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before...");
//用反射调用目标方法
Object result = method.invoke(target);
System.out.println("after...");
return result;
}
});
proxy.foo();
}
}
结果:
before...
target foo
after...
注意:
cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
根据上述分析 final 类,final方法无法被 cglib 增强,因为是子父类的关系,相当于代理类对目标类方法的重写。
invoke 与 invokeSuper的区别
与jdk动态代理不同的是,cglib可以避免使用反射调用目标方法。
使用methodProxy的invoke 或 invokeSuper方法
public static void main(String[] param) {
Target target = new Target();
Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before...");
Object result = methodProxy.invoke(target, args);
System.out.println("after...");
return result;
}
});
proxy.foo();
}
两者区别
//需要目标对象
methodProxy.invoke(target, args)
//需要代理对象
methodProxy.invokeSuper(p, args);
spring使用的是invoke方法
四、JDK动态代理进阶
学习目标:代理类的内部原理
- 方法重写可以增强逻辑
- 通过接口回调将【增强逻辑】置于代理类之外
- 配合接口方法反射(也是多态),就可以再联动调用目标方法
1、模拟JDK动态代理
public class A12 {
interface Foo {
void foo();
int boo();
}
//目标类
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
@Override
public int boo() {
System.out.println("target boo");
return 100;
}
}
//提供动态执行增强逻辑的方法
interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
}
代理类
public class $Proxy0 implements Foo {
private InvocationHandler invocationHandler;
public $Proxy0() {
}
public $Proxy0(InvocationHandler invocationHandler) {
this.invocationHandler = invocationHandler;
}
static Method foo;
static Method boo;
static {
try {
//根据方法名获取Method属性
foo = Foo.class.getMethod("foo");
boo = Foo.class.getMethod("boo");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@Override
public void foo() {
try {
invocationHandler.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
}catch (Throwable e){
throw new UndeclaredThrowableException(e);
}
}
@Override
public int boo() {
try {
int result = (int) invocationHandler.invoke(this, boo, new Object[0]);
return result;
} catch (RuntimeException | Error e) {
throw e;
}catch (Throwable e){
throw new UndeclaredThrowableException(e);
}
}
}
这里说明一下异常处理:
- 运行时异常:RuntimeException,Error,无需捕获,直接抛出
- 检查异常:Throwable,接口不一定有Throwable,需要将检查异常转换为运行时异常抛出
测试
Foo foo = new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before...");
Object result = method.invoke(target);
System.out.println("after...");
return result;
}
});
foo.foo();
foo.boo();
结果:
before...
target foo
after...
before...
target boo
after...
JDK生成代理类时并没有经历源码阶段,编译阶段,而是直接进入字节码阶段,现在看到的java源码是通过Arthas工具对它进行了反编译, 直接生成字节码的底层技术是ASM,被广泛应用于JDK,Spring等框架,它的作用就是在运行期间动态生成字节码。
和我们自己写的代理类进行比较发现比我们多实现了Object中的toString,equals,hashCode方法,并且继承了Proxy类。
package com.itheima.a11;
import com.itheima.a11.JdkProxyDemo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class $Proxy0 extends Proxy implements JdkProxyDemo.Foo {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.itheima.a11.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void foo() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
Proxy类内部已经定义了 InvocationHandler
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
}
注意:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现
2、方法反射优化
由于ASM学习成本比较高,这里直接给出结论,感兴趣的小伙伴可以去看视频。
优化:
-
前 16 次反射性能较低
-
第 17 次调用会生成代理类,优化为非反射调用
五、cglib 代理进阶
和 JDK 动态代理原理查不多
-
回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
-
调用目标时有所改进,见下面代码片段
-
method.invoke 是反射调用,必须调用到足够次数才会进行优化
-
methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
-
methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
-
1、模拟cglib代理
目标类
public class Target {
public void save() {
System.out.println("save()");
}
public void save(int i) {
System.out.println("save(int)");
}
public void save(long j) {
System.out.println("save(long)");
}
}
代理类
public class Proxy extends Target{
private MethodInterceptor methodInterceptor;
public Proxy() {
}
public Proxy(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method save0;
static Method save1;
static Method save2;
static {
try {
save0 = Target.class.getMethod("save");
save1 = Target.class.getMethod("save", int.class);
save2 = Target.class.getMethod("save", long.class);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@Override
public void save() {
try {
methodInterceptor.intercept(this, save0, new Object[0], null);
} catch (RuntimeException | Error e){
throw e;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, null);
} catch (RuntimeException | Error e){
throw e;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public void save(long j) {
try {
methodInterceptor.intercept(this, save2, new Object[]{j}, null);
} catch (RuntimeException | Error e){
throw e;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
测试
public class A13 {
public static void main(String[] args) {
Target target = new Target();
Proxy proxy = new Proxy(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before...");
return method.invoke(target, args);
}
});
proxy.save();
proxy.save(1);
proxy.save(2L);
}
}
结果:
before...
save()
before...
save(int)
before...
save(long)
上面的代码其实和模拟JDK动态代理的代码差不多,重点是下面的避免反射调用的代码。
2、cglib 避免反射调用
给代理类增加原始功能的方法以及MethodProxy
public class Proxy extends Target{
private MethodInterceptor methodInterceptor;
public Proxy() {
}
public Proxy(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method save0;
static Method save1;
static Method save2;
static MethodProxy save0Proxy;
static MethodProxy save1Proxy;
static MethodProxy save2Proxy;
static {
try {
save0 = Target.class.getMethod("save");
save1 = Target.class.getMethod("save", int.class);
save2 = Target.class.getMethod("save", long.class);
/*
参数一:目标类型 参数二:代理类型
参数三:方法参数描述符
参数四:带增强功能的方法名 参数五:带原始功能的方法名
*/
save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
//>>>>>>>>>>>>>>>>>>>>>>>带原始功能的方法
public void saveSuper(){
super.save();
}
public void saveSuper(int i){
super.save(i);
}
public void saveSuper(long j){
super.save(j);
}
//>>>>>>>>>>>>>>>>>>>>>>>带增强功能的方法
@Override
public void save() {
try {
methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
} catch (RuntimeException | Error e){
throw e;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
} catch (RuntimeException | Error e){
throw e;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
@Override
public void save(long j) {
try {
methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);
} catch (RuntimeException | Error e){
throw e;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类
- ProxyFastClass 配合代理对象一起使用, 避免反射
- TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)
这两个类会继承FastClass抽象类,为了演示简单,下面的getIndex 和 invoke是实现它的两个方法。
1)invoke方法的无反射演示
public class TargetFastClass {
//方法签名
static Signature s0 = new Signature("save","()V");
static Signature s1 = new Signature("save","(I)V");
static Signature s2 = new Signature("save","(J)V");
// 获取目标方法的编号
/*
Target
save() 0
save(int) 1
save(long) 2
signature 包括方法名字、参数返回值
*/
public int getIndex(Signature signature) {
if (signature.equals(s0)){
return 0;
} else if (signature.equals(s1)){
return 1;
} else if (signature.equals(s2)){
return 2;
}
return -1;
}
// 根据方法编号, 正常调用目标对象方法
public Object invoke(int index, Object target, Object[] args) {
if (index == 0){
((Target) target).save();
return null;
} else if (index == 1){
((Target) target).save((int) args[0]);
return null;
}else if (index == 2){
((Target) target).save((long) args[0]);
return null;
}else {
throw new RuntimeException("无此方法");
}
}
//模拟操作
public static void main(String[] args) {
//首次使用MethodProxy方法时被创建
TargetFastClass fastClass = new TargetFastClass();
//MethodProxy在创建时由于记录了方法签名,所以能够调用getIndex方法得到方法编号
int i = fastClass.getIndex(new Signature("save", "()V"));
//调用MethodProxy的invoke方法,间接会调用到fastClass的invoke方法
fastClass.invoke(i, new Target(), new Object[0]);
}
}
2)invokeSuper方法的无反射演示
public class ProxyFastClass {
/*
调用的是代理类带原始功能的方法而不是增强方法,不然会陷入死循环
*/
static Signature s0 = new Signature("saveSuper","()V");
static Signature s1 = new Signature("saveSuper","(I)V");
static Signature s2 = new Signature("saveSuper","(J)V");
// 获取代理方法的编号
/*
Target
saveSuper() 0
saveSuper(int) 1
saveSuper(long) 2
signature 包括方法名字、参数返回值
*/
public int getIndex(Signature signature) {
if (signature.equals(s0)){
return 0;
} else if (signature.equals(s1)){
return 1;
} else if (signature.equals(s2)){
return 2;
}
return -1;
}
// 根据方法编号, 正常调用目标对象方法
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0){
((Proxy) proxy).saveSuper();
return null;
} else if (index == 1){
((Proxy) proxy).saveSuper((int) args[0]);
return null;
}else if (index == 2){
((Proxy) proxy).saveSuper((long) args[0]);
return null;
}else {
throw new RuntimeException("无此方法");
}
}
public static void main(String[] args) {
ProxyFastClass fastClass = new ProxyFastClass();
int i = fastClass.getIndex(new Signature("saveSuper", "()V"));
fastClass.invoke(i, new Proxy(), new Object[0]);
}
}
总结
为什么有这么麻烦的一套东西呢?
-
避免反射,提高性能,代价是一个代理类配两个 FastClass 类,代理类中还得增加仅调用 super 的一堆方法
-
用编号处理方法对应关系比较省内存,另外,最初获得方法顺序是不确定,这个过程没法固定死
与JDK动态代理相比,CGLIB代理类数目相对较少,只有两个类。而JDK动态代理在调用到第十七次后会生成代理类去优化为非反射调用,并且是一个方法对应一个代理类。