设计模式系列:代理模式
一、问题引入
现在有这样一个场景:假设我们想邀请一位明星,那么一般我们不是直接联系明星,而是联系明星的经纪人,通过经纪人建立和明星的合作。在这里,明星就是一个目标对象,他只要负责活动演出就行了,而其他琐碎的事情就交给他的代理人(经纪人)来解决。这就是代理模式在现实中的一个应用场景。
同样地,假如我们想要租房,我们不可能每个小区每个住户去敲门问业主是否要出租,这样是不可行的,太浪费时间和精力了。我们只是想要租房,为什么要额外做这么多事呢?很简单,我直接通过中介公司来租房,由中介帮我们找房源,帮我们办理租房流程,签合同。我们只负责挑选自己喜欢的房源,然后付钱入住就可以了。这也是代理模式在现实中的一个应用。
二、模式定义
代理(Proxy)是一种设计模式,又叫委托模式,提供了对目标对象另外的访问方式,即通过代理对象去访问目标对象,由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。这样做的好处是:可以在目标对象实现的基础上,增加额外的功能,即扩展目标对象的功能。同时还能起到隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,方法就是代理类和委托类实现相同的接口。
开闭原则,增加功能:代理类除了是委托类的中介之外,还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果进行处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共服务。例如,我们想给项目加入缓存、日志等功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
代理模式可以分为静态代理和动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。动态代理可以分为jdk动态代理和cglib动态代理。
1、静态代理
主题接口,具体类和代理类都需要实现的接口,里面的接口方法是要被代理的方法。如下:
//主题接口 public interface Subject { /** * 接口方法 */ public void request(); }
具体的实现类,是要被代理的类。如下:
//具体实现类 public class Concrete implements Subject { /** * 具体的业务逻辑实现 */ @Override public void request() { //业务处理逻辑 } }
代理类,相当于中介。如下:
//代理类 public class Proxy implements Subject { /** * 要代理的实现类 */ private Subject subject = null; public Proxy(Subject subject) { this.subject = subject; } /** * 实现接口方法 */ @Override public void request() { log.info("其他业务处理"); this.subject.request(); log.info("其他业务处理"); } }
测试类,客户端调用类。如下:
public class Client { public static void main(String[] args) { Subject subject = new ConcreteSubject(); Proxy proxy = new Proxy(subject); proxy.request(); } }
2、JDK动态代理
定义一个和代理类相关联的InvacationHandler,如下:
//定义和代理类相关联的InvacationHandler public class ProxyInvocationHandler<T> implements InvocationHandler { //invocationHandler持有的被代理对象 T target; public ProxyInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("其他业务处理"); Object result = method.invoke(target, args); return result; } }
测试类,客户端调用类。如下:
//调用测试类 public class Client { public static void main(String[] args) { Subject subject = new ConcreteSubject(); //创建一个与代理对象相关联的InvocationHandler InvocationHandler invocationHandler = new ProxyInvocationHandler<Subject>(subject); //创建一个代理对象proxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法 Subject proxy = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, invocationHandler); //执行方法代理方法 proxy.request(); } }
3、cglib动态代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
定义代理类,如下:
public class CglibProxy implements MethodInterceptor { private Object target; public Object getInstance(final Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { log.info("其他业务处理"); Object result = methodProxy.invoke(object, args); return result; } }
定义测试类,如下:
public class CglibProxyTest { public static void main(String[] args){ Subject subject = new ConcreteSubject(); CglibProxy cglibProxy = new CglibProxy(); Subject subjectProxy = (Subject) cglibProxy.getInstance(subject); subjectProxy.request(); } }
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
三、总结
代理解决的问题:当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可。代理模式还可以让我们完成与另一个类之间的关系的统一管理。但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
代理模式使用了编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
参考资料:
1、https://www.cnblogs.com/leeego-123/p/10995975.html
2、https://www.cnblogs.com/daniels/p/8242592.html