※ 引述《egheee (阿平)》之铭言:
: class A {
: void tell() {
: Log.e("", "I am a");
: }
: }
: class B extends A{
: void tell() {
: Log.e("", "I am b");
: }
: }
: class C extends B {
: void tell() {
: super.super.tell(); // 问题
: Log.e("", "I am c");
: }
: }
: 如上列所示,这样的写法是有问题的,super好像规定只能用一次
: 请问我要怎么从C里面call到A的tell()呢?
依所示的情境与需求,就我所知是无法单从 Java 语法层面去解决的。
有些版友提到在 C::tell method 里另外建构一个 A instance,由此 instance
去调用 tell method。我想这应该只能针对上面范例之类的个案(tell method 对
调用的 object 本身没有副作用),能适用的实例不多,不能算是一种解法。
如果不限制在语法层面,也暂时不考虑这样的设计是否合理...等等,的确是有方式
可以转寰的。我提供一个的可行做法是透过 bytecode 工程去加工。
先定义一下需求与条件,以上述的 sample 来说:
1. 你没有 class A, class B 的 source code,只有 class file。
2. 有 class C 的 source 可供编辑。
3. 基于某种因素,你需要明确在 C::tell method 里调用 A::tell implemention,
不论 C 的 base classes 有无 override tell method。
我做了一个 bytecode 加工的工具,可在此网址下载(jar file):
http://ul.to/2k6o1dzz
将 C.java 修改为:
import ptt.java.tool.RedirectSuperCall;
class C extends B {
@RedirectSuperCall(targetType=A.class)
void tell() {
super.tell();
Log.e("", "I am c");
}
}
编译 C.java 时将下载的 jar 加入 classpath。
编译完后,执行 jar 内的 ptt.java.tool.Main class,执行时 classpath 除了
下载的 jar 外也需要包含你的 project binary(你的 project class file 不能
打包成 jar)。
java -cp <path_to_RedirectSuper.jar>;<project_output> ptt.java.tool.Main
<path_to_C.class>
后面参数可以是(多个)档案或档案夹的路径,如果档案是 .class 档案,tool 会去
看是否有使用 RedirectSuperCall annotation 来做加工,如果是档案夹则会查看
档案夹内的所有 .class 档案(包括子档案夹)。
为了比较快做出这个 tool 我有省略一些考量,算是比较简化的版本。暂不考虑
generic method 以及 method exception clause,但 return type 与 parameter
type/数量倒是没有限制。
这个 tool 做的事情如下:
去修改以 RedirectSuperCall(targetType=XXX.class) 标注的 method bytecode
(假设被标注的 method 是 someMethod),将 super.someMethod(...) expression
改成 invoke tool 注入在 XXX class 内的 static someMethod$hook method。
(被注入的 method 内容大致如下:
class XXX {
public static ... someMethod$hook(XXX obj, ...) {
return obj.someMethod(...); // but use invokespecial instruction
}
}
虽然说 annotation 只标注在 C class,但是单只是去加工 C class bytecode 是
不足以做所需的效果,若是把 super.someMethod(...) expression 中
invokespecial instruction 的目标 class 从 B 改成 A,还是会执行到 B class
定义的 overriding 版本。(因为此时的 context 是 C class)
我的做法是把原来的 invokespecial call 改成一个 invokestatic call,而被
指定的 method 里使用 invokespecial instruction 来调用真正想要调用的
non-static virtual method。
这里 hook method 简单地透过 invokespecial instruction 调用另一个 virtual
method 的做法应该是比较不保险的做法,但我在几种 JRE 上测试过都正确。
(如果有人测试时没有成功 redirect,欢迎来信告知/分享你使用的 JRE)
比较保险的做法是把目标 method inline 在 hook method 里,但若是 targetType
并没有真的 declare/override 被标注的 method,实作上会很麻烦。所以是我偷懒
没错,但是额外好处是 targetType 可以指定任一个 base class,他的意思是
“我要 redirect 到 targetType 为止的最新版本”。
*tool bytecode version 是 50.0,故你需要 JRE 1.6+ 才能使用之。
**理论上,这个 tool 应该可以做成 annotation processor 成为编译的一部份,
或是做成 instrumentation agent 在 runtime 时部署。前者我经验还不够,要搞
比较久;后者则是因为当 JVM 加载使用 annotation 标记的 class 时,他的 base
class 已经加载,如果还要加工 base class 的话,必须要使用的 JVM 有支援
retransform。所以我做出了这样的工具...呵呵,反正只是 demo 用途。