今天我们来聊聊JAVA中的代理模式,代理又分为静态代理和动态代理。代理模式其实很常见,我们举个例子来说明,比如在携程买火车票。携程就相当于代理对象,它接受我们的订单需求,然后到铁路部门买好车票后再给我们返回来。一个过程就完成了。
有的人可能会说,为什么要使用代理,为什么不直接跳过这个代理对象,直接执行我们自己的操作呢?还是刚刚的买票问题,我们都知道,12306上买票的话,是需要登录后才能进行购买操作的。而有的时候你的身份信息被人注册了,这个时候就没法登录了。所以继续通过12306的话是没法购买到票的,这个时候是不是得到携程上购买了?这就是代理的作用,有的时候我们并不能直接去访问目标,而需要一个中间人去访问。
引用一段关于JAVA代理描述
代理模式:为其它对象提供一种代理以控制对这个对象的访问。
代理模式的角色:
抽象角色:声明真实对象和代理对象的共同接口
代理角色:代理对象内部包含有真实角色的引用,从而可以操作真实角色,同时代理对象 与真实对象有相同的接口,能在任何时候代替真实对象,同时代理对象可以在执行真实对 象前后加入特定的逻辑以实现功能的扩展。
真实角色:代理角色所代表的真实对象
静态代理
静态代理其实就三个东西。代理接口,目标对象,代理对象。继续刚刚携程买火车票这个,目标对象为我们自己,因为我们要买火车嘛,携程则为代理对象。
IProxy接口:
package proxy;
public interface IProxy {
public void buyTicket();
}
User类(用户类):
package proxy;
public class User implements IProxy{
@Override
public void buyTicket() {
System.out.println("用户购买到了火车票~");
}
}
CtripProxyBuyTicket类(代理类):
package proxy;
public class CtripProxyBuyTicket implements IProxy{
IProxy ip;
public CtripProxyBuyTicket(IProxy ip) {
this.ip = ip;
}
@Override
public void buyTicket() {
System.out.println("携程开始代理购买火车票!");
if(null != ip) ip.buyTicket();
System.out.println("携程已帮助用户购买火车票完成");
}
public void setIp(IProxy ip) {
this.ip = ip;
}
}
最后是客户端调用Client :
package proxy;
public class Client {
public static void main(String[] args) {
IProxy user = new User();
IProxy ctrip = new CtripProxyBuyTicket(user);
ctrip.buyTicket();
}
}
最终运行结果:
看到这里,有人可能会问,这不就是和上次的策略模式差不多嘛,如果再加两个类的话 。其实初看是差不多,但是本质上是有区别的。别急,慢慢往下看。
###动态代理
动态代理和静态代理的最大区别在于实现的接口不同,在静态代理中,代理类和被代理类都要实现同一个接口。每当接口中新增方法时,需要同时修改代理类和被代理类的具体实现。大大的加重了代码的维护难度。而动态代理则不同,被代理类实现自己的接口,而代理类实现的是JDK中的InvocationHandler接口。我们先看看该接口源码
该接口中就一个invoke方法。传入参数介绍一下
Object proxy:被代理类对象
Method method:被代理类方法
Object[] args:传入参数
为了方便大家的理解,这里直接上动态代理的源码,稍后再来分析该源码内容。
User类:
package proxy;
public class User implements IProxy{
@Override
public void buyTicket() {
System.out.println("用户购买到了火车票~");
}
@Override
public void unsubscribeTicket() {
System.out.println("用户退订火车票~");
}
}
IProxy 接口
package proxy;
public interface IProxy {
public void buyTicket();
public void unsubscribeTicket();
}
携程代理类CtripProxyBuyTicket :
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class CtripProxyBuyTicket implements InvocationHandler{
private IProxy ip;
public Object getInstance(IProxy ip) {
this.ip = ip;
Class clazz = ip.getClass();
Object o = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
return o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("携程开始动态代理用户火车票~");
method.invoke(ip, args);
System.out.println("携程已经完成动态代理用户火车票!");
return null;
}
}
客户端执行类Client :
package proxy;
public class Client {
public static void main(String[] args) {
IProxy ip = (IProxy) new CtripProxyBuyTicket().getInstance(new User());
ip.buyTicket();
ip.unsubscribeTicket();
}
}
结果输出:
通过源码我们可以看到,我在我们的自定义接口里面增加了一个新的unsubscribeTicket方法,模拟用户退票。毕竟我们在携程上面也是可以退票和改签的嘛,如果继续用静态代理的话,我们要做的就是除了在user类中实现unsubscribeTicket方法之外,还需要在CtripProxyBuyTicket类中实现unsubscribeTicket方法。而若是用了动态代理的话,就不需要实现了,我们来看看下图,图片上半部分为动态代理的代理类,下部分则为静态代理的代理类
通过比对我们不难发现,静态代理时,代理类与被代理类都要实现同一个接口,而动态代理类则实现的InvocationHandler这个接口就行了,再观察实现方法,动态代理类中主要为getInstance方法与invoke方法,先说说getInstance方法
public Object getInstance(IProxy ip) {
Class clazz = ip.getClass();
Object o = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
return o;
}
该方法为我们自定义的一个方法,返回一个Object类型的对象。这里其实用到了JAVA中的反射,动态的生成了一个代理类。然后将这个代理类返回。有兴趣的可以去看看JAVA中的反射。
这个方法完成后就是invoke方法了,这个方法其实是InvocationHandler这个方法。的反射执行方法。我们在通过代理类调用被代理类的方法时,最终都会委托给这个invoke方法执行。
总结一下:
jdk的代理让我们在不直接访问某些对象的情况下,通过代理机制也可以访问被代理对象的方法,这种技术可以应用在很多地方比如RPC框架,Spring AOP机制,特别是像spring中事务管理一样,我们可以在invoke方法内进行事务控制,当执行完我们的业务代码后,提交事务或回滚事务。或者像处理servlet传输中的字符串编码时,也可以用动态代理完成。