代理模式
简介
什么是代理模式:
代理模式,也称委托模式,是结构型设计模式之一。是为其他对象提供一种代理以控制这个对象的访问。
适用场景:
- 当不想访问某个对象或访问某个对象存在困难时,就可以为这个对象创建一个代理,通过代理来间接的访问这个对象;
- 如果原始对象有不同的访问权限,可以使用代理控制对原始对象的访问,保护原始对象;
- 在访问原始对象时执行一些自己的附加操作;
区别:
静态代理在我们的代码运行之前,代理类的.class文件就已经存在,而动态代理则与静态代理相反,在代码运行之前不存在代理类的.class文件,在代码运行时才动态的生成代理类。
静态代理
使用静态代理
- 定义代理对象和真实对象的公共接口;
- 真实对象实现公共接口中的方法;
- 代理对象实现公共接口中的方法,并把方法的逻辑转发给真实对象。
以张三买房为例:
张三(真实对象)想买房,但由于太慢就没直接找房东,而是找了一个中间商(代理对象),告诉了中间商他的想法,中间商帮他买到了房,他也支付了中间商一定费用(附加操作)
图解:
- 公共租房接口
public interface IRoom {
void seekRoom();//找房
void watchRoom();//看房
void room();//给钱租房
void finish();//完成租房
}
- 真实角色
public class ZhangSan implements IRoom{
@Override
public void seekRoom() {
System.out.println("找房");
}
@Override
public void watchRoom() {
System.out.println("看房");
}
@Override
public void room() {
System.out.println("给钱租房");
}
@Override
public void finish() {
System.out.println("完成租房");
}
}
- 代理角色
public class Middleman implements IRoom{
private IRoom mRoom;//持有一个被代理人(张三)的引用
public Middleman(IRoom room){
this.mRoom = room;
}
@Override
public void seekRoom() {
mRoom.seekRoom();
}
@Override
public void watchRoom() {
mRoom.watchRoom();
}
@Override
public void room() {
mRoom.room();
}
@Override
public void finish() {
mRoom.finish();
}
/**
* 附加操作
*/
public void getMoney(){
System.out.println("中间商转差价");
}
}
- 测试
public class Test {
public static void main(String[] args) {
//张三想租房
ZhangSan zhangSan = new ZhangSan();
//找一个代理人,房产中介
Middleman middleman = new Middleman(zhangSan);
//房产中介找房
middleman.watchRoom();
//房产中介看房
middleman.seekRoom();
//房产中介租房
middleman.room();
//房产中介完成租房
middleman.finish();
//附加操作,中间商转差价
middleman.getMoney();
}
}
优缺点
优点:
- 代理类作为客户端和被代理类之间的中介,起到了保护被代理类的作用
- 通过接口对代理类和被代理类进行解耦,降低了系统的耦合度
缺点:
- 由于在客户端和被代理类之间增加了代理对象,因此会造成请求的处理速度变慢
- 如果张三是想要买房而不是租房,这时房产中介还能满足张三的需求吗?很显然不能了,因为这个房产中介它只有替人租房的能力,没有替人买房的能力,这时就需要更换租房接口为买房接口,再定义一个专门买房的的房产中介,你会发现我每次更换接口,都需要更换代理类,这就是静态模式的缺点,只能为给定接口下的实现类做代理,如果接口不同就需要定义不同的代理类,随着系统的复杂度增加,就会很难维护这么多代理类和被代理类之间的关系,这时动态代理就应运而生,当需要频繁的更换接口,更换代理类时,采用动态代理是一个更好的选择,动态代理可以通过一个代理类来代理N多个被代理类,它在更换接口时,不需要重新定义代理类,因为动态代理不需要根据接口提前定义代理类,它把代理类的创建推迟到代码运行时来完成。
动态代理
为了让我们更加容易的实现动态代理,java提供了动态代理接口InvocationHandler和动态代理类Proxy供我们使用,它们都在java.lang.reflect包中,
可见❤️ 动态代理和反射有不可逃脱的关系❤️ 。
InvocationHandler和Proxy
- InvocationHandler
public interface InvocationHandler {
/**
* 这个方法的含义是:代理对象proxy要调用真实对象的method
* @param proxy 代理对象
* @param method 真实对象被调用的方法
* @param args 被调用的方法的参数
*/
Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
}
InvocationHandler接口的作用就是在invoke方法中执行真实对象的方法,可以看到里面只有一个invoke方法,我们需要为真实对象定义一个实现了这个接口中的invoke方法的动态代理类,同时在创建这个动态代理类的实例的时候,我们还要在方法或构造中传入真实对象的引用,即InvocationHandler的实现类需要持有真实对象的引用,这样才能执行真实对象的方法。
- Proxy
public class Proxy implements Serializable {
protected InvocationHandler h;//持有一个InvocationHandler类型的引用
protected Proxy(InvocationHandler h) {
this.h = h;
}
//根据指定的类加载器和接口来获取代理对象的Class对象
public static Class<?> getProxyClass(ClassLoader loader, Class... interfaces) throws IllegalArgumentException {
//...
}
//根据指定的类加载器和接口生成代理对象
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
//...
}
//...
}
Proxy这个类的作用就是用来动态的创建一个代理对象,它内部会持有一个InvocationHandler引用,在构造中传入,它提供了许多的方法,但是我们常用的是 getProxyClass方法和newProxyInstance方法:
- getProxyClass(重点方法):这个方法的作用是在运行时根据.class的结构生成一个代理Class二进制流,并通过传入的ClassLoader去把代理Class二进制流加载成一个代理Class对象,该代理Class对象继承Proxy并实现了传入的第二个参数对应的Interface列表。
- newProxyInstance(常使用的方法): 这个方法的作用是在运行时根据代理Class对象生成代理对象实例,这个方法中会先调用了getProxyClass方法生成代理Class对象,在获取到代理Class对象后,根据第三个参数InvocationHandler引用通过反射创建代理对象实例,所以newProxyInstance最终的结果是生成一个代理对象实例,该代理对象会继承Proxy类并实现给定的接口列表,同时内部持有一个InvocationHandler引用。
使用动态代理
- 定义代理对象和真实对象的公共接口;
- 真实对象实现公共接口中的方法;
- 定义一个实现了InvocationHandler接口的动态代理类;
- 通过Proxy类的newProxyInstance方法创建代理对象,调用代理对象的方法。
张三买房
- 定义代理对象和真实对象的公共接口
public interface IRoom {
void seekRoom();//找房
void watchRoom();//看房
void room();//给钱租房
void finish();//完成租房
}
- 真实对象实现公共接口中的方法
public class ZhangSan implements IRoom{
@Override
public void seekRoom() {
System.out.println("找房");
}
@Override
public void watchRoom() {
System.out.println("看房");
}
@Override
public void room() {
System.out.println("给钱租房");
}
@Override
public void finish() {
System.out.println("完成租房");
}
}
- 定义一个实现了InvocationHandler接口的动态代理类
/**
* 实现了InvocationHandler接口的动态代理类;
*/
public class DynamicProxy implements InvocationHandler {
private Object mObject;//真实对象的引用
public DynamicProxy(Object object){
this.mObject = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(mObject, args);//反射调用方法:方法名.invoke(对象,参数)
return invoke;
}
}
- 通过Proxy类的newProxyInstance方法创建代理对象,调用代理对象的方法
public class Test {
public static void main(String[] args) {
//小明想租房
ZhangSan zhangSan = new ZhangSan();
//构造一个动态代理
DynamicProxy dynamicProxy=new DynamicProxy(zhangSan);
//获取被代理类小明的ClassLoader
ClassLoader classLoader = zhangSan.getClass().getClassLoader();
System.out.println(classLoader);
//获得一个代理Class对象
Class<?> proxyClass = Proxy.getProxyClass(classLoader, new Class[]{IRoom.class});
//生成了代理对象
IRoom iRoom = (IRoom) Proxy.newProxyInstance(classLoader, new Class[]{IRoom.class}, dynamicProxy);
//代理对象调用真实对象方法
iRoom.watchRoom();
iRoom.seekRoom();
iRoom.room();
iRoom.finish();
}
}
❤️ 注意生成的代理对象被强转为接口
优缺点
优点:
- 代理类在程序运行时由反射自动生成,无需我们手动编写代理类代码,简化编程工作
- 一个动态代理类InvocationHandler就能代理多个被代理类,较为灵活
缺点:
- 利用反射,效率低
- 只能代理实现了接口的类,而不能代理实现抽象类的类