设计模式——代理模式(超级详细的代理模式讲解)
本文首发于cdream的个人博客,点击获得更好的阅读体验!
欢迎转载,转载请注明出处。
本文主要对设计模式中的代理模式进行讲解,包括静态代理举例,动态代理中的jdk动态代理、cglib动态代理原理分析等几个方面。
一、概念
定义:代理模式(Proxy Pattern)代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的访问。代理对象在客户端了和目标对象之间起到中介作用。
1.定义一个创建对象的单独操作(工厂方法)
2.将创建对象推迟到子类完成
二、结构
UML:
主要角色:
真实主题类:客户端真正想调用的的主题类。
代理类:保存一个真实主题类的引用,使得代理对象可以访问真实主体对象的实体。真实主题类和代理对象都会继承相同的接口:在用到真实主题类的地方都可以使用代理类来完成。
抽象主体:定义真实主题类和代理类的接口。
三、静态代理
虚拟代理
虚拟代理作为创建开销大的对象的代表。直到我们真正使用对象时才会创建它,当对象在创建前和创建中,由虚拟代理来扮演对象的替身。对象创建后,代理会将请求直接委托给对象。
抽象主题接口
public interface LargeObject {
/**
* 干了一个老大的事了,所以我这个类老大了!
*/
public void doBigThing();
}
具体主题角色,实现了抽象主题接口
public class RealLargeObject implements LargeObject {
@Override
public void doBigThing() {
System.out.println("做了老大的一个事了");
}
}
现在我们要对类进行访问控制,对”巨型对象”进行延迟创建。
public class ProxyLargeObject implements LargeObject {
private LargeObject largeObject;
@Override
public void doBigThing() {
if (largeObject==null){
largeObject = new RealLargeObject();
}
largeObject.doBigThing();
}
}
当时使用代理类时,只有当客户端调用doBigThing方法的时候才会创建LargeObject对象。
当我们需要对开销大的对象进行延迟创建或隐藏其创建过程时可以使用虚拟代理模式。
远程代理
概述
RMI
远程代理RMI允许一个jvm上的对象调用另一个jvm上的对象,流程类似于上面这个图。
客户对象直接调用辅助对象 stub的方法,stub打包调用信息,通过网络把他运送给服务端辅助对象 skeleton,服务端辅助对象进行解包,调用真正服务对象的真正方法。
然后服务对象将结果返回给服务辅助对象,服务辅助对象将结果打包,然后客户服务对象将返回结果解包交给真正客户对象。
由registry来作为注册中心,服务端将对象注册到其中,客户端通过相关方法调用。
源代码
下面来介绍RMI步骤
1.制作远程接口
public interface MyRemote extends Remote {
// 这里由于是网络调用,肯定要面远程调用风险
public String sayHello() throws RemoteException;
}
2.制作远程实现
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
private static final long serialVersionUID = 6780156706603775814L;
protected MyRemoteImpl() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException {
return "hahahahaha";//真实的服务
}
public static void main(String[] args) {
try {
// 创建远程服务对象
MyRemote myRemote = new MyRemoteImpl();
// 绑定远程服务对象到 rmiregistry
Naming.rebind("RemoteServer", myRemote);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.产生Stub和Skeleton
对远程实现类执行,到classes目录下找到远程实现类,然后使用如下命令rmic com.rmidemo.MyRemoteImpl
产生stub和skeleton文件。
4.启动registry
依然是在classes 目录下运行 remiregistry 命令启动registry
5.启动服务
运行远程服务实现的 main() 方法 —> MyRemoteImpl
public class MyRemoteClient {
public static void main(String[] args) {
new MyRemoteClient().go();
}
public void go() {
try {
MyRemote myRemote = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteServer”);// RemoteServer 就是注册(rebind)时的 key
String hello = myRemote.sayHello();
System.out.println(hello);
} catch (Exception e) {
e.printStackTrace();
}
}
}
这样就完成了RMI的调用,这种方法其实已经过时了,但是这是一个标准的远程代理模式,客户端调用的是注册中心中的远程实现类的代理,但就像是调用本地的方法一样,正常使用。
四、动态代理
动态代理有两种,一种是jdk动态代理,另一种是cglib动态代理,前者是根据继承当前类的接口,而cglib是继承当前类。所以jdk动态代理无法对没有实现接口的类进行代理。
jdk动态代理
UML:
主题对象
// 定义一个人的接口,用来创建代理~
public interface Person {
// 移动
void move();
// 获取名字
String getName();
}
真正主题对象,就是我Cderam,移动只能靠走路
public class Cdream implements Person {
private String name;
private String desc;
public Cdream() {
}
public Cdream(String name, String desc, String state) {
this.name = name;
this.desc = desc;
}
@Override
public void move(){
System.out.println("我现在移动只能靠走路!");
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
现在我要开始通过动态代理来改装自己了!!
// 搞一个handler
public class CdreamInvocationHandler implements InvocationHandler {
private Cdream cdream;
public CdreamInvocationHandler(Cdream cdream) {
this.cdream = cdream;
}
// proxy 代理对象
// method 调用方法
//
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用方法呢
Object invoke = method.invoke(cdream, args);
// 这个看起来太像Spring的Aop了,有木有?
if (method.getName().startsWith("move")) {
System.out.println("我要通过代理类给自己加技能了!");
System.out.println("穿上钢铁战衣,化身钢铁侠");
System.out.println("老子会飞了!哼!");
}
return invoke;
}
}
1.可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())
2.可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
测试类
public class Test {
public static void main(String[] args) {
Cdream cdream = new Cdream("在水一方","java的粉丝");
// 创建一个handler
CdreamInvocationHandler handler = new CdreamInvocationHandler(cdream);
// 创建代理类
Person o = (Person)Proxy.newProxyInstance(Cdream.class.getClassLoader(), cdream.getClass().getInterfaces(),
handler);
cdream.move();
System.out.println("---------");
o.move();
System.out.println("---------");
System.out.println(cdream.getName());
System.out.println(o.getName());
}
}
******输出结果*********
我现在移动只能靠走路!
---------
我现在移动只能靠走路!
我要通过代理类给自己加技能了!
穿上钢铁战衣,化身钢铁侠
老子会飞了!哼!
---------
在水一方
在水一方
看,我就这样穿上了钢铁战衣,化身钢铁侠,能飞上天与太阳肩并肩了!
这就是动态代理的功能,其实是把我加强了啊~~~~本来代理模式的目的是用来控制访问的,结果让我弄成加强自己了……有点像装饰者模式了,不过大家对付看吧,用法基本就是这个样子的。
看到这里一定有人要提问题了:“等等等等,我好像在哪里见过这个模式啊,哇!!!!这不是装饰者模式吗?小样,别以为你穿上马甲我就不认识你了!“
唉,其实我也觉得这两个模式太像了!然后我就谷歌啊百度,努力找到了这两个模式的不同,开始做笔记吧!
代理模式与装饰者模式的不同: