代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。本文是对这篇博客(http://www.importnew.com/26116.html)进行学习的,通过自己理解,对其中的一些代码加上自己的注解,更方便大家学习。
可以用图来表示:
代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象。
代理的角色:
第一种:抽象角色,声明真实对象和代理对象的共同接口。
第二种:代理角色,代理对象角色内部含有对真实对象的引用,从而可以操纵真实的对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象,同时代理对象可以在执行真实对象操作时,附加其他的操作,相对于对真实对象封装。
第三种:真实角色,代理所代理的真实角色,是我们要引用的对象。
举个例子,小张是一个普通的码农,有一个产品经理小李,有一个客户有需求想要找小张。
对照上面定义,小张可以映射成真实角色,小李是代理角色,这两个人都是程序员,都可以抽象成码农。
基于面向对象的思想,首先定义一个码农接口,它有一个实现用户需求的方法
|
我们假设小张是JAVA程序员,定义一个JAVA码农类,他通过JAVA语言实现需求。
|
委屈一下产品经理,将其命名为码农代理类,同时让他实现ICoder接口。
public class CoderProxy implements ICoder {
//真实角色,但现在是一个抽象接口
private ICoder coder;
public CoderProxy(ICoder coder){
this.coder = coder;
}
@Override
public void implDemands(String demandName) {
coder.implDemands(demandName);
}
}
上面一个接口,两个类,就实现了代理模式。Are you kidding me?这么简单?是的,就是这么简单。 我们通过一个场景类,模拟用户找产品经理增加需求。
|
运行程序,结果如下:
Zhang implemented demand:add user manageMent in JAVA! |
产品经理充当了程序员的代理,客户把需求告诉产品经理,并不需要和程序员接触。
静态代理总结:
缺点:
如何解决静态代理中的缺点呢?答案是可以使用动态代理方式
--------------------------------------------------------------------------------------------------------------------
动态代理有以下特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理。
JDK中生成代理对象的API------java中文API 1.8(https://blog.fondme.cn/apidoc/jdk-1.8-google/)
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object newProxyInstance(ClassLoader loader, Class>[] interfaces,InvocationHandler h )
注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:
ClassLoader loader,
:指定当前目标对象使用类加载器,获取加载器的方法是固定的Class>[] interfaces,
:目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
与静态代理相比,抽象角色、真实角色都没有变化。变化的只有代理类。因此,抽象角色、真实角色,参考ICoder和JavaCoder。
在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,也叫动态代理类,这个类被要求实现InvocationHandler接口:
public class CoderDynamicProxy implements InvocationHandler {
//被代理的实例
private ICoder coder;
public CoderDynamicProxy(ICoder _coder){
this.coder = _coder;
}
/*jdk的动态代理,被代理的对象必须实现接口
* 第一个参数是要被代理的类
* 第二个参数method是被代理对象的接口方法
* 第三个参数是被代理对象接口方法的参数
*
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//System.out.println(System.currentTimeMillis());
Object result = method.invoke(coder, args);
//System.out.println(System.currentTimeMillis());
return result;
}
}
当我们调用代理类对象的方法时,这个“调用”会转送到中介类的invoke方法中,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。
我们通过一个场景类,模拟用户找产品经理更改需求。
public class DynamicClient {
public static void main(String args[]){
//要代理的真实对象
ICoder coder = new JavaCoder("Zhang");
//创建动态代理类实例
InvocationHandler handler = new CoderDynamicProxy(coder);
//动态产生一个代理类,第一个参数是获得被代理的对象的类加载器,第二个参数获得被代理对象的类的所有接口,第三个参数是自定义动态代理类的对象中介类
ICoder proxy = (ICoder) Proxy.newProxyInstance(coder.getClass().getClassLoader(), coder.getClass().getInterfaces(), handler);
//通过代理类,执行impleDemands方法,会被转送到中介类的invoke方法中;
proxy.implDemands("Modify user management");
}
}
执行结果如下:
Zhang implemented demand:Modify user management in JAVA!
通过上述代码,就实现了,在执行委托类的所有方法前、后打印时间。还是那个熟悉的小张,但我们并没有创建代理类,也没有时间ICoder接口。这就是动态代理。
总结一下,一个典型的动态代理可分为以下四个步骤:
ps:如果想深入了解为什么通过proxy.newProxyInstance就能把代理类的方法调用,分派给中介类的invoke方法,可以看这篇博客http://www.importnew.com/26116.html(从“源码分析JDK7”这一栏往下阅读)