动态署理在java里边算是一种比常用的技术,它和静态署理的差异在于静态署理需在编译的时分署理类就现已确定了,而动态署理的署理类是在运转的时分动态生成的。
例如运用retrofit的时分咱们只需求界说好interface:
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
然后就能够在运转的时分创立出这个接口的实例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
这个实例其实便是接口的署理,它的原理是运用Proxy.newProxyInstance
。
interface的动态署理
Proxy是java内置的一个类,咱们能够用它来创立接口的动态署理,详细的用法如下:
// 界说interface
public interface ITestInterface {
void foo();
}
// 创立InvocationHandler用于署理interface的办法
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Log.d(TAG, "invoke " + method, new Exception());
return null;
}
};
// 创立ITestInterface的实例:
ITestInterface testInterface = (ITestInterface) Proxy.newProxyInstance(
getClassLoader(),
new Class[]{ITestInterface.class},
handler);
然后咱们调用testInterface.foo()
最终就会去到InvocationHandler.invoke
办法里边。
安卓里边最终是在ClassLinker::CreateProxyClass里边创立了完成ITestInterface接口的子类。从仓库上看它生成的类名叫$Proxy1
:
11-02 16:58:32.641 4205 4205 D testtest: invoke public abstract void me.linjw.demo.ITestInterface.foo()
11-02 16:58:32.641 4205 4205 D testtest: java.lang.Exception
11-02 16:58:32.641 4205 4205 D testtest: at me.linjw.demo.MainActivity$1.invoke(MainActivity.java:23)
11-02 16:58:32.641 4205 4205 D testtest: at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
11-02 16:58:32.641 4205 4205 D testtest: at $Proxy1.foo(Unknown Source)
11-02 16:58:32.641 4205 4205 D testtest: at me.linjw.demo.MainActivity.onCreate(MainActivity.java:32)
...
$Proxy1.foo
办法里边调用了Proxy.invoke,而这个invoke办法实践便是调用了咱们注册的InvocationHandler:
// Android-added: Helper method invoke(Proxy, Method, Object[]) for ART native code.
private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {
InvocationHandler h = proxy.h;
return h.invoke(proxy, method, args);
}
JVM里边能够经过设置环境变量的办法将动态生成的类保存下来,可是安卓里边并没有这样的机制。咱们能够经过反射打印这个生成类的结构
11-02 20:40:10.580 13546 13546 D ProxyDemo: class $Proxy1 extends Proxy implements ITestInterface {
11-02 20:40:10.580 13546 13546 D ProxyDemo: public static final Class[] interfaces;
11-02 20:40:10.580 13546 13546 D ProxyDemo: public static final Class[][] throws;
11-02 20:40:10.580 13546 13546 D ProxyDemo: public final equals(Object arg0) { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo: public final foo() { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo: public final hashCode() { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo: public final toString() { ... }
11-02 20:40:10.580 13546 13546 D ProxyDemo: }
从打印的信息来看咱们能够知道生成的$Proxy1
类是Proxy的之类并且完成了咱们需求署理的ITestInterface接口,它的foo办法咱们没有办法打印出实践的字节码,可是从仓库上看能够猜测大概是这样的:
void foo() {
Proxy.invoke(this, ITestInterface.class.getMethod("foo"), null);
}
Proxy不能署理class的原因
因为interface是支撑多完成的所以咱们能够署理多个接口,这样生成的类就会完成多个接口:
ITestInterface testInterface = (ITestInterface) Proxy.newProxyInstance(
getClassLoader(),
new Class[]{ITestInterface.class}, // 这个数组能够传入多个interface进行署理
handler);
可是java并不支撑多承继,动态生成的类现已承继Proxy了就不能再承继其他的类,所以Proxy并不能署理类或者抽象类:
11-02 16:33:06.615 2966 2966 E testtest: err
11-02 16:33:06.615 2966 2966 E testtest: java.lang.IllegalArgumentException: me.linjw.demo.TestAbstractClass is not an interface
11-02 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:635)
11-02 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy$ProxyClassFactory.apply(Proxy.java:602)
11-02 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.WeakCache$Factory.get(WeakCache.java:230)
11-02 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.WeakCache.get(WeakCache.java:127)
11-02 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy.getProxyClass0(Proxy.java:438)
11-02 16:33:06.615 2966 2966 E testtest: at java.lang.reflect.Proxy.newProxyInstance(Proxy.java:873)
class的动态署理
理解了Proxy署理接口的原理之后,如果咱们想要对类做动态署理的话,能够模仿Proxy的原理运转时创立子类重写被署理类的办法去完成.实践上java有个开源项目cglib能够在运转的时分生成java字节码完成这个功用。可是因为安卓运转时运用的不是java字节码而是安卓自己的字节码,所以不能直接运用cglib去完成。
// 界说需求署理的类
public class TestClass {
public void foo() {
Log.d(MainActivity.TAG, "on TestClass.foo");
}
}
// 创立MethodInterceptor用于署理class的办法
Enhancer e = new Enhancer();
e.setSuperclass(TestClass.class);
e.setInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object o, Object[] objects, MethodProxy methodProxy) {
// 调用父类(即被署理类)的办法
methodProxy.invokeSuper(o, objects);
Log.d(TAG, "invoke " + methodProxy.getOriginalMethod(), new Exception());
return null;
}
});
// 创立署理实例
TestClass testClass = (TestClass) e.create(getCacheDir().getAbsolutePath());
// 调用办法
testClass.foo();
然后调用TestClass的办法就会去到MethodInterceptor:
11-02 22:25:56.108 3357 3357 D ProxyDemo: on TestClass.foo
11-02 22:25:56.110 3357 3357 D ProxyDemo: invoke public void me.linjw.demo.proxy.TestClass$Enhancer$.foo()
11-02 22:25:56.110 3357 3357 D ProxyDemo: java.lang.Exception
11-02 22:25:56.110 3357 3357 D ProxyDemo: at me.linjw.demo.proxy.MainActivity$2.intercept(MainActivity.java:45)
11-02 22:25:56.110 3357 3357 D ProxyDemo: at leo.android.cglib.proxy.MethodProxyExecuter.executeInterceptor(MethodProxyExecuter.java:15)
11-02 22:25:56.110 3357 3357 D ProxyDemo: at me.linjw.demo.proxy.TestClass$Enhancer$.foo(Unknown Source:19)
11-02 22:25:56.110 3357 3357 D ProxyDemo: at me.linjw.demo.proxy.MainActivity.onCreate(MainActivity.java:51)
关注大众号:Android老皮!!!欢迎大家来找我讨论沟通