目录
一、什么是代理模式
Proxy模式又叫做代理模式,是构造型的设计模式之一,它可以为其他对象提供一种代理(Proxy)以控制对这个对象的访问。 所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代理与被代理的目标类交互,而代理一般在交互的过程中(交互前后),进行某些特别的处理。
二、代理模式的结构
三、代理模式的角色和职责
subject(抽象主题角色)
真实主题与代理主题的共同接口,这样就可以在任何使用具体目标对象的地方使用代理对象
RealSubject(真实主题角色)
定义了代理角色所代表的真实对象。真正实现目标接口要求的功能
Proxy(代理主题角色)
实现与目标对象一样的接口,这样就可以使用代理对象代替具体的目标对象
含有对真实主题角色的引用,可以在任何需要的时候调用具体的目标对象
代理角色通常在将客户端调用传递给真是主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。
可以控制对具体目标对象的访问,并可能负责创建和删除它
四、简单示例
例如,出版社出了一本书并出售,书店代替出版社卖书(可以有折扣或者代金券等优惠),客户通过书店买书
出版社和书店的共同接口:
public interface Subject {
public void sellBook();
}
出版社卖书:
public class RealSubject implements Subject {
public void sellBook() {
System.out.println("卖书");
}
}
书店卖书:
public class ProxySubject implements Subject{
private RealSubject realSubject;
public void sellBook() {
disCount();
if(realSubject == null) {
realSubject = new RealSubject();
}
realSubject.sellBook();
coupon();
}
public void disCount() {
System.out.println("打折");
}
public void coupon() {
System.out.println("赠送代金券");
}
}
客户买书:
public class MainClass {
public static void main(String[] args) {
ProxySubject proxySubject = new ProxySubject();
proxySubject.sellBook();
}
}
由以上代码可以看出,客户端调用的实际是RealSubject的sellBook方法,用ProxySubject来代理RealSubject也能达到同样的效果,同时还封装了其他的方法(disCount,coupon)
但是,按照上述方法实现代理,那么真实角色必须是已经存在的,并将其作为代理对象的内部属性。但是实际应用时,一个真实对象必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;另外,如果事先不知道真实角色,也无法使用代理,所以,需要使用java动态代理来解决。
五、动态代理
Java动态代理类主要位于java.lang.reflect包下,涉及到以下两个类:
1.InvocationHandler
public interface InvocationHandler
InvocationHandler
是代理实例的调用处理程序 实现的接口。每个代理实例都具有一个关联的Invocation Handler。当调用代理实例的方法时,将对方法调用进行编码并将其指派到它的Invocation Handler的
invoke
方法。- invoke(Object proxy,Method method,Object[] args)
在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
参数:
proxy
- 在其上调用方法的代理实例
method
- 对应于在代理实例上调用的接口方法的Method
实例。Method
对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
args
- 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为null
。基本类型的参数被包装在适当基本包装器类(如java.lang.Integer
或java.lang.Boolean
)的实例中。返回:
从代理实例的方法调用返回的值。如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为
null
并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出NullPointerException
。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出ClassCastException
。抛出:
Throwable
- 从代理实例上的方法调用抛出的异常。该异常的类型必须可以分配到在接口方法的throws
子句中声明的任一异常类型或未经检查的异常类型java.lang.RuntimeException
或java.lang.Error
。如果此方法抛出经过检查的异常,该异常不可分配到在接口方法的throws
子句中声明的任一异常类型,代理实例的方法调用将抛出包含此方法曾抛出的异常的UndeclaredThrowableException
。
2.Proxy(动态代理类)
Proxy
提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。- newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数:
loader
- 定义代理类的类加载器
interfaces
- 代理类要实现的接口列表
h
- 指派方法调用的调用处理程序返回:
一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
抛出:
IllegalArgumentException
- 如果违反传递到getProxyClass
的参数上的任何限制
NullPointerException
- 如果interfaces
数组参数或其任何元素为null
,或如果调用处理程序h
为null
所谓动态代理是这样一种class,是一种在运行时生成的class,在生成它时必须提供给一组interface给它,然后该class宣称它实现了这些interface,可以把该class当成这些interface中的任意一个来用,其实就是一个Proxy,他不会替你做任何实质性的工作,在生成实例时必须提供一个handler,由他接管实际工作。
动态代理实现:
public interface Subject {
public void sellBook();
}
public class RealSubject implements Subject {
public void sellBook() {
System.out.println("卖书");
}
}
public class RealSubject2 implements Subject {
public void sellBook() {
System.out.println("卖书");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*该代理类的内部属性是Object,实际使用时通过set方法(构造方法)传递进来一个对象
*此外,该类实现了invoke方法,该方法中的method.invoke其实就是调用被代理对象将要
*执行的方法,方法参数是obj,表示改方法从属于obj,我们可以在执行真实对象的方法前后加入额外方法
*/
public class MyHandler implements InvocationHandler {
/**
*维护对真实对象的引用
*/
private Object obj;
/**
*也可以通过构造方法传值
*/
public void setRealSubject(Object realSubject) {
this.obj= realSubject;
}
/**
*重写invoke
*/
public Object invoke(Object proxy, Method method, Object[] args){
Object result = null;
disCount();
try {
result = method.invoke(realSubject, args);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
coupon();
return result;
}
public void disCount() {
System.out.println("打折");
}
public void coupon() {
System.out.println("赠送代金券");
}
}
import java.lang.reflect.Proxy;
public class Client{
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
InvocationHandler myHandler = new MyHandler();
myHandler.setRealSubject(realSubject);
//下面代码一次性生成代理 Proxy.newProxyInstance
Subject subject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(), realSubject.getClass().getInterfaces(), myHandler);
//当执行到这里,不管调用subject哪个方法,都会进入到myHandler的invoke方法中
subject.sellBook();
//查看subject的实际类型 $Proxy0
System.out.println(subject.getClass());
RealSubject2 realSubject2 = new RealSubject2();
myHandler.setRealSubject(realSubject2);
//下面代码一次性生成代理 Proxy.newProxyInstance
subject = (Subject)Proxy.newProxyInstance(RealSubject2.class.getClassLoader(), realSubject2.getClass().getInterfaces(), myHandler);
//当执行到这里,不管调用subject哪个方法,都会进入到myHandler的invoke方法中
subject.sellBook();
//查看subject的实际类型 $Proxy0
System.out.println(subject.getClass());
}
}
六、理解代理模式
认识代理模式
1.代理模式的功能
代理模式是通过创建一个代理对象,用这个代理对象去代表真实对象,客户端得到这个代理对象过后,对客户端没什么影响,就和得到了真实对象一样使用
当客户端操作这个对象时,实际上功能最终还是由真实的对象来完成,只不过是通过代理来操作的,也就是客户端操作代理,代理操作真实对象
正是因为有代理对象夹在客户端和被代理对象中间,相当于一个中转,那么在中转的时候就可以执行其他操作。
2.代理的分类
(1)虚代理:根据需要来创建开开销很大的对象,该对象只有在需要的时候才会被真正创建
(2)远程代理:用来在不同的地址空间上代表同一个对象,这个不同的地址空间可以是在本机,也可以是在其他机器上,例如java中的RMI
(3)copy-on-write代理:在客户端操作的时候,只有对象确实改变了,才会真的拷贝一个目标对象,算是虚代理一个分支.
拷贝一个大的对象是很消耗资源的,如果这个被拷贝的对象从上次操作以来,根本没有被修改过,那么没必要再拷贝这个对象。可以使用代理来延迟拷贝的过程,等到对象被修改时再进行拷贝
(4)保护代理:控制对原始对象的访问,如果有需要,可以给不同的用户提供不同的访问权限,以控制他们对原始对象的访问。保护代理会检查调用者是否具有请求所必需的访问权限,如果没有相应的权限,那么就不会调用目标对象,从而实现对目标对象的保护
(5)Cache代理:为那些昂贵的操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
(6)防火墙代理:保护对象不被恶意用户访问和操作
(7)同步代理:使多个用户能够同时访问目标对象而没有冲突
(8)智能指引:在访问对象时执行一些附加操作,比如:对指向实际对象的引用计数,第一次引用一个持久对象时,装入内存等
3.具体目标和代理的关系
如果一个代理类能完全通过接口来操作它所代理的目标对象,那么代理对象就不需要知道具体的目标对象,无须为每一个具体目标创建一个代理类
但是如果代理类必须要实例化他代理的目标对象,那么代理类就必须知道具体被代理的对象,一个具体目标类通常会有一个代理类。
java中的代理
1.静态代理
如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不灵活
2.动态代理
动态代理实现的时候,虽然Subject接口上定义了很多方法,但是动态代理类始终只有一个invoke方法,这样当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了。
七、思考代理模式
本质
控制对象访问
何时选用
1.需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理
2.按照需要创建开销很大的对象的时候,可以使用虚代理
3.需要控制原始对象的访问的时候,可以使用保护代理
4.需要在访问对象的时候执行一些附加操作的时候,可以使用智能指引代理