设计模式之代理模式
前言
在生活中,我们经常遇到这样的角色:房产中介、婚介、经纪人、快递、产品代理商等,这些都是代理模式的实际体现,代理对象在客户端和目标对象其一个中介的作用。为什么出现这种模式呢,其实也是单一职责模式的体现,就像一个人,如果做一个工作就比较容易做好,如果一个人同时做多分工作,那就很难做好,容易出错。这时候就是代理模式大显身手的时候了,举一个代理商的例子,比如某公司刚生产出一批新电脑,需要销售,一般都会找很多代理商来代销售(比如某东,某猫),而不是自己去销售,因为如果找代理商来代销售只需要签订代理合同就可以了,而且可以和很多家代理商合作,这些代理商的代理点分布在全国各地,所以可以很容易的推广这个产品,代理商专注销售,生产厂商专注生产,这样就可以明确分工,合作共赢。
定义
为其他对象提供一种代理,以控制对这个对象的访问,代理模式属于结构型设计模式。
1.静态代理
先来看一下结构图(摘自《大话设计模式》)
第一步:编写需要被代理的类(RealSubject)和代理类(Proxy)的公共接口(Subject)
/** * 定义了RealSubject和Proxy的公共接口,这样,在任何使用RealSubject的地方都可以用Proxy替代 */ public abstract class Subject { public abstract void request(); }
第二步:编写需要被代理的类(RealSubject)
/** * 定义Proxy代理的真正实体 */ public class RealSubject extends Subject { @Override public void request() { System.out.println("真实的请求"); } }
第三步:编写代理类(Proxy),代理对RealSubject的访问
/** *保存一个引用使得代理可以访问实体,并提供一个和RealSubject相同的接口,这样代理就可以替代实体 */ public class Proxy extends Subject { private Subject realSubject; @Override public void request() { if (realSubject == null) { realSubject = new RealSubject(); } realSubject.request(); } }
第四步:编写客户端代码,通过访问代理类访问真实对象
public class Client { public static void main(String[] args) { Proxy proxy = new Proxy(); proxy.request(); } }
第五步:编写增强方法,增强RealSubject的功能
public class Proxy extends Subject { private Subject realSubject; @Override public void request() { if (realSubject == null) { realSubject = new RealSubject(); } before(); realSubject.request(); after(); } private void before() { System.out.println("调用前增强"); } private void after() { System.out.println("调用后增强"); } }
运行结果
如果项目中有多个需要增强的类,就需要对每个类编写代理类,这样就会非常麻烦,而且很多情况下,我们需要为多个类增强同样的功能,比如,日志记录,项目中很多地方都需要记录日志。还有就是如果需要在被代理中添加方法,再对这个方法进行代理的话,就需要同时修改代理类的逻辑,违反开闭原则。那么,就需要用动态代理解决静态代理不够灵活的问题了。
2.动态代理
下面举一个婚介帮忙介绍对象的例子
第一步:编写Person接口,声明找对象的方法
public interface Person { /** * 找对象 */ void findLove(); }
第二步:编写具体的客户类
public class Customer implements Person { @Override public void findLove() { System.out.println("见面、吃饭、看电影..."); } }
第三步:编写婚介类,代理客户找对象
/** * 婚介代理类 */ public class HunJie implements InvocationHandler { private Object target; public Object getInstance(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(args); Object invoke = method.invoke(this.target, args); after(); return invoke; } private void after() { System.out.println("after()"); } private void before(Object[] args) { System.out.println("找到符合条件的单身人士"); System.out.println("安排见面"); }
}
第四步:编写客户端
public class Client { public static void main(String[] args) { Person person = (Person)new HunJie().getInstance(new Customer()); person.findLove(); } }
运行结果
Person是一切客户的父类,当然,婚介也就可以作为代理为所有人找对象了。
3.Java实现动态代理
上面婚介介绍对象的例子已经实现了动态代理,在java中,常用的实现动态代理的方式有2种:jdk动态代理和cglib动态代理。
3.1JDK动态代理
3.1.1
婚介介绍对象的例子使用的是jdk动态代理,jdk动态代理中需要注意3点
第一点:被代理的对象必须首先接口,如果一个对象没有实现任何接口,则这个对象无法被代理;
第二点:代理对象必须实现InvocationHandler 接口,这个接口是jdk中定义的用于实现动态代理的接口;
第三点:通过getInstance()得到的是代理对象,而非实际的被代理的对象,代理对象实现了被代理对象的所有接口,所以需要转换为被代理对象的接口类型。
为了搞清楚jdk动态代理的实现原理,下面我们修改测试类,将代理类person保存为文件,然后使用jad反编译,分析代理类的源代码
public static void main(String[] args) throws Exception{ Object instance = new HunJie().getInstance(new Customer()); Person person = (Person)instance; person.findLove(); byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class}); OutputStream os = new FileOutputStream("E://$Proxy0.class"); os.write(bytes); os.close(); }
为了证明instance对象为代理类,下面看一下调试结果
jad反编译结果:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) import java.lang.reflect.*; public final class $Proxy0 extends Proxy implements Person { public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } public final boolean equals(Object obj)//Object类的equals方法 { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } //Person接口的方法 public final void findLove() { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString()//Object类的toString方法 { try { return (String)super.h.invoke(this, m2, null); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode()//Object类的hashCode方法 { try { return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } private static Method m1; private static Method m3; private static Method m2; private static Method m0; //拿到方法签名,便于反射调用 static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("Person").getMethod("findLove", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch(NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch(ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }
上面的h即为InvocationHandler 接口,所以当调用person.findLove()时,会调用上面代理对象的findLove()方法,然后反射调用InvocationHandler的实现类,也就是HunJie的 invoke()方法,HunJie的 invoke()方法再次反射,调用被代理对象Customer的findLove()方法。
之所以称之为动态代理,是因为动态代理是在运行时动态生成字节码,编译,加载,运行的,过程可以分为以下5个步骤:
1.拿到被代理对象的引用,反射获取它的所有接口;Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this)中的第二个参数
2.JDK Proxy类重新生成一个新的类,此类要实现被代理对象的所有接口;
3.动态生成java代码;
4.编译生成.class;
5.重新加载到JVM中运行。
好了,到这里就彻底搞清楚jdk动态代理的实现原理了。
3.2Cglib(基于ASM)动态代理
第一步:引入依赖,这里引入cglib或spring的依赖都可以
第二步:编写代理类
/** * Cglib婚介代理类 */ public class CglibHunJie implements MethodInterceptor { public Object getInstance(Object target) { Enhancer enhancer = new Enhancer(); enhancer.setCallback(this); //将被代理类设置为父类 enhancer.setSuperclass(target.getClass()); return enhancer.create(); } private void after() { System.out.println("CglibHunJie after()"); } private void before(Object[] args) { System.out.println("CglibHunJie找到符合条件的单身人士"); System.out.println("CglibHunJie安排见面"); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(objects); Object invoke = methodProxy.invokeSuper(o, objects); after(); return invoke; } }
第三步:编写测试类
public class Client { public static void main(String[] args) { Person person = (Person)new CglibHunJie().getInstance(new Customer()); person.findLove(); } }
第四步:运行
3.3CGLib 和 JDK 动态代理对比
1.JDK动态代理实现代理对象的接口,Cglib是继承了代理对象。
2.JDK动态代理只能代理实现了接口的代理对象,Cglib动态代理只能代理代理对象中非final的方法。
3.JDK和Cglib都是运行时生成字节码,JDK是直接写Class字节码,Cglib通过ASM框架写字节码,Cglib实现更复杂,生成代理类效率比JDK低。
4.JDK动态代理通过反射机制调用代理对象的方法,CGLib 是通过 FastClass 机制直接调用方法,CGLib 执行效率更高 。
4.总结
4.1静态代理与动态代理的区别
1.静态代理只能通过手动完成代理操作,如果被代理对象添加新的方法,代理类需要同步修改,违背开闭原则。
2.动态代理通过运行时动态生成字节码的方式,取消了对被代理类扩展的限制,符合开闭原则。
4.2代理模式的优缺点
优点:
1.代理模式将调用方与真正被调用的目标对象隔离,可以起到保护目标对象的作用。
2.一定程度上降低了系统的耦合性,提高了可扩展性。
3.可以对目标对象增强。
缺点:
1.代理模式会造成系统设计中类的数量增加。
2.在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
3.增加了系统的复杂度 。