目录
2.1 方法一:method.invoke() 方法反射调用
2.2 方法二(spring使用的这个方法): methodProxy.invoke()
2.3 方法三:methodProxy.invokeSuper()
七、cglib动态代理如何避免反射——methodProxy原理
一、jdk动态代理的基本使用
在下面的模拟中,我们的代理目标是Target类,他实现了Foo接口。在main方法中,我们模拟jdk实现动态代理的方法,来模拟实现AOP代理增强。
需要注意的一点是:jdk只能针对接口代理。
public class JdkProxyDemo {
interface Foo {
void foo();
}
/**
* 目标类,实现了Foo接口。 jdk只能针对接口代理
*/
static final class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) throws IOException {
// 目标对象
Target target = new Target();
// 用来加载在运行期间动态生成的字节码。 因为代理类并没有.java的源代码,他是在运行期直接生成的字节码
ClassLoader loader = JdkProxyDemo.class.getClassLoader();
/**
* newProxyInstance生成一个代理实例。
* 参数:1、类加载器;2、要实现的接口;3、InvocationHandler,用于规定所实现接口的增强方法的行为
*/
//因为Proxy实例实现了Foo接口,所以可以强转为Foo类型
Foo proxy = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (p, method, args) -> {
// 模拟前置增强
System.out.println("before...");
// 反射调用,让target执行method方法。 普通调用:目标.方法(参数); 反射调用:方法.invoke(目标, 参数);
Object result = method.invoke(target, args);
// 模拟后置增强
System.out.println("after....");
// 让代理也返回目标方法执行的结果
return result;
});
proxy.foo();
}
}
运行结果:
before...
target foo
after....
需要注意:代理对象Proxy 和 代理目标Target是兄弟关系,他们都实现了Foo接口。所以,目标类Target也可以是final类型,这点与cglib实现的动态代理不同。
二、cglib动态代理的基本使用
与jdk的实现不同,cglib的实现是基于父子类的,代理类作为目标类的子类,因此目标类和目标方法不能是final类型。
除了用method反射调用外,cglib还提供了methodProxy参数来避免反射调用,从而提升效率(原理在本文第七章中有讲解)。因此,cglib主要有三种实现方式。
在下面的实例中,目标类为Target。
2.1 方法一:method.invoke() 方法反射调用
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
// 代理是子类型, 目标是父类型
public static void main(String[] param) {
// 目标对象
Target target = new Target();
/**
* 创建一个代理类
* 参数:1、目标类本身; 2、回调函数,决定代理类中方法执行的行为
* 回调函数的参数:1、代理类对象本身; 2、当前被代理类中执行的方法; 3、方法执行的实际参数
*/
// 子类型的对象可以转换为父类型,所以Proxy可以转换为Target
Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {
//模拟前置增强
System.out.println("before...");
// 方法一:用方法反射调用目标
Object result = method.invoke(target, args);
//模拟后置增强
System.out.println("after...");
return result;
});
proxy.foo();
}
}
2.2 方法二(spring使用的这个方法): methodProxy.invoke()
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
// 代理是子类型, 目标是父类型
public static void main(String[] param) {
Target target = new Target();
/**
* 创建一个代理类
* 参数:1、目标类本身; 2、回调函数,决定代理类中方法执行的行为
* 回调函数的参数:1、代理类对象本身; 2、当前被代理类中执行的方法; 3、方法执行的实际参数; 4、methodProxy可以避免反射调用目标
*/
// 子类型的对象可以转换为父类型,所以Proxy可以转换为Target
Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {
//模拟前置增强
System.out.println("before...");
// methodProxy 可以避免反射调用
// 内部没有用反射, 需要目标(spring使用的是这种方法)
Object result = methodProxy.invoke(target, args);
//模拟后置增强
System.out.println("after...");
return result;
});
proxy.foo();
}
}
2.3 方法三:methodProxy.invokeSuper()
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
// 代理是子类型, 目标是父类型
public static void main(String[] param) {
/**
* 创建一个代理类
* 参数:1、目标类本身; 2、回调函数,决定代理类中方法执行的行为
* 回调函数的参数:1、代理类对象本身; 2、当前被代理类中执行的方法; 3、方法执行的实际参数; 4、methodProxy可以避免反射调用目标
*/
// 子类型的对象可以转换为父类型,所以Proxy可以转换为Target
Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {
//模拟前置增强
System.out.println("before...");
// 内部没有用反射, 需要代理类对象,不需要目标类对象
Object result = methodProxy.invokeSuper(p, args);
//模拟后置增强
System.out.println("after...");
return result;
});
proxy.foo();
}
}
三、jdk实现代理的原理
jdk实现代理的源码在Proxy.newProxyInstance() 中,它是通过ASM动态地生成代理类的,我们看不到代理类对应的Java代码,想要阅读他的源码并不容易。因此,我们抛开jdk的源码,思考一下如果代理的原理,如果我们自己实现,应该如何去实现;然后再去阅读jdk源码,这样可能会有更多的收获。
我这里准备好了一段代码:
public class A13 {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] args) {
}
}
代码中有一个需要增强的目标类Target,他实现了Foo接口,下一步就要使用代理类来增强它了。在这里我不使用jdk的代理类,而是手写一个代理类。根据前面一中的介绍,jdk实现代理类需要和目标类实现共同的接口:
public class $Proxy0 implements Foo {
@Override
public void foo() {
// 1.功能增强
System.out.println("before...");
// 2.调用目标
new Target().foo();
}
}
再在main方法中使用代理类调用方法:
public class A13 {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] args) {
Foo proxy = new $Proxy0();
proxy.foo();
}
}
运行结果:
before...
target foo
这样我们就实现了基本的代理增强。但是问题也随之而来:我们的代理类目前是把功能增强的逻辑和调用目标的逻辑写死在类中,但在实际的项目中, 我们并不会固定增强的功能,比如我们可能要做日志增强,也可能要做权限验证的增强;同样的,调用目标也不会固定,我们不一定会只调foo方法,也可能会调其他方法,甚至不一定会调方法(比如做权限认证,权限通过了才调方法,权限认证没通过,就可能抛出异常或者做其他处理了)。总之,这样不确定的代码我们不应该放在代理内部,而应该写成抽象方法,剥离到类外。
于是我们建一个InvocationHandler接口,声明invoke方法,将功能增强和调用目标的逻辑写在其中:
public class A13 {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}
interface InvocationHandler {
void invoke();
}
public static void main(String[] args) {
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke() {
// 1.功能增强
System.out.println("before...");
// 2.调用目标
new Target().foo();
}
});
proxy.foo();
}
}
public class $Proxy0 implements Foo {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
this.h = h;
}
@Override
public void foo() {
h.invoke();
}
}
其实这样代理类也无需知道目标是谁了,只要实现父接口即可。
接下来的问题就是调用方法了,不一定每次调的都是foo方法,比如Foo接口当中加入了其他的方法。所以我们的代理类要能够知道调用目标类的哪个方法。这里就需要用到反射了,我们可以通过反射拿到当前调用的方法是哪一个。
public class A13 {
interface Foo {
void foo();
void bar();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
@Override
public void bar() {
System.out.println("target bar");
}
}
interface InvocationHandler {
void invoke(Method method, Object []args) throws Throwable;
}
public static void main(String[] args) {
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
// 1.功能增强
System.out.println("before...");
// 2.调用目标
// new Target().foo();
method.invoke(new Target(), args);
}
});
proxy.foo();
proxy.bar();
}
}
public class $Proxy0 implements Foo {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
this.h = h;
}
@Override
public void foo() {
try {
Method foo = Foo.class.getMethod("foo");
h.invoke(foo, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public void bar() {
try {
Method bar = Foo.class.getMethod("bar");
h.invoke(bar, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
运行结果:
before...
target foo
before...
target bar
这样我们就基本上解决了调用目标方法的问题了。我们再将方法改进一下,让他能针对有返回值的方法,并将异常的处理也打磨一下:
public class A13 {
interface Foo {
void foo();
int bar();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
@Override
public int bar() {
System.out.println("target bar");
return 100;
}
}
interface InvocationHandler {
/**
* 回调函数
* @param proxy 代理类本身
* @param method 调用代理的方法
* @param args 方法的参数
* @return method的返回值
*/
Object invoke(Object proxy, Method method, Object []args) throws Throwable;
}
public static void main(String[] args) {
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
// 1.功能增强
System.out.println("before...");
// 2.调用目标
// new Target().foo();
Object result = method.invoke(new Target(), args);
return result;
}
});
proxy.foo();
proxy.bar();
}
}
public class $Proxy0 implements Foo {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
this.h = h;
}
@Override
public void foo() {
try {
Method foo = Foo.class.getMethod("foo");
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 {
Method bar = Foo.class.getMethod("bar");
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);
}
}
}
现在还有一个小问题:每次我们调用代理类方法的时候,都要去获取一次正在调用的方法:
这样显然是没必要的,因此我们可以用静态变量和静态代码块,只获取一次方法,就可以一直使用了:
public class $Proxy0 implements Foo {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
this.h = h;
}
@Override
public void foo() {
try {
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 = Foo.class.getMethod("foo");
bar = Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
//检查异常不能直接抛 要转换成运行时异常再抛
//静态代码块出错是很严重的问题 所以我们用error来表示严重的问题
throw new NoSuchMethodError(e.getMessage());
}
}
}
到此为止,我们的InvocationHandler已经和jdk 的InvocationHandler无异,我们可以直接使用jdk的;代理类也与jdk的只有细微差别,jdk的$Proxy0 会继承Proxy 类,进一步减少代码,像这样:
import java.lang.reflect.InvocationHandler;
public class $Proxy0 extends Proxy implements Foo {
public $Proxy0(InvocationHandler h) {
super(h);
}
}
四、jdk实现代理的源码
JDK动态代理生成的代理类是以字节码的形式存在的,并不存在所谓的.java文件,但也不是说就没办法看到生成的代理类信息了。因此我们需要借助反编译工具(例如:Arthas)才能看到Java代码。
下面就是jdk 生成的代理类源码。可以看出跟我们手写的代理类基本一致,只不过JDK生成的代理类信息还生成equals() 、 toString()和hashCode()三个方法对应的Method对象,并对它们也进行了相同的增强。
经过前面的手写代理类,相信看懂源码已经是十分简单的事情了。
final class $Proxy0 extends Proxy implements Foo {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void foo() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("indi.mofan.a12.JdkProxyDemo$Foo").getMethod("foo");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
五、jdk对代理的优化
我们知道使用JDK的动态代理时,会使用反射调用方法:
Object result = method.invoke(target, params);
相比于正常调用方法,利用反射的性能要稍微低一些,JDK有对反射进行优化吗?我们来实验一下。
/**
* @author mofan
* @date 2023/1/16 22:36
*/
public class TestMethodProxy {
public static void main(String[] args) throws Exception {
Method foo = TestMethodProxy.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;
}
// DelegatingMethodAccessorImpl 的全限定类名(不同版本的 JDK 存在差异)
Field delegate = Class.forName("sun.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");
}
}
运行结果:
1: null
1: foo
2: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
2: foo
3: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
3: foo
4: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
4: foo
5: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
5: foo
6: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
6: foo
7: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
7: foo
8: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
8: foo
9: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
9: foo
10: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
10: foo
11: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
11: foo
12: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
12: foo
13: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
13: foo
14: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
14: foo
15: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
15: foo
16: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
16: foo
17: sun.reflect.GeneratedMethodAccessor2@5b2133b1
17: foo
从上述信息可知,第一次调用时没有使用MethodAccessor对象,是因为这个时候刚刚生成;从第二次到第十六次,使用了NativeMethodAccessorImpl对象,而在第十七次使用了GeneratedMethodAccessor2对象。
NativeMethodAccessorImpl 基于Java本地API实现,性能较低,第十七次调用换成GeneratedMethodAccessor2后,性能得到一定的提升。
使用Arthas反编译查看 GeneratedMethodAccessor2类中的信息,内容如下:
public class GeneratedMethodAccessor2 extends MethodAccessorImpl {
/*
* Loose catch block
*/
public Object invoke(Object object, Object[] objectArray) throws InvocationTargetException {
// --snip--
try {
// 正常调用方法
TestMethodProxy.foo((int)c);
return null;
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
catch (ClassCastException | NullPointerException runtimeException) {
throw new IllegalArgumentException(super.toString());
}
}
}
在反编译得到的代码中,不再是通过反射调用方法,而是直接正常调用方法,即:
TestMethodProxy.foo((int)c);
因此性能得到了提升,但这样的提升也是有一定代价的:仅仅为优化一个方法的反射调用,就生成了一个GeneratedMethodAccessor2代理类。
六、cglib实现动态代理的原理
看懂了jdk,可以发现cglib跟它大同小异。我们跟学习jdk的时候一样,手写代理类来加深对原理的理解。
先准备一个目标类。
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)");
}
}
然后是代理类。jdk通过 InvocationHandler来实现增强逻辑,cglib通过 MethodInterceptor实现增强逻辑,两者区别不大,我们可以看看MethodInterceptor 的源码:
public interface MethodInterceptor extends Callback {
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
这是一个接口,我们在测试类中实现它。
下面是我们实现的代理类:
public class Proxy extends Target{
//jdk通过InvocationHandler来实现增强逻辑,cglib通过MethodInterceptor实现增强逻辑
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(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 (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(long j) {
try {
methodInterceptor.intercept(this, save2, new Object[]{j}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
在主函数中测试一下:
public class A14 {
public static void main(String[] args) {
Target target = new Target();
Proxy proxy = new Proxy();
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object o, 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)
七、cglib动态代理如何避免反射——methodProxy原理
7.1 methodProxy的使用
要使用methodProxy,我们不仅需要带有增强功能的方法,还需要仅带有原始功能的方法。
public class Proxy extends Target{
//jdk通过InvocationHandler来实现增强逻辑,cglib通过MethodInterceptor实现增强逻辑
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(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());
}
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有原始功能的方法
//因为不能和save同名,我们换个名字,换成saveSuper
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], null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(long j) {
try {
methodInterceptor.intercept(this, save2, new Object[]{j}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
调用MethodProxy.create() 方法创建一个MethodProxy对象,我们也像处理Method对象一样,用一个静态成员变量来接收。创建好了之后,将这个静态变量传入intercept方法中即可
public class Proxy extends Target{
//jdk通过InvocationHandler来实现增强逻辑,cglib通过MethodInterceptor实现增强逻辑
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);
// create的参数:1、目标类;2、代理类;3、增强方法的描述;4、增强方法名;5、原始方法名
//()V : ()表示入参是空,V表示返回值是void
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());
}
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带有原始功能的方法
//因为不能和save同名,我们换个名字,换成saveSuper
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);
}
}
}
在主函数中的使用是我们本文二中已经讲到的:
public class A14 {
public static void main(String[] args) {
Target target = new Target();
Proxy proxy = new Proxy();
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 功能增强
System.out.println("before...");
// 反射调用
// return method.invoke(target, args);
// 结合目标对象来调用,内部没有用反射
// return methodProxy.invoke(target, args);
// 结合代理对象来调用,内部没有用反射
return methodProxy.invokeSuper(p, args);
}
});
proxy.save();
proxy.save(1);
proxy.save(2L);
}
}
7.2 methodProxy不使用反射的原理
我们知道,methodProxy.invoke 和 methodProxy.invokeSuper 是不需要反射的,那么他们的原理是什么呢?这与FastClass有关,FastClass本质上也是代理类,在他的实现类中的一些关键方法能够做到无需反射。而FastClass也是直接生成的字节码,我们也无法看到他的源码,因此我们来模拟一下实现FastClass。
先模拟实现TargetFastClass。
当调用了MethodProxy.create的时候,就会根据字节码生成对应的TargetFastClass,就可以根据TargetFastClass 中的getIndex方法获得方法对应的编号;而在调用Method.invoke方法时,内部会调用TargetFastClass的invoke 方法,这样就能知道要调用哪个方法了,而无需反射调用。
public class TargetFastClass extends FastClass {
//先定义好编号对应的方法签名
static Signature s0 = new Signature("save", "()V");
static Signature s1 = new Signature("save", "(I)V");
static Signature s2 = new Signature("save", "(J)V");
/**
* 获取目标方法的编号。我们可以事先规定好Target类中所有方法对应的编号,通过此方法拿到
* 例如,Target中的方法,我们可以这样编号:
* save() 0
* save(int i) 1
* save(long j) 2
*
* @param signature 方法签名
* @return 方法编号
*/
@Override
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
//返回方法编号0
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
}
return -1;
}
/**
* 根据getIndex 返回的方法编号,正常调用对应方法
* @param i getIndex返回的方法编号
* @param o 目标对象
* @param objects 方法的参数
* @return 方法的返回结果
* @throws InvocationTargetException
*/
@Override
public Object invoke(int i, Object o, Object[] objects) throws InvocationTargetException {
if (i == 0) {
((Target)o).save();
return null;
} else if (i == 1) {
((Target)o).save((Integer) objects[0]);
return null;
} else if (i == 2) {
((Target)o).save((Long) objects[0]);
return null;
} else {
throw new RuntimeException("没有此方法");
}
}
//>>>>>>>>>>>>>>>>>>下面是相对来说不重要的方法,就不研究了
@Override
public int getIndex(String s, Class[] classes) {
return 0;
}
@Override
public int getIndex(Class[] classes) {
return 0;
}
@Override
public Object newInstance(int i, Object[] objects) throws InvocationTargetException {
return null;
}
@Override
public int getMaxIndex() {
return 0;
}
}
同样地,也有和Proxy配合的ProxyFastClass:
public class ProxyFastClass {
// 这里应该用原始方法,因为我们这里是需要配合MethodProxy.invoke方法,在此方法之前已经进行了功能增强,所以只需要原始方法即可
// 同时,这里不能用增强的方法,只能用原始方法,因为增强方法中本身要调用intercept,而intercept方法中又需要用到ProxyFastClass,会形成死循环
static Signature s0 = new Signature("saveSuper", "()V");
static Signature s1 = new Signature("saveSuper", "(I)V");
static Signature s2 = new Signature("saveSuper", "(J)V");
// 获取代理方法的编号
/*
Proxy
saveSuper() 0
saveSuper(int) 1
saveSuper(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).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("没有此方法");
}
}
}
7.3 与jdk反射优化的性能对比
jdk当中也对反射做了性能优化,原理与cglib大致相同,不过jdk需要进行16次调用,从第17次开始才做优化,并且针对一个方法产生一个代理类。
cglib的优化在于,在第一次调用的时候就无需反射,并且一个代理类对应生成两个FastClass(本质上也是代理类),一个用于配合目标类,一个用于配合代理类,一个FastClass中会对应多个方法。总体而言,生成代理类的数量会比jdk少。