23种设计模式(6):代理模式

1.概念

定义:为另一个对象提供代理,以控制外界对其的访问。

​ 维基百科上的对代理模式是这样解释的,代理是客户端正在调用的包装器或代理对象,以访问幕后的真实服务对象。代理的使用可以简单地转发到真实对象,也可以提供其他逻辑。在代理中,可以提供额外的功能,例如在对实际对象的操作占用大量资源时进行缓存,或者在对实际对象的操作被调用之前检查先决条件。

​ 我们举一个例子加以说明,从前有一个象牙塔,当地的巫师都去那里学习法术。随着时间的推移,当地学习法术的巫师越来越多,最后造成每天的象牙塔人满为患。于是请来了两个力大无比的门卫战士,他们规定每次只能进去三个巫师进行学习。于是,这两个门卫战士就像是象牙塔的代理,代理代表塔的访问出入权限并向其添加访问控制。简而言之,使用代理模式,用一个类表示另一个类的功能。

2.程式范例

2.1巫师
public class Wizard {
    private String name;

    public Wizard(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

2.2塔接口
public interface Tower {
    public void enter(Wizard wizard);
}
2.3象牙塔实现类
/**
 * 象牙塔(所有人都能进入,没有人数限制)
 *
 * @author suvue
 */
@Slf4j
public class IvoryTower implements Tower {
    @Override
    public void enter(Wizard wizard) {
        log.info("{}进入了象牙塔", wizard.toString());
    }
}
2.4象牙塔的代理类(相当于例子的门卫)
/**
 * 我是象牙塔的代理,最多只允许3个人进入
 *
 * @author suvue
 */
@Slf4j
public class IvoryTowerProxy implements Tower {
    //允许进入的最大人数
    private static final int NUMBER_ALLOW = 3;
    private Tower tower;
    private int numWizards;

    //构造函数
    public IvoryTowerProxy(Tower tower) {
        this.tower = tower;
    }

    @Override
    public void enter(Wizard wizard) {
        if (numWizards < NUMBER_ALLOW) {
            tower.enter(wizard);
            numWizards++;
        } else {
            log.error("象牙塔的进入人数已满,请稍后再来!");
        }
    }
}
2.5客户端调用者
public class Client {
    public static void main(String[] args){
        IvoryTowerProxy towerProxy = new IvoryTowerProxy(new IvoryTower());
        towerProxy.enter(new Wizard("红巫师"));
        towerProxy.enter(new Wizard("黄巫师"));
        towerProxy.enter(new Wizard("蓝巫师"));
        towerProxy.enter(new Wizard("绿巫师"));
        towerProxy.enter(new Wizard("白巫师"));
    }
}
2.6结果打印
红巫师进入了象牙塔
黄巫师进入了象牙塔
蓝巫师进入了象牙塔
象牙塔的进入人数已满,请稍后再来!
象牙塔的进入人数已满,请稍后再来!
2.7.小结

​ 上述的象牙塔人满为患时需要找两个守卫来限流,在程序中,这就是静态代理的实现,它为每个实际对象都产生一个代理类,并将其作为内部属性。但是世界上还有很多的象牙塔呢?都需要限流的时候该怎么办呢?每个象牙塔都需要找俩守卫吗?这就是静态代理的缺点了,这会导致类数量的急速增加,并且代理类的代码存在大量重复,在例子里就是每个象牙塔都需要俩职责一样的守卫了。

	我们换一种想法,假如让部落 首领出台一个法令,规定所有的象牙塔每次只能进入
	3个人学习,那么是不是就不用招募守卫了呢?这就是我们动态代理的实现。

3.jdk动态代理模式

概念:简而言之,就是将有一样功能的代理类,合并为一个代理类,这样的话就减少了很多重复功能的代理类。

我们对上面的例子进行优化优化,直接看代码吧。

先来看jdk中一个重要的接口类

3.1 InvocationHandler接口
package java.lang.reflect;
//一个类若想实现代理功能,就必须实现这个接口
public interface InvocationHandler {
    //实现该方法的功能
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

}
3.2Proxy类
package java.lang.reflect;
//提供创建动态代理类和实例的静态方法,并且它也是这些方法创建的所有代理类的父类
public class Proxy implements java.io.Serializable {
//下面这个方法返回一个实现了InvocationHandler的代理类
@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
       //...
    }
}
3.3程式范例
package cn.suvue.discipline.practice.designpattern.proxy.jdk;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * jdk动态代理类
 *
 * @author suvue
 * @date 2020/1/14
 */
@Slf4j
public class JdkProxyHandler implements InvocationHandler {

    private static final int NUMBER_ALLOW = 3;
    private int numWizards = 0;
    private Object target;

    //注意:这里构造方法 传递的参数时object类型
    public JdkProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (numWizards < NUMBER_ALLOW) {
            method.invoke(target, args);
            numWizards++;
        } else {
            log.error("象牙塔的进入人数已满,请稍后再来!");
        }
        return null;
    }
}

客户端调用方

public class Client {
    public static void main(String[] args){
        Tower target = new IvoryTower();
        Tower proxyInstance = (Tower)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new JdkProxyHandler(target));
        proxyInstance.enter(new Wizard("白巫师"));
        proxyInstance.enter(new Wizard("黑巫师"));
        proxyInstance.enter(new Wizard("红巫师"));
        proxyInstance.enter(new Wizard("蓝巫师"));
        proxyInstance.enter(new Wizard("绿巫师"));
    }
}

​ 用了动态代理,本来对象牙塔管用的控制进入权限功能,现在对城堡、农场、学校来说都可以使用了,因为它使用的反射机制,只有在运行时才确定参数类型。这一点确实很牛逼,当然也很方便。

spring框架中也用到了这种模式,大家可以看看 JdkDynamicAopProxy 这个类。

注:但是JDK的动态代理是通过代理接口实现的,如果对象没有实现接口,那就无能为力了

4.CGLIB动态代理模式

4.1程式范例
package cn.suvue.discipline.practice.designpattern.proxy.cglib;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * cglib动态代理
 *
 * @author suvue
 * @date 2020/1/14
 */
@Slf4j
public class CglibInterceptor implements MethodInterceptor {
    private static final int NUMBER_ALLOW = 3;
    private int numWizards = 0;


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if (numWizards < NUMBER_ALLOW) {
            methodProxy.invokeSuper(o, objects);
            numWizards++;
        } else {
            log.error("象牙塔的进入人数已满,请稍后再来!");
        }
        return null;
    }
}

客户端调用方

public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(IvoryTower.class);
        enhancer.setCallback(new CglibInterceptor());
        Tower tower = (Tower) enhancer.create();
        tower.enter(new Wizard("白巫师"));
        tower.enter(new Wizard("黑巫师"));
        tower.enter(new Wizard("红巫师"));
        tower.enter(new Wizard("蓝巫师"));
        tower.enter(new Wizard("绿巫师"));
    }
}

可以看到,CGLIB是通过动态生成目标类的子类,在子类中设置拦截逻辑,来进行动态代理。因此目标类的方法不能被final修饰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值