Java设计模式之代理模式-奇思妙想应用场景举例

前言

本文将用西门庆、潘金莲、王婆等人来解释静态代理模式。再继续延伸动态代理模式。

正文

静态代理模式

什么是代理模式?

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。

代理模式有什么好处?

在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式一般涉及到的角色有:

  • 抽象角色:声明真实对象和代理对象的共同接口;
  • 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
  • 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

应用场景举例:

比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理,表现在程序上时是这样的体现的

先说说这个场景中的要素:一种类型的女人,潘金莲,王婆,西门庆,后来扩展的贾氏也和西门庆勾上了,我们是假设的,然后西门庆找潘金莲happy,但潘金莲不好意思直接,就找个王婆代理呗。我们看看具体代码。

先定义一种女人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.yangguangfu.proxy;  
/**
*
* @author 阿福(trygf521@126.com)<br>
*定义一种类型的女人,王婆和潘金莲都属于这个类型的女人
*/
public interface KindWoman {

//这种女人能做什么事情呢?
public void makeEyesWithMan();//抛媚眼

public void happyWithMan();//和男人那个....

}

一种类型嘛,那肯定是接口,定义个潘金莲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.yangguangfu.proxy;
/**
*
* @author 阿福(trygf521@126.com)<br>
*定义一个潘金莲是什么样的人
*/
public class PanJinLian implements KindWoman{

@Override
public void happyWithMan() {
System.out.println("潘金莲和男人在做那个...");

}

@Override
public void makeEyesWithMan() {
System.out.println("潘金莲抛媚眼...");

}
}

再定义个丑陋的王婆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.yangguangfu.proxy;
/**
*
* @author 阿福(trygf521@126.com)<br>
*王婆这个人老聪明了,她太老了,是个男人都看不上她,
*但是她有智慧经验呀,他作为一类女人的代理!
*/
public class WangPo implements KindWoman {

private KindWoman kindWoman;

public WangPo(){
//默认的话是潘金莲的代理
this.kindWoman = new PanJinLian();
}
//她可以是KindWomam的任何一个女人的代理,只要你是这一类型
public WangPo(KindWoman kindWoman){
this.kindWoman = kindWoman;
}

@Override
public void happyWithMan() {
//自己老了,干不了了,但可以叫年轻的代替。
this.kindWoman.happyWithMan();

}

@Override
public void makeEyesWithMan() {
//王婆年纪大了,谁看她抛媚眼啊
this.kindWoman.makeEyesWithMan();

}

}

两个女主角都上场了,该男主角了,定义个西门庆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.yangguangfu.proxy;
/**
*
* @author 阿福(trygf521@126.com)<br>
*水浒传是这样写的:西门庆被潘金莲用竹竿敲了一下,西门庆看痴迷了,被王婆看到了,就开始撮合两人好事,王婆作为潘金莲的代理人收了不少好处费,那我们假设一下:
*如果没有王婆在中间牵线,这两个不要脸的能成事吗?难说得很!
*/
public class XiMenQiang {

/**
* @param args
*/
public static void main(String[] args) {
WangPo wangPo;
//把王婆叫出来
wangPo = new WangPo();
//然后西门庆说,我要和潘金莲Happy,然后王婆就安排了西门庆丢筷子哪出戏:
wangPo.makeEyesWithMan();
//看到没有表面是王婆在做,其实爽的是潘金莲
wangPo.happyWithMan();



}

}

那这就是活生生的一个例子,通过代理人实现了某种目的,如果真去了王婆这个中间环节,直接西门庆和潘金莲勾搭,估计很难成就武松杀嫂事件。
那我们再考虑一下,水浒里面还有没有这类型的女人?有,卢俊义的老婆贾氏(就是和那个管家苟合的那个),这个名字起的:“贾氏”,那我们也让王婆做她的代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yangguangfu.proxy;
/**
*
* @author 阿福(trygf521@126.com)<br>
*定义一个贾氏是什么样的人
*/
public class JiaShi implements KindWoman {

@Override
public void happyWithMan() {
System.out.println("贾氏和男人在做那个...");

}

@Override
public void makeEyesWithMan() {
System.out.println("贾氏抛媚眼...");

}


}

西门庆勾潘金莲又勾引贾氏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.yangguangfu.proxy;
/**
*
* @author 阿福(trygf521@126.com)<br>
*水浒传是这样写的:西门庆被潘金莲用竹竿敲了一下,西门庆看痴迷了,被王婆看到了,就开始撮合两人好事,王婆作为潘金莲的代理人收了不少好处费,那我们假设一下:
*如果没有王婆在中间牵线,这两个不要脸的能成事吗?难说得很!
*/
public class XiMenQiang {

/**
* @param args
*/
public static void main(String[] args) {
WangPo wangPo;
//把王婆叫出来
wangPo = new WangPo();
//然后西门庆说,我要和潘金莲Happy,然后王婆就安排了西门庆丢筷子哪出戏:
wangPo.makeEyesWithMan();
//看到没有表面是王婆在做,其实爽的是潘金莲
wangPo.happyWithMan();



//西门庆勾引贾氏
JiaShi jiaShi = new JiaShi();
wangPo = new WangPo(jiaShi);
wangPo.makeEyesWithMan();
wangPo.happyWithMan();

}

}

总结

代理模式主要使用了java的多态,干活的是被代理类,代理类主要是接活,你让我干活,好,我交给幕后的类去干

动态代理模式

简要说明

动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
假如我想在每个代理的方法前都加上一个处理方法:

1
2
3
4
5
public void giveMoney() {
//调用被代理方法前加入处理方法
beforeMethod();
stu.giveMoney();
}

动态的简单实现

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

创建一个动态代理对象的步骤,具体代码见后面:

  • 创建一个InvocationHandler对象

    1
    2
    //创建一个与代理对象相关联的InvocationHandler
    InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
  • 使用Proxy类getProxyClass静态方法生成一个动态代理类stuProxyClass

    1
    Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
  • 获得stuProxyClass 中一个带InvocationHandler参数构造器constructor

    1
    Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
  • 通过构造器constructor来创建一个动态实例stuProxy

    1
    Person stuProxy = (Person) cons.newInstance(stuHandler);

就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类newProxyInstances方法来简化:

1
2
3
4
 //创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。

首先是定义一个Person接口:

1
2
3
4
5
6
7
8
/**
* 创建Person接口
* @author Gonjan
*/
public interface Person {
//上交班费
void giveMoney();
}

创建需要被代理的实际类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}

@Override
public void giveMoney() {
try {
//假设数钱花了一秒时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "上交班费50元");
}
}

再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MonitorUtil {

private static ThreadLocal<Long> tl = new ThreadLocal<>();

public static void start() {
tl.set(System.currentTimeMillis());
}

//结束时打印耗时
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
}
}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例targetInvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法

再再invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;

public StuInvocationHandler(T target) {
this.target = target;
}

/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
*/
//代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}

做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简化的方式创建动态代理对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ProxyTest {
public static void main(String[] args) {

//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");

//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);

//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

//代理执行上交班费的方法
stuProxy.giveMoney();
}
}

我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。

运行结果:

上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。

总结

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的

就这三招,搞得的周郎是“赔了夫人又折兵”呀!这就是策略模式,高内聚低耦合的特点也表现出来了,还有一个就是扩展性,也就是OCP原则,策略类可以继续添加下去气,只是修改Context.java就可以了,这个不多说了,自己领会吧。
如有诠释不清或说错的地方请不吝指正,与大家一起成长

转载请注明出处:天雷

以上

参考资料

  1. 阿福
谢谢你请我吃糖果