目录
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修饰。