本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
代理模式简介
生活中的实例
什么是代理?代理相当于一个中介,假如当前有一所大学,对全世界招生。但是生源太广泛,只能委托给招生中介,来帮助这所大学招生。中介就是学校的代理,中介就代替大学完成招生的功能。
这个代理有如下的特点:
- 代理和学校他们做的事情是一样的,都是招生
- 中介是学校的代理,学校是学生最终的目标
- 中介代替学校为学生进行学校的介绍以及入学手续的办理
- 中介不能白干活,需要收取一定的费用(功能增强)
- 中介将学校和学生进行隔离,不让学生直接访问学校(控制访问)。
为什么要找中介呢?
- 中介是专业的,可以减少很多不必要的麻烦
- 学生没有能力直接访问学校,或者学校不接受学生的直接访问
开发中的实例
以上是一个实际生活中的例子,在我们的开发中,也会有类似的情况。
有一个A类,需要调用C类的方法,但是C不让A类调用,但是可以让B类进行访问。所以B类就成为了A和C之间的代理,A访问B,通过B访问C类的方法。
再比如发送短信验证码,中国移动、联通、电信具备发送短信的能力,但是我们在开发中不能直接调用它们的接口,需要通过其关联的子公司来调用,这些子公司专门负责向社会提供发送短信的功能接口。这个调用链如下:项目A调用—>子公司或者关联公司的公共接口—>调用移动/联通/电信的真实接口。
这些子公司就相当于上学实例中的中介,最终调用的真实接口就是我们的目标,也就是上学例子中的大学。
有什么好处
- 功能增强:在原有的基础上,增加额外的功能,比如代理是要收费的,收完费才能帮你办理手续(这也不是啥好事儿啊 QAQ),收费就是新增的功能。
- 控制访问:代理类不让你直接访问目标类,例如商家不让用户直接访问厂商,这样也会更加安全。
静态代理:
代理类是自己手动实现创建java类,表示代理类,同时你所代理的目标类是确定的。
实现流程
静态代理的实现比较简单,简单说一下大体的步骤吧。
- 创建一个接口,定义目标相关方法,表示目标类要实现的功能
public interface StudyInterface {
void study();
}
复制代码
- 创建目标类,实现1中的接口
public class RealSchool implements StudyInterface {
@Override
public void study() {
System.out.println("real study");
}
}
复制代码
- 创建代理类,也实现1中的接口
public class ProxySchool implements StudyInterface{
//持有目标类的引用
RealSchool realSchool;
public ProxySchool(RealSchool realSchool) {
this.realSchool = realSchool;
}
@Override
public void study() {
//功能增强
doSomethingBefore();
//目标类调用
realSchool.study();
//功能增强
doSomethingAfter();
}
public void doSomethingBefore() {
System.out.println("before");
}
public void doSomethingAfter() {
System.out.println("before");
}
}
复制代码
- 创建客户端类,通过代理来调用
public class Test {
public static void main(String[] args) {
//创建代理类
ProxySchool school = new ProxySchool(new RealSchool());
//通过代理类来调用
school.study();
}
}
复制代码
缺陷
静态代理短短几个步骤就可以设计好了,由于其类型是定义好的,也就是说在程序运行之前,.class文件就已经存在了,其使用时运行效率还是很高的,同时我们在使用代理时,可以添加我们需要的功能进行扩展。
但是其也有很明显的缺陷,代理类和目标类需要实现同样的接口,这样就出现了大量的重复代码,如果接口增加一个方法或者修改一个方法,除了所有的目标类需要修改之外,所有的代理类也需要修改,这就直观的增加了维护的难度,其次,代理对象只能服务于一种类型的目标类,如果要服务多类型的目标类,就需要为每一种类型的目标创建响应的代理对象,如果是大型的项目,则使用静态代理所带来的麻烦则会成倍增加。
动态代理:
说完静态代理,也知道了静态代理的缺点,现在就来看看动态代理,以及动态代理是如何规避这些缺点的。
什么是动态代理?在程序的执行过程中,使用JDK的反射机制,创建代理类对象,并动态的指定要代理的目标类。也就是说,动态代理是一种创建java对象的能力。
动态代理的实现:使用java反射包中的类和接口实现动态代理的功能。有三个比较重要的类:
InvocationHandler
是一个接口,只有一个invoke()
方法,这个方法表示代理对象要执行的功能代码,也就是代理类要完成的功能就写在这个方法中。代理类要完成哪些功能呢?
- 调用目标方法
- 功能增强,在调用目标方法的同时,增加功能
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
复制代码
这个方法中需要三个参数:
- Object proxy:jdk创建的代理对象,不需要赋值
- Method method:目标类中的方法,jkd提供method对象
- Object[] args:目标类中方法的参数,也是jdk提供
InvocationHandler表示我们的代理要干什么,怎么用呢?
- 创建类实现InvocationHandler接口
- 重写invokeP()方法,把原来在静态代理中要实现的功能,放在这个方法中。
Method
表示方法,准确的说是目标类中的方法。通过Method可以执行某个目标类的方法,method.invoke(目标对象,方法参数)
Proxy
创建代理对象,通过Porxy的类方法newProxyInstance()
来创建代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
...
}
复制代码
看一下其三个参数:
- ClassLoader loader:类加载器,负责向内存中加载对象,使用反射获取对象的classLoader。
A.getClass().getClassLoader()
,获取目标对象的类加载器 - Class<?>[] interfaces:接口,目标对象实现的接口,也是通过反射来获取
- InvocationHandler h:我们自己实现的InvocationHandler,表示代理类要完成的功能
实现一个动态代理
- 创建接口,定义目标类要完成的功能
public interface StudyInterface {
//接口中需要执行的方法
void study();
}
复制代码
- 创建目标类实现接口
public class RealSchool implements StudyInterface{
@Override
public void study() {
//真实目标的实现
System.out.println("real school");
}
}
复制代码
- 创建InvocationHandler接口的实现类,在invoke方法中完成指定的功能
public class MyInvocationHandler implements InvocationHandler {
private Object obj;
//目标类是活的,动态传入目标类,传入什么就给什么创建代理对象
public MyInvocationHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//功能增强
System.out.println("study before");
//执行目标类方法
Object invoke = method.invoke(obj, args);
//功能增强
System.out.println("study after");
//返回结果
return invoke;
}
}
复制代码
- 使用Proxy类的静态方法,创建代理对象,并把返回值转为接口类型
public static void main(String[] args) {
//创建目标对象
StudyInterface study = new RealSchool();
//创建InvocationHandlerduixiang
MyInvocationHandler handler = new MyInvocationHandler(study);
//创建代理对象
StudyInterface newProxyInstance = (StudyInterface) Proxy.newProxyInstance(study.getClass().getClassLoader(), study.getClass().getInterfaces()
, handler);
//com.sun.proxy.$Proxy0
System.out.println(newProxyInstance.getClass().getName());
//通过代理对象调用方法
newProxyInstance.study();
}
复制代码
动态代理原理
System.out.println(newProxyInstance.getClass().getName());
复制代码
在上边的代码中我们打印一下newProxyInstance代理类的name:com.sun.proxy.$Proxy0
。
这个Proxy0是什么呢?
public final class $Proxy0 extends Proxy implements StudyInterface
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
* 注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型
*
* super(paramInvocationHandler),是调用父类Proxy的构造方法。
* 父类持有:protected InvocationHandler h;
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//这个静态块本来是在最后的,把它拿到前面来,方便描述
static
{
...
//看看这儿静态块儿里面有什么,是不是找到了study方法。study通过反射得到的名字m3
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.StudyInterface").getMethod("study", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
...
}
/**
*
* 这里调用代理对象的study方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
* this.h.invoke(this, m3, null)
*/
public final void study()
{
...
this.h.invoke(this, m3, null);
return;
...
}
}
复制代码
jdk为我们生成一个proxy0(0是编号,有多个代理类就依次递增),这个类文件在内存中,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
我们可以将InvocationHandler看做一个中介类,中介类持有一个被代理对象(我们传入的目标对象),在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
调用流程
动态代理问答
- 动态代理动态在哪里
动态代理之所以称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有Proxy类,它是根据需要,从你传入的接口集创建的。
- 如何知道某个类是不是代理类?
通过静态方法isProxyClass,返回值为true,表示是一个动态代理类。
boolean proxyClass = Proxy.isProxyClass(handler.getClass());
复制代码
- 对于我能传入newProxyInstance()的接口类型,有没有什么限制?
首先,我们需要传给newProxyInstance一个接口数组,此数组中只能有接口,不能有类。如果接口不是public,就必须属于同一个package,不同的接口内,不可以有名称和参数完全一样的方法。
- 代理和装饰者模式有什么区别吗
简单来说装饰者模式会为对象增加行为,是对原始对象的包装,而代理除了会对原有功能进行额外的功能添加,还可以对保护进行目标,避免不必要的访问。
总结
代理模式在实际工作中经常会用到,是一个非常重要并且实用的设计模式。它帮助我们对目标类进行扩展同时又控制对目标类的访问。动态代理规避了静态代理的很多问题,更加的灵活的同时,不会因为业务逐渐庞大而变得难以管理。在Android开发中,retrofit中就使用了动态代理来进行网络请求的封装,这也是动态代理的一个很重要的体现。希望通过本文可以让你对代理模式有一个更清晰的认识。
参考资料
- www.cnblogs.com/gonjan-blog…
- www.zhihu.com/question/20…
- 《Head First 设计模式》