一、基本介绍
1. 代理模式的基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
- 被代理的对象可以是远程对象、创建开销大的对象或者需要安全控制的对象
- 代理模式有不同的形式,主要有三种静态代理、动态代理(JDK代理、接口代理)和CGLIB代码(可以在内存动态创建对象,而不需要实现接口,他是属于动态代理的范畴)
二、静态代理
1. 静态代理模式代码的基本介绍
- 静态代理在使用时,需要定义接口,被代理对象(即目标对象)与代理对象一起实现相同接口。
2. 应用实例
现在有一个场景,就是你想买Switch游戏机,但是正版的Switch只在日本生产和销售,所以你想买Switch游戏机,只能在淘宝、京东等这些地方去购买。那么你从淘宝、京东上购买的Switch的价格和直接在日本购买的价格是一样的吗,肯定是淘宝、京东上买的更贵,这就是功能增强,提高Switch的价格。
(1) 定义一个接口:SwitchSell
public interface SwitchSell {
/**
* 根据购买相应switch的数量,返回相应的价格。
* @param buySwitchNum 购买switch的数量
* @return
*/
double sell(long buySwitchNum);
}
(2)目标类对象NintendoSwitchSell实现SwitchSell
public class NintendoSwitchSell implements SwitchSell{
public static final double SWITCH_PRICE = 100.0d;
/**
* 任天堂:一个switch100元,购买100个打9折
* @param buySwitchNum 购买switch的数量
* @return
*/
@Override
public double sell(long buySwitchNum) {
return buySwitchNum < 100 ? buySwitchNum*SWITCH_PRICE : buySwitchNum*SWITCH_PRICE*0.9;
}
}
(3)代理对象TaoBaoSwitchSell实现了SwitchSell
public class TaobaoSwitchSell implements SwitchSell{
public static final double SWITCH_PRICE = 120.0d;
private NintendoSwitchSell nintendoSwitchSell;
public TaobaoSwitchSell(NintendoSwitchSell nintendoSwitchSell) {
this.nintendoSwitchSell = nintendoSwitchSell;
}
/**
* 淘宝:一个Switch游戏机120元一个
* @param buySwitchNum 购买switch的数量
* @return
*/
@Override
public double sell(long buySwitchNum) {
// 可以在这里做一些功能增强,例如,购买会员,满多少送一个优惠券、购买数量满多少打折。
// 这里提高售价就是功能增强的一种体现
double price = nintendoSwitchSell.sell(buySwitchNum);
System.out.println("代理商在任天堂购买"+buySwitchNum+"个Switch,共花了"+price+"元");
price = buySwitchNum * SWITCH_PRICE;
System.out.println("用户在淘宝购买"+buySwitchNum+"个Switch,共花了"+price+"元");
return price;
}
}
(4)调用的时候通过调用代理对象的方法来调用目标对象
public class SwitchTest {
public static void main(String[] args) {
// 创建目标对象
NintendoSwitchSell nintendoSwitchSell = new NintendoSwitchSell();
// 创建代理对象,同时将被代理对象传递给代理对象
TaobaoSwitchSell taobaoSwitchSell = new TaobaoSwitchSell(nintendoSwitchSell);
// 通过代理对象,调用被对象对象的方法
// 执行的是代理对象的方法,代理对象再去调用目标对象的方法
taobaoSwitchSell.sell(100);
}
}
3、静态代理优缺点
- 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
- 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类
- 一旦接口增加方法,目标对象与代理对象都要维护
三、动态代理
1、动态代理模式的基本介绍
- 目标对象要实现接口,否则不能用动态代理
- 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
- 动态代理也叫做:JDK代理、接口代理
2、JDK代理生成代理对象的API
- 代理类所在的包:java.lang.reflect.Proxy
- JDK实现代理只需要使用newProxyInstance方法,但是改方法需要接受三个参数,完整的写法是:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
loader:用于定义代理类的类装入器
interfaces:目标类所实现的接口
h:调用处理程序,这是一个接口,只要一个invoke方法。执行代理类的相应方法时,方法内部会调用处理程序的invoke方法,而在invoke方法里面我们自己可以实现,通过反射从而调用目标对象的方法。
3、动态代理实例
(1)创建调用处理程序
/**
* 调用处理器实现类
* 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
*/
public class InvocationHandlerImpl implements InvocationHandler {
/**
* 这个就是我们要代理的真实对象
*/
private NintendoSwitchSell nintendoSwitchSell;
/**
* 构造方法,给我们要代理的真实对象赋初值
*
* @param nintendoSwitchSell
*/
public InvocationHandlerImpl(NintendoSwitchSell nintendoSwitchSell) {
this.nintendoSwitchSell = nintendoSwitchSell;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用。
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*
* @param proxy 代理类实例
* @param method 被调用的方法对象
* @param args 调用参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object price = method.invoke(nintendoSwitchSell, args);
long buySwitchNum = Long.parseLong(args[0].toString());
System.out.println("代理商在任天堂购买"+buySwitchNum+"个Switch,共花了"+price+"元");
price = buySwitchNum * 120.0d;
System.out.println("用户在淘宝购买"+buySwitchNum+"个Switch,共花了"+price+"元");
//在代理真实对象后我们也可以添加一些自己的操作
return price;
}
}
(2)调用代码
public class SwitchTest {
public static void main(String[] args) {
// 创建目标对象
NintendoSwitchSell nintendoSwitchSell = new NintendoSwitchSell();
// 创建代理对象,同时将被代理对象传递给代理对象
TaobaoSwitchSell taobaoSwitchSell = new TaobaoSwitchSell(nintendoSwitchSell);
// 通过代理对象,调用被对象对象的方法
// 执行的是代理对象的方法,代理对象再去调用目标对象的方法
taobaoSwitchSell.sell(100);
// 创建代理对象
SwitchSell proxy = (SwitchSell) Proxy.newProxyInstance(NintendoSwitchSell.class.getClassLoader(), NintendoSwitchSell.class.getInterfaces(