开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
JDK代理简单实现
这里我们模拟JDK代理实现的方式来对其原理进行解析,它的底层是使用了ASM的技术来处理的,这里先不讨论这个。我们用java代码的方式尝试还原整体逻辑。
public class A08 {
interface Foo {
void foo();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("foo执行");
}
}
public static void main(String[] args) {
$Proxy0 proxy0 = new $Proxy0();
proxy0.foo();
}
}
public class $Proxy0 implements A08.Foo{
@Override
public void foo() {
System.out.println("before....");
A08.Target target = new A08.Target();
target.foo();
System.out.println("after.....");
}
}
这里案例代码和上节中的案例比较相似,不过我们这里自己建立了一个代理类,起名格式也是模仿代理类的命名,和目标类实现了同一个接口,并实现其中的方法做增强。这里没有特别的地方,执行main方法后就会打印出$Proxy0类中的foo方法逻辑。
这里只是一个很简单的实现,针对这种简单的实现,我们引出一个问题,就是将来有可能会有众多的增强逻辑,肯定不能全靠这种方式写到代理类中。那么我们就要使用某种方法来解决这个问题,这里引出上节创建JDK代理时的一个重要参数:InvocationHandler。
自定义InvocationHandler
public class A08 {
interface Foo {
void foo();
}
interface InvocationHandler {
void invoke();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("foo执行");
}
}
public static void main(String[] args) {
$Proxy0 proxy0 = new $Proxy0(() -> {
System.out.println("before.....");
new Target().foo();
System.out.println("after.....");
});
proxy0.foo();
}
}
public class $Proxy0 implements Foo{
private InvocationHandler invocationHandler;
public $Proxy0(InvocationHandler invocationHandler) {
this.invocationHandler = invocationHandler;
}
@Override
public void foo() {
invocationHandler.invoke();
}
}
如上代码,我们将最上面的简单案例修改了一下,我们模仿JDK代理的方式建了一个自定义的且同名的InvocationHandler接口,里面也有一个invoke方法。然后我们在代理类中增加该接口的参数和构造器。我们在创建$Proxy0类的时候通过该构造器传入一个InvocationHandler接口的实现,实现里写入了具体的增强逻辑。通过这样一套流程的调用,我们就摆脱了在代理类中写增强逻辑。
引出新问题,如果我们在Foo接口中添加多个方法,如果代理类中使用同样的方法来调用,那么,由于InvocationHandler实现方法中写死的调用foo方法,那么不管有多少方法,最终调用的都只是foo方法。
Method获取
结合上一步的问题,我们回想上节InvocationHandler的invoke方法的三个参数中,有一个method参数,还有个method的参数的参数。想到这里,就明白这两个参数就是为了解决上面所说的问题。
public class A08 {
interface Foo {
void foo();
void bar();
}
interface InvocationHandler {
void invoke(Method method, Object[] object) throws Throwable;
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("foo执行");
}
@Override
public void bar() {
System.out.println("bar执行");
}
}
public static void main(String[] args) {
$Proxy0 proxy0 = new $Proxy0((method, args1) -> {
System.out.println("before.....");
method.invoke(new Target(), args1);
System.out.println("after.....");
});
proxy0.foo();
proxy0.bar();
}
}
public class $Proxy0 implements Foo {
private InvocationHandler invocationHandler;
public $Proxy0(InvocationHandler invocationHandler) {
this.invocationHandler = invocationHandler;
}
@Override
public void foo() {
try {
Method foo = Foo.class.getMethod("foo");
invocationHandler.invoke(foo, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public void bar() {
try {
Method foo = Foo.class.getMethod("bar");
invocationHandler.invoke(foo, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
}
我们根据所说逻辑再次修改后,得到了上面的代码。在代理类中的每个方法中,获取接口方法本身,传到invoke方法中,这时我们就不是通过创建目标对象来进行方法调用了,而是根据传回的方法,进行反射调用。
代理实现优化
我们发现我们上面写的代码内容和JDK本身的还是有些区别,我们再做一步优化,直接上代码。
public class A08 {
interface Foo {
void foo();
int bar();
}
static class Target implements Foo {
@Override
public void foo() {
System.out.println("foo执行");
}
@Override
public int bar() {
System.out.println("bar执行");
return 0;
}
}
public static void main(String[] args) {
$Proxy0 proxy0 = new $Proxy0((proxy, method, args1) -> {
System.out.println("before.....");
Object invoke = method.invoke(new Target(), args1);
System.out.println("after.....");
return invoke;
});
proxy0.foo();
proxy0.bar();
}
}
public class $Proxy0 extends Proxy implements Foo {
public $Proxy0(InvocationHandler h) {
super(h);
}
static Method foo;
static Method bar;
static {
try {
foo = Foo.class.getMethod("foo");
bar = Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@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 invoke = h.invoke(this, bar, new Object[0]);
return (int) invoke;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
改变还是比较大的,改动步骤如下:
- 对其中一个方法,这里选取bar方法,来增加返回值,一系列代码同步修改为Object接收返回值的方式。
- 然后为了避免每次调用方法都要执行getMethod()方法,我们使用静态成员变量和静态代码块使其只会被初始化执行一次,每次调用的时候直接用即可。
- 根据JDK代理方法的参数,我们增加了代理类本身的参数的回传,在代理类中可以用this表示,可以在一些特殊业务中使用。
- 删除了自定义的InvocationHandler,直接使用的jar包中的接口类,翻看源码会发现和我们自定义的是一致的,这里可以直接替换代理类中的引用路径即可。
- 删除了代理类中的InvocationHandler成员变量,选用继承Proxy类,该类中已经有对该参数的定义和构造,我们修改构造器使用super调用父类即可,下面所有的使用直接使用父类中定义的h即可。
ASM
文章最初提到JDK代理使用的ASM字节码技术来处理的代理类生成,这里对ASM不做深究,感兴趣的小伙伴可以继续翻阅相关的文档。我们这里只说一下,由于是使用该技术直接生成字节码,所以,代理类没有经过任何的源码、编译等阶段。
inflation机制
反射是比正常编译慢的过程,至于慢的过程总结以下几点。
- java的invoke方法是传object和object[]数组的,由于过多的通用性质,基本参数类型都需要拆箱和装箱,产生大量额外的对象和内存开销,进而频繁的促发GC。
- 编译器难以对动态调用的代码提前做优化,比如方法内联。
- 反射需要按名检索类和方法,这样也会有一定的时间开销。
正是这么多的不便,那么就需要一定的优化手段。我们从Proxy.newProxyInstance入手查看源码。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
查看return cons.newInstance(new Object[]{h});
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
查看T inst = (T) ca.newInstance(initargs);
public Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException {
if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.c.getDeclaringClass())) {
ConstructorAccessorImpl var2 = (ConstructorAccessorImpl)(new MethodAccessorGenerator()).generateConstructor(this.c.getDeclaringClass(), this.c.getParameterTypes(), this.c.getExceptionTypes(), this.c.getModifiers());
this.parent.setDelegate(var2);
}
return newInstance0(this.c, var1);
}
重点就在这里,我们看到有个数值的比较,ReflectionFactory.inflationThreshold()小于多少就会执行if内的方法。我们先看if之外的处理方式
private static native Object newInstance0(Constructor<?> var0, Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;
native修饰的方法,底层由非java语言编写。这个if判断的逻辑整体的意思就是,如果反射调用不超过16次(数值是默认值,可以通过jvm参数控制),那么就走native的方式调用。如果超过,就会生成代理类GeneratesConstructorAccessor,该类中的调用方式已经不再是反射了,而是通过类本身来去调用具体的执行方法,这个类似于Cglib的一些逻辑,具体可以通过下一篇的Cglib里再次讨论这个问题。这个机制本身就叫做inflation机制。