前两篇讲了Android和Js的交互、NDK开发,今天准备写一篇关于动态代理的博客。记得在上家公司项目有个模块用到了动态代理,并且在它基础上进行了扩展运用,自己接触动态代理还是在最早学Java的时候,然后结果大家懂的…
虽然动态代理用起来比较简单,并不复杂,但我还是想写一篇关于它的文章,不为其它的,只为对自己一个交待^-^
在项目开发里,有时候可能会有这种情况,因为某些原因或者需求变动,需要在不改变某个类的某个方法的代码前提下,添加一些额外的逻辑代码进去。这种情况怎么实现?可能第一个想到的——代理,就是今天准备说的代理模式。
比如,现在要添加额外逻辑代码的类(也就是被代理类)的代码,(ps:为了方便演示,这里是创建的Java工程):
public class MyClass {
//不可以更改coumpute(int a, int b)方法内部的代码逻辑
public void coumpute(int a, int b){
System.out.println("正在执行计算逻辑,计算参数,a:" + a + ",b:" + b);
}
}
这里为了简单起见,只是在compute方法里打印了一条日志,现在要求在不改变compute方法任何代码的前提下,在计算逻辑执行之前,执行一段其它代码。
使用动态代理实现,需要满足两个条件,一:代理类和被代理类必须实现同一个接口;二:需要使用到InvocationHandler接口,添加的额外逻辑,主要就是在InvocationHandler实现类的invoke方法中进行实现。
那我们进行第一步,定义一个接口,定义被代理类中的方法
public interface IMyInterface {
void compute(int a, int b);
}
很简单,就是定义了一个compute方法,然后让MyClass实现定义的接口。第二步,新建一个DynamicProxyHandler实现InvocationHandler,在其中实现添加额外逻辑代码。具体实现代码:
public class DynamicProxyHandler implements InvocationHandler{
//被代理对象
private Object beProxy;
public DynamicProxyHandler(Object beProxy){
this.beProxy = beProxy;
}
//通过Proxy类,获取代理对象
public Object getProxy(){
return Proxy.newProxyInstance(
//代理对象和被代理对象应该在同一类加载器
this.beProxy.getClass().getClassLoader(),
//获取被代理对象实现的接口(代理类和被代理类必须实现同一接口)
this.beProxy.getClass().getInterfaces(),
this
);
}
//不需要手动调用,当代理对象某个方法被调用时,自动调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行添加的额外代码逻辑");
Object result = method.invoke(this.beProxy, args);
return result;
}
先通过构造传入被代理对象,然后通过Proxy.newProxyInstance(),获取到代理对象。代理对象和被代理对象必须实现同一接口,代理对象和被代理对象必须实现同一接口,重要的事情说三遍。然后我们要实现的添加,主要就在invoke方法中,动态代理所有的处理全部交给InvocationHandler,InvocationHandler所有被代理方法的处理,全部在invoke方法中。这里,我们也只是简单得在compute方法执行之前,打印了一条日志。
好,看代码应该已经实现了动态代理,我们新建一个MainCls类,(ps:因为我compute方法里有中文,所以在模块的build.gradle文件里,添加了tasks.withType(JavaCompile) {options.encoding = “UTF-8”},避免控制台输出乱码)
public class MainCls {
public static void main(String[] args){
//获取代理对象
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(new MyClass());
IMyInterface proxy = (IMyInterface) dynamicProxyHandler.getProxy();
//调用compute方法
proxy.compute(3, 5);
}
}
运行测试一下结果:
执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5
输出了这两条log,说明动态代理成功。看到这里,可能有朋友会问,这只是一个方法的代理,如果有多个方法需要代理?
刚刚说过InvocationHandler所有被代理方法的处理,全部是交给了invoke方法处理,那我们直接根据invoke方法的method参数,进行方法名区分,就可以了。比如,MyCls多添加一个query()方法,要求获取到query()方法值后,进行一次额外运算之后,将最后运算结果返回。实现也比较简单,MyCls中添加了query()方法
//不可以更改query()方法内部的代码逻辑,查询到的值,进行一次额外运算逻辑,将最后运算结果返回
@Override
public int query() {
return 10;
}
IMyInterface添加query()
void compute(int a, int b);
int query();
DynamicProxyHandler更改invoke方法实现逻辑:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
if("compute".equals(methodName)){
//在执行compute方法计算逻辑之前,添加一段额外代码逻辑
System.out.println("compute 执行添加的额外代码逻辑");//添加的额外代码逻辑
result = method.invoke(this.beProxy, args);
} else if("query".equals(methodName)){
//获取query方法查询结果
result = method.invoke(this.beProxy, args);
//进行额外计算,然后将结果返回
if(result instanceof Integer){
int r = (Integer)result;
r*=1000;
System.out.println("query 获取到二次计算后数据:" + r);
return r;
}
}
return result;
}
然后MainCls添加query()方法的调用,运用一下测试结果:
compute 执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5
query 获取到二次计算后数据:10000
查询到最新数据:10000
说明compute(…)和query()方法都达到要求,全部代理成功。动态代理,大致就是这样一个实现过程。不过每次实现的时候,都需要手动传入被代理的对象实例,这样看起来,好像low了一点。直接使用反射,可不可以呢?我们试一下
//使用反射
return Proxy.newProxyInstance(
Class.forName("com.example.MyClass").newInstance().getClass().getClassLoader(),
new Class<?>[]{Class.forName("com.example.IMyInterface")},
this);
运行一下,发现结果一致,说明成功。这样调用好像还显得代码有些多,干脆直接这样调用:
IMyInterface proxy = (IMyInterface)Proxy.newProxyInstance( MainCls.class.getClassLoader(),
new Class<?>[]{Class.forName("com.example.IMyInterface")},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String methodName = method.getName();
if("compute".equals(methodName)){
//在执行compute方法计算逻辑之前,添加一段额外代码逻辑
System.out.println("compute 执行添加的额外代码逻辑");//添加的额外代码逻辑
result = method.invoke(//被代理类实例
Class.forName("com.example.MyClass").newInstance(), args);
} else if("query".equals(methodName)){
//获取query方法查询结果
result = method.invoke(Class.forName("com.example.MyClass").newInstance(), args);
//进行额外计算,然后将结果返回
if(result instanceof Integer){
int r = (Integer)result;
r*=1000;
System.out.println("query 获取到二次计算后数据:" + r);
return r;
}
}
return result;
}
});
运行:
compute 执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5
query 获取到二次计算后数据:10000
查询到最新数据:10000
输出结果一致,说明代理成功。(ps:这种调用比较粗暴,不太推荐)
代理模式分动态代理和静态代理,这里我们主要讲的动态代理,如果用静态代理,应该怎么实现?
新建StaticProxy类,实现IMyInterface
public class StaticProxyCls implements IMyInterface{
private IMyInterface beProxy;
public StaticProxyCls(IMyInterface beProxy){
this.beProxy = beProxy;
}
@Override
public void compute(int a, int b) {
System.out.println("静态代理 执行添加的额外代码逻辑");//添加的额外代码逻辑
beProxy.compute(a,b);
}
@Override
public int query() {
int n = beProxy.query();
return n*1000;
}
}
在MainCls中调用,运行:
静态代理 执行添加的额外代码逻辑
正在执行计算逻辑,计算参数,a:3,b:5
静态代理 查询到最新数据:10000
说明代理成功。静态代理,相当于组合实现,小量的使用还好,如果大量的使用,缺点就非常明显。比如现在有1000个类需要代理,那就要需要创建1000个来实现对应接口的代理类?!!!
动态代理的原理,涉及到JVM编译机制,我理解得还比较浅显,所以不敢深入讲它的一个原理,只能浅面讲它的一个基本运用。
个人观点,难免有不正确错误的地方,欢迎大家不吝指出。如果需要转载,请注明出处,谢谢。
源码下载