设计模式(1-1)-代理模式
几个月没写博客了。前些日子换了工作,把事情调整好了,又可以继续写博客了~
学习了下代理模式,本篇文章讲动态代理与静态代理模式怎么写,后续文章会讲动态代理(JDK动态代理)原理,讲讲怎么使用CGLIB实现没有接口的类的动态代理
一、代理在生活中的例子
代理, 代表授权方处理事务(wiki 的解释)。 举个例,顾客(client)想买一个u盘; 顾客想去西部数据工厂买一个u盘, 保安都直接把他拦住叫他去京东买; 没办法只能去被授权了的京东那去买,被可恶的中间商赚了差价。
虽然京东和西部数据都是卖u盘的,但是京东只是个代理商,最终需要的u盘还是要去西部数据进货,京东但是可以发优惠券在顾客买u盘前。
生活中的代理例子很多, 找中介租房子。自己不想做的事,可以交给代理方做,但是代理方也能力有限,最终的事还是需要交给本人去做。
代理模式有啥用?
- 方法增强, 代理方不仅仅是卖u盘了,还可以发放优惠券,赚取差价
- 控制访问,顾客去找西部数据工厂买个u盘,保安直接把他拦住了
二、 静态代理模式实现
下面我们先看看静态代理在Java中的实现
以买卖u盘为例子,
步骤一,定义一个接口(serviceInterface), 定义厂家与代理商的共同目标(卖u盘)
public interface UpanSell {
float sell(int amount);
}
步骤二,创建厂家类(serive),实现步骤一定义的接口
public class WesternDigitalUpanFactory implements UpanSell {
@Override
public float sell(int amount) {
float price = 85.0f * amount;
System.out.println("------西部数据卖出" + amount + "个u盘, 价格: " + price);
return price;
}
}
步骤三,创建一个商家类(proxy),实现相同的接口
public class jd implements UpanSell {
private final UpanSell upanSell = new WesternDigitalUpanFactory();
@Override
public float sell(int amount) {
float price = upanSell.sell(amount);
// -------增强功能 ↓↓↓↓↓↓↓↓↓↓
// 中间商赚差价
price += 25.f * amount;
System.out.println("------京东卖出" + amount + "个u盘, 价格: " + price);
System.out.println("------给你一个优惠券, 0.01分");
return price;
}
}
步骤四, 创建一个客户端(client)调用商家买U盘
public class Shop {
public static void main(String[] args) {
UpanSell upanSell = new jd();
upanSell.sell(2);
}
}
输出结果
------西部数据卖出2个u盘, 价格: 170.0
------京东卖出2个u盘, 价格: 220.0
------给你一个优惠券, 0.01分
上面的代理类(jd), 做了两件事,1. 中间商加了价格 2. 给了我们一张优惠券
我们能看到静态代理的实现还是挺简单的,也是多态的一个运用,我们是由商家类调用到的厂家类。
简单是简单,但是
- 如果多一个商家,比如淘宝,也要卖了,是不是需要多一个代理类。
- 如果serviceInterface,是不是厂家类与商家类都要修改。
那么,我们来看看动态代理是怎么把这些缺点规避掉的
运用JDK实现动态代理👇
三、动态代理模式的实现
动态代理,其实就是不用我们手动实现代理类,而是根据我们传入的参数在程序运行时,动态生成代理类。
3.1 JDK动态代理
我们在看JDK动态代理的代码前,不要问为什么要这么写,因为我们可以把它理解成一个模板,规定就是要这么写(不用背,写多了,就自然记住了)。
步骤一、二都跟上面一样
步骤一,定义一个接口(serviceInterface), 定义厂家与代理商的共同目标(卖u盘)
public interface UpanSell {
float sell(int amount);
}
步骤二,创建厂家类(serive),实现步骤一定义的接口
public class WesternDigitalUpanFactory implements UpanSell {
@Override
public float sell(int amount) {
float price = 85.0f * amount;
System.out.println("------西部数据卖出" + amount + "个u盘, 价格: " + price);
return price;
}
}
步骤三,实现InvocationHandler接口中的invoke() (定义代理类要干的事情) !!!
public class MySellHandler implements InvocationHandler {
/**
* 目标对象
*/
private final Object target;
public MySellHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行目标方法
Object res = method.invoke(target, args);
int amount = (int) args[0];
if(res != null)
{
float price = (Float) res;
price += 25.f * amount;
System.out.println("------京东卖出" + amount + "个u盘, 价格: " + price);
System.out.println("------给你一个优惠券, 0.01分");
res = price;
}
return res;
}
}
步骤四,获取代理实例,购买U盘 !!!
public class Shop {
public static void main(String[] args) {
// 1. 创建目标对象
UpanSell factory = new WesternDigitalUpanFactory();
// 2. 创建InvocationHandler对象
InvocationHandler handler = new MySellHandler(factory);
// 3. 生成一个代理实体类
UpanSell proxy = (UpanSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
factory.getClass().getInterfaces(),
handler
);
float price = proxy.sell(2);
}
}
输出结果
------西部数据卖出2个u盘, 价格: 170.0
------京东卖出2个u盘, 价格: 220.0
------给你一个优惠券, 0.01分
输出结果当然是一样的。步骤三、步骤四是最重要的,可以依葫芦画瓢,写一个新的JDK动态代理(中介租房...),不用在意细节!
我们能看见,如果增加或减少serviceInterface的方法,修改的地方没有静态代理的多;
用起来更加灵活,代理类与service是解耦的。唯一恼火,可能就是不太容易理解。
我们可以看到,我们并没有写代理的类,却实现了代理功能,如何看到这个生成的代理类?
在main方法的第一行加下面的属性
...
// 加一个这个玩意儿,就可以了
System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
...
生成的代理class文件
package com.sun.proxy;
import com.ukyu.dynamicproxy.service.UpanSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements UpanSell {
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final float sell(int var1) throws {
try {
return (Float)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.ukyu.dynamicproxy.service.UpanSell").getMethod("sell", Integer.TYPE);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
先对这个class文件混个眼熟吧,下一篇文章开始去了解JDK动态代理的原理。