概念
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。
使用图表表示如下:
目的
在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
使用场景
但需要对现有的代码增加新的功能时,如果直接在原有代码上修改,一方面要熟悉之前的代码,另外修改的代码还可能带来新的问题。因此这时代理模式就可以很好的解决这个问题:不修改之前已经写好的代码或者方法,通过代理的方式对该方法进行功能拓展。
两种代理模式
1 静态代理
关键点
用户访问的是目标对象的代理对象,目标对象与代理对象实现同一接口或继承同一父类,且代理对象中持有目标对象。
举例如下
用户(User)去买东西(buy),如果自己去市场买,那么花费说要购买物品的价格即可;如果不想跑远,只想到楼下的小商铺那里买,那人家就要收取点福利了。。。。
代码实现
买东西接口:
public interface IBuy {
void buy(long money);
}
买东西的用户,实现这个买的接口:
public class User implements IBuy {
@Override
public void buy(long money) {
System.out.println("买东西,一共花了"+ money+"元!");
}
}
然后呢,楼下小商铺代理,帮用户从市场把东西买来了,那么就要收点福利。。所以楼下小商铺代理也具有买这个功能:
public class UserProxy implements IBuy {
private IBuy mIBuy; // 真实代理的对象
public UserProxy(IBuy IBuy) {
mIBuy = IBuy;
}
@Override
public void buy(long money) {
long fee = (long) (money * 0.01); // 代理对象的一些附加福利
System.out.println("静态代理中介,坑了" + fee + "中介费!");
mIBuy.buy(money + fee); // 真实对象执行
}
}
然后是我们的测试代码:
@Test
public void buy() {
IBuy user = new User();
user.buy(10000);
IBuy userProxy = new UserProxy(user);
userProxy.buy(10000);
}
执行,测试,输出如下结果:
可以看到,第一行输出是用户直接购买物品说需要花费的钱,下面两行是用户使用了小商铺这种代理,来购买东西,所需要付给小商铺的福利,以及一共花费的金额。
这就是静态代理模式的实现,从实现上看,代理对象的确没有对目标对象的代码做修改,只是在目标对象功能的基础上,添加了一些其他的附加功能;并且从代理对象中持有目标对象这一设计可以看出,代理对象可以完全控制目标对象的功能是否执行。但是,从中我们也可以看出静态代理模式也存在下面这个缺点:
代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
所以既然有静态代理,那么对应的应该也有个动态代理,不知道动态代理能不能解决这个缺点呢?
1 动态代理
关键点
1.代理对象,需要实现JDK中的接口:InvocationHandler,并实现方法invoke;
2.代理对象的生成,是利用JDK的API,程序运行时生成,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
JDK api介绍
JDK中生成代理对象的API
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
ClassLoader loader:当前目标对象的类加载器
Class<>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
InvocationHandler h:实现了InvocationHandler接口的代理对象
代码实现
动态代理对象:
public class DProxy implements InvocationHandler {
private IBuy mBuy; // 持有的目标对象
public DProxy(IBuy buy) {
mBuy = buy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("buy")) {
long money = (long) args[0];
long fee = (long) (money * 0.02);
System.out.println("动态代理中介,坑了" + fee + "中介费!");
long newMoney = money + fee;
args[0] = newMoney;
return method.invoke(mBuy, args);
}
return null;
}
}
代码中,由于我们知道接口实现的名称以及接口参数,所以在invoke中,我们直接对buy方法进行了额外的功能添加,如果不知道接口对应的名称以及接口参数,那么可以直接使用反射的方法执行method对应的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("附加功能1");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("附加功能2");
return returnValue;
}
然后呢,是我们的测试代码:
@Test
public void buy() {
IBuy user = new User();
user.buy(10000);
IBuy userProxy = new UserProxy(user);
userProxy.buy(10000);
IBuy dProxy = (IBuy) Proxy.newProxyInstance(user.getClass().getClassLoader(),user.getClass().getInterfaces(),new DProxy(user));
dProxy.buy(10000);
}
最终的执行结果:
总结:动态代理的invoke方法中,是利用反射的方法来执行接口中的method方法的,所以无论接口中的方法增加还是减少,都不影响动态代理的invoke方法执行,因而可以解决静态代理中的问题。另外,动态代理的目标对象需要实现某一接口,这样动态代理才可以代理该对象。