一.我们都知道spring的aop是由动态代理来实现的,而动态代理一般有2种实现jdk自带的和cglib动态代理。
1.先用代码分别演示一下2种实现:
基于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();
// 代理对象
Foo proxy = (Foo) Proxy.newProxyInstance(
Target.class.getClassLoader(), new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
-
jdk 动态代理要求目标必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系
基于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,
(MethodInterceptor) (p, method, args, methodProxy) -> {
System.out.println("proxy before...");
Object result = methodProxy.invoke(target, args);
// 另一种调用方法,不需要目标对象实例
// Object result = methodProxy.invokeSuper(p, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
cglib 不要求目标实现接口,它生成的代理类是目标的子类,因此代理与目标之间是子父关系
限制⛔:根据上述分析 final 类无法被 cglib 增强
2.接下来模拟jdk的动态代理实现
接口、目标定义:
interface Foo {
void foo();
int bar();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
public int bar() {
System.out.println("target bar");
return 100;
}
}
public static void main(String[] param) {
// ⬇️1. 创建代理,这时传入 InvocationHandler
Foo proxy = new $Proxy0(new InvocationHandler() {
// ⬇️5. 进入 InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// ⬇️6. 功能增强
System.out.println("before...");
// ⬇️7. 反射调用目标方法
return method.invoke(new Target(), args);
}
});
// ⬇️2. 调用代理方法
proxy.foo();
proxy.bar();
}
代理实现:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
// ⬇️这就是 jdk 代理类的源码, 秘密都在里面
public class $Proxy0 extends Proxy implements A12.Foo {
public $Proxy0(InvocationHandler h) {
super(h);
}
// ⬇️3. 进入代理方法
public void foo() {
try {
// ⬇️4. 回调 InvocationHandler
h.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public int bar() {
try {
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
static Method foo;
static Method bar;
static {
try {
foo = A12.Foo.class.getMethod("foo");
bar = A12.Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
}
方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
通过接口回调将【增强逻辑】置于代理类之外
配合接口方法反射(是多态调用),就可以再联动调用目标方法
会用 arthas 的 jad 工具反编译代理类
限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现
其实本质上就是采用InvocationHandler接口使用其的匿名内部类,给出相对应的加强方法。同时这个动态代理类其实本质上并不是实际存在的文件而是spring根据asm技术(字节码生成技术)来动态生成的,动态加载的。我们可以用阿里开源的arthas来观察。
3.另外就是jdk自带代理的反射调用优化了
因为反射调用其实是非常有损性能的(反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。)jdk对反射的反复调用进行了优化:
-
前 16 次反射性能较低
-
第 17 次调用会生成代理类,优化为非反射调用
-
会用 arthas 的 jad 工具反编译第 17 次调用生成的代理类
可以用下面的代码进行测试
package com.itheima.a12;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
// 运行时请添加 --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
public class TestMethodInvoke {
public static void main(String[] args) throws Exception {
Method foo = TestMethodInvoke.class.getMethod("foo", int.class);
for (int i = 1; i <= 17; i++) {
show(i, foo);
foo.invoke(null, i);
}
System.in.read();
}
// 方法反射调用时, 底层 MethodAccessor 的实现类
private static void show(int i, Method foo) throws Exception {
Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
getMethodAccessor.setAccessible(true);
Object invoke = getMethodAccessor.invoke(foo);
if (invoke == null) {
System.out.println(i + ":" + null);
return;
}
Field delegate = Class.forName("jdk.internal.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
delegate.setAccessible(true);
System.out.println(i + ":" + delegate.get(invoke));
}
public static void foo(int i) {
System.out.println(i + ":" + "foo");
}
}
4.cglib代理实现
target类
public class Target {
public void save(){
System.out.println("save");
}
public void save(int i){
System.out.println("saveInt" + i);
}
public void save(long j){
System.out.println("saveLong" + j);
}
}
代理类实现
public class Proxy extends Target {
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(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 (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(long j) {
try {
methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
MethodProxy实现:基于Fastclass
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
save() 0
save(int) 1
save(long) 2
signature 包括方法名字、参数返回值
*/
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
}
return -1;
}
// 根据方法编号, 正常调用目标对象方法
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0) {
((Proxy) proxy).save();
} else if (index == 1) {
int i = (int) args[0];
((Proxy) proxy).save(i);
} else if (index == 2) {
long i = (long) args[0];
((Proxy) proxy).save(i);
}else {
throw new RuntimeException();
}
return null;
}
}
-
当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类
-
ProxyFastClass 配合代理对象一起使用, 避免反射
-
TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)
-
-
TargetFastClass 记录了 Target 中方法与编号的对应关系
-
save(long) 编号 2
-
save(int) 编号 1
-
save() 编号 0
-
首先根据方法名和参数个数、类型, 用 switch 和 if 找到这些方法编号
-
然后再根据编号去调用目标方法, 又用了一大堆 switch 和 if, 但避免了反射
-
-
ProxyFastClass 记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法
-
saveSuper(long) 编号 2,不增强,仅是调用 super.save(long)
-
saveSuper(int) 编号 1,不增强, 仅是调用 super.save(int)
-
saveSuper() 编号 0,不增强, 仅是调用 super.save()
-
查找方式与 TargetFastClass 类似
-
-
为什么有这么麻烦的一套东西呢?
-
避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法
-
用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死
-
二:另外的可能aop实现
1.ajc 编译器
通过 ajc 编译器在编译 class 类文件时,就把通知的增强功能,织入到目标类的字节码中
-
编译器也能修改 class 实现增强
-
编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
优点:
-
aspectj 在编译和加载时,修改目标字节码,性能较高
-
aspectj 因为不用代理,能突破一些技术上的限制,例如对构造、对静态方法、对 final 也能增强
缺点:
- 但 aspectj 侵入性较强,且需要学习新的 aspectj 特有语法,因此没有广泛流行
2.agent 类加载
类加载时可以通过 agent 修改 class 实现增强
三:spring采用jdk和cglib相结合的实现
首先aop的包括切点(接口为Pointcut,典型实现 AspectJExpressionPointcut )和通知(典型接口为 MethodInterceptor 代表环绕通知 ),将切点和通知组合就形成了切面(接口为Advisor ),然后采用代理工厂来根据切面生成代理,流程如下:
-
AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现
-
AopProxy 通过 getProxy 创建代理对象
-
图中 Proxy 都实现了 Advised 接口,能够获得关联的切面集合与目标(其实是从 ProxyFactory 取得)
-
调用代理方法时,会借助 ProxyFactory 将通知统一转为环绕通知:MethodInterceptor
于是代理的实现选择有如下规则:
ProxyFactory 用来创建代理
-
如果指定了接口,且 proxyTargetClass = false,使用 JdkDynamicAopProxy
-
如果没有指定接口,或者 proxyTargetClass = true,使用 ObjenesisCglibAopProxy
-
例外:如果目标是接口类型或已经是 Jdk 代理,使用 JdkDynamicAopProxy
四:代理需要注意的地方
1.spring 代理的设计特点
依赖注入和初始化影响的是原始对象。因此 cglib 不能用 MethodProxy.invokeSuper()
代理与目标是两个对象,二者成员变量并不共用数据
2.static 方法、final 方法、private 方法均无法增强
进一步理解代理增强基于方法重写