首先这是一段普通的代码子类继承父类并重写了print方法
public class TestVisualInvoke {
public static class Father {
public void print() {
System.out.println("father's print");
}
}
public static class Son extends Father {
@Override
public void print() {
System.out.println("son's print");
}
}
public static void main(String[] args) {
new Son().print();
}
}
众所周知这段代码执行结果为
son's print
那么我们怎么才能达到输出下边的结果呢?
father's print
首先你得用JDK7,因为JDK7中加入了动态类型的新特性。
然后如下的代码可以达到我们预期的效果:
MethodType mt = MethodType.methodType(void.class);
MethodHandle methodHandle = MethodHandles.lookup().findSpecial(Father.class, "print", mt, Father.class);
methodHandle.invoke();
但是因为findSpecial方法中做了安全限制:调用MethodHandles.lookup()的类要与findSpecial最后一个参数相同。要不然会报错。
所以整体的代码需要这么写:
public class TestVisualInvoke {
public static class Father {
public void print() {
System.out.println("father's print");
}
public static MethodHandles.Lookup getLookup() {
return MethodHandles.lookup();
}
}
public static class Son extends Father {
@Override
public void print() {
System.out.println("son's print");
}
}
public static void main(String[] args) throws Throwable {
Son son = new Son();
MethodType mt = MethodType.methodType(void.class);
MethodHandle methodHandle = Father.getLookup().findSpecial(Father.class, "print", mt, Father.class);
methodHandle.invoke(son);
}
}
这样执行结果就是我们想要的了,如下:
father's print
但是在父类中加一个静态方法又不太美观。那么看一下lookup源码如下:
@CallerSensitive
public static Lookup lookup() {
return new Lookup(Reflection.getCallerClass());
}
看到这里其实我们可以自己new一个Lookup,但是Lookup的构造都不是public的。
但是反射API第一个表示不服气。我们可以通过反射创建这个Lookup。
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class);
MethodHandles.Lookup lookup = constructor.newInstance(Father.class);
那么整体的代码就如下:
public class TestVisualInvoke {
public static class Father {
public void print() {
System.out.println("father's print");
}
}
public static class Son extends Father {
@Override
public void print() {
System.out.println("son's print");
}
}
// 方法一
public static void main(String[] args) throws Throwable {
Son son = new Son();
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class);
constructor.setAccessible(true);
MethodHandles.Lookup lookup = constructor.newInstance(Father.class);
MethodType mt = MethodType.methodType(void.class);
MethodHandle methodHandle = lookup.findSpecial(Father.class, "print", mt, Father.class);
methodHandle.invoke(son);
}
// 方法二
public static void main2(String[] args) throws Throwable {
Son son = new Son();
MethodHandles.Lookup lookup = MethodHandles.lookup();
Field f = MethodHandles.Lookup.class.getDeclaredField("lookupClass");
f.setAccessible(true);
f.set(lookup, Father.class);
MethodType mt = MethodType.methodType(void.class);
MethodHandle methodHandle = lookup.findSpecial(Father.class, "print", mt, Father.class);
methodHandle.invoke(son);
}
}
好了这就达到了我们的最终目的。
-----------------------
举个例子日常我们在哪里会用到呢?
机缘巧合下发现了mybatis(3.4.3)的源码中应用到了这样的操作。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {// 这个方法判断方法是否是interface中的default方法
return invokeDefaultMethod(proxy, method, args);// 这个方法调用了生成的代理类的的default方法
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
// 看这里!!!
@UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
这段代码是什么意思呢?就是说如果我们调用的方法是mapper接口的default方法那么不会去走mybatis生成的代理逻辑,而是去直接调用default方法。这样我们就可以在mapper接口中声明一些default方法定一些我们需要的操作。举个例子:
算了直接上issues吧
https://github.com/mybatis/mybatis-3/issues/709
因为这个新特性是在jdk7中加入的所以pr作者说了如下一段话
I wrote a patch.
But...it requires Java 7 and won't get merged until Java 9 is released.
Still, it would be great if you could test it and see if it works as you expect.
And feel free to send a PR if you found a way to achieve this in Java 6.