一:定义
代理模式是常用的结构型设计模式之一,代理模式也叫委托模式。当客户端无法直接访问目标对象或者客户端访问目标对象存在困难时可以通过一个代理对象作为中介,让客户端来间接访问目标对象。为了保证客户端使用的透明性,所访问的真实目标对象与代理对象需要实现相同的接口。
也可以这样理解:当两个类需要通信时,引入第三方代理类,将两个类关系解耦,客户端只需要了解代理类的即可。
二:代理模式角色说明
Subject(抽象主题类):接口或者抽象类,作用是声明真实主题类与代理类的共同的方法,这样任何使用真实主题类的地方都可以使用代理类。
RealSubject(真实主题类):也称作被代理类,负责具体业务逻辑的执行。客户端可以通过代理类间接的调用真实主题类的方法。
Proxy代理类:持有真实主题类的引用,从而可以在任何时候操作真实主题对象;
Client客户端类:使用代理模式的地方。
三:静态代理和动态代理的区别
代理模式按照代理的创建时期,代理类可以分为两种。
(1)静态代理:代理类和被代理类在运行前关系已经确定了。由程序员创建源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
(2)动态代理:代理类和被代理类的关系在程序运行时才确定。在程序运行时,运用反射机制动态创建而成。
四:静态代理
以图片加载为例
/**
* Subject(抽象主题类)
*/
public interface ImageLoad {
void loadImage();
}
/**
* RealSubject(真实主题类)
*/
public class GlideImageLoad implements ImageLoad {
@Override
public void loadImage() {
System.out.println("Glide库 加载图片");
}
}
/**
* Proxy代理类,持有真实主题类的引用
*/
public class ImageLoadProxy implements ImageLoad {
private ImageLoad imageLoad;
public ImageLoadProxy() {
imageLoad = new GlideImageLoad();
}
@Override
public void loadImage() {
System.out.println("代理类加载图片");
if (imageLoad != null) {
imageLoad.loadImage();
}
}
}
/**
* Client客户端类:使用代理模式的地方
*/
public class ImageLoadDemo {
public static void main(String[] args) {
ImageLoadProxy imageLoadProxy = new ImageLoadProxy();
imageLoadProxy.loadImage();
}
}
打印结果:
代理类加载图片
Glide库 加载图片
静态代理优点:
-
职责清晰:真实角色就是实现实际的业务逻辑,不用关心其他非本职责的事物,通过代理完成调用,附带的结果就是编程的简洁清晰。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给为委托类,以及事后处理消息等。
-
降低耦合:代理作为调用者和真实主题的中间层,降低了模块间和系统的耦合性。
-
保护目标对象:代理对象能够控制调用者的访问权限,起到了保护真实主题的作用。
静态代理缺点:
1.增加了代码复杂度:代理类和委托类实现了相同的接口,代理通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码复杂度。
2.如果新增了抽象主题类,要新增一个代理。
五:动态代理
静态代理扩展和维护困难,因为代码写的太固定,没有可替换的余地。针对这种问题,我们可以通过反射来解决。反射可以在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
如何实现动态代理呢?java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke()方法。
我们先来看一下InvocationHandler这个接口。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
/**
* 动态代理类
*/
public class ImageLoadProxyDynamic implements InvocationHandler {
private Object obj;//被代理的对象
public ImageLoadProxyDynamic(Object obj) {
this.obj = obj;
}
/**
* proxy:指的是我们所代理的那个真实对象
* method:指的是我们所要调用真实对象的某个方法的Method对象
* args:指的是调用真实对象某个方法时接受的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("动态代理调用方法: " + method.getName());
Object result = method.invoke(obj, args);//调用被代理的对象的方法
return result;
}
}
/**
* Client客户端使用动态代理
*/
private static void loadImage() {
ImageLoadProxyDynamic proxy = new ImageLoadProxyDynamic(new GlideImageLoad()); //创建动态代理
//通过 Proxy创建代理实例 ,实际上通过反射来实现的。
ImageLoad imageLoad = (ImageLoad) Proxy.newProxyInstance
(ImageLoad.class.getClassLoader(), new Class[]{ImageLoad.class}, proxy);
imageLoad.loadImage();
}
打印结果:
动态代理调用方法: loadImage
Glide库 加载图片
newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
loader:一个ClassLoader对象,定义了由当前的ClassLoader对象来对生成的代理对象进行加载
interfaces:一个Interface对象的数据,表示:代理对象可以调用的方法。
h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,
会关联到哪一个InvocationHandler对象上
动态代理的优点:
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
动态代理类不仅简化了编程工作,而且提高了软件系统的扩展性,因为Java反射机制可以生成任意类型的动态代理类。
六:静态代理使用场景:
一:封装真实的主题类,将真实的业务逻辑隐藏,只暴露给调用者公共的主题接口。刚才的图片加载。
二:做事物控制。比如版本适配。类似于数学的分段函数。假如出租车计费
小于2km,10元钱。2-10km,每km是5元钱。10km以上,每km是6元钱。
这时候就可以针对行程数写三个真实主题类。只要在代理类添加一个行程数的判断,调用哪一个就可以了。