代理,顾名思义,就是代替别人去处理某件事情。Java代理模式也是如此。
- 应用场景
先想下生活中什么地方能用到代理呢?我第一时间想到的是微商,每一个微商都是总公司的代理。再比如说租房者和中介。
我们可以发现它们的一些共性:
- 1.分为两个角色,一个是执行者,另一个是被代理者。执行者是具体去执行的人。
- 2.对于被代理者而言,这件事情一定要做,但是没有时间或没有能力去做,恰好代理者可以帮助完成这个操作,所以去找代理帮忙。
- 3.需要获取到被代理者的资料。
java代理模式也是如此,当 当前对象不满足要求或者资源珍贵,而这个资源本体又不得不使用时,就是使用代理模式最好场景。
java代理模式的实现有两种方式:
- 第一种是实现 InvocationHandler 接口。这种动态代理方式更像是闹钟这种类型的代理:我只负责在到时间把你叫醒(就是说具体的执行还是由被代理者来执行,代理者只负责在你方法执行的前后增加功能)。
/** * 模拟生活中的黄牛卖票场景 * 为什么会存在黄牛呢?因为有这种需求,现在就假设 小王 需要买一张周杰伦的演唱会门票 * 而他自己又买不到票,只好来找黄牛 * * * */ public class Scalper implements InvocationHandler { private Person person; /** * 传入对象,创建动态代理 * * * */ public Object getIntance(Person person){ this.person = person; Class clazz = person.getClass(); //此处返回值就是动态代理创建好的代理对象,可以看作是伪装成小王的票贩子 //注意此处的最后一个参数 this return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } /** * * 这个invoke就表明了这个方法的作用---反射出某个方法。 * 具体的参数一会在讲 * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我可是个老黄牛了,找我买票准没错"); System.out.println("你叫什么来着?"); //这两个分割线让我们更方便看清楚动态代理的过程 System.out.println("-----------------我是分割线-------------------------"); Object obj = method.invoke(this.person, args); System.out.println("-----------------我是分割线-------------------------"); System.out.println("哦 是" + person.getName() + ",请等一会,我去看看还有没有票了"); System.out.println("请拿好你的票,下次还来找我啊"); return obj; } }
/** * 接口出定义了两个方法是因为在动态代理那个类中强转形式是Person,至于位什么非要实现接口稍后会讲清楚 * 的 * */ public interface Person { public String getName(); //public void setTicket(); public Ticket buyTicket(); } /** * * 这个类用来模拟小王买票这个行为的 * * */ public class XiaoWang implements Person { private String name = "小王"; private String ticketType = "周杰伦演唱会门票"; public String getTicketType() { return ticketType; } public String getName() { return name; } @Override public Ticket buyTicket() { System.out.println("我是" + this.getName() + ",我要买张 " + this.getTicketType()); return new ZhouTicket(); } }
到现在为止,动态代理也就基本完成了,来做下测试:
好像使用静态代理(就是单纯的将对象引用传入,然后再调用这个方法前后进行相应的操作)做更方便吧。静态代理确实比动态代理更加方便,但是动态代理强大之处正是因为他的动态,我们可以看到前面的代码中,动态代理生成的对象是object类,众所周知,所有类都可以是object类,也就是说这一个动态代理可以满足所有的需求。联系到我们的spring框架中,可以发现spring中像事务模块、日志模块等aop编程是动态代理的大舞台。为什么spring可以实现面向切面编程,想像 测试图中两个分割线中间的就是我们代码执行的主体,分割线上下就是事务的开启和关闭。是不是我们也写出了一个简单的面向切面编程。那么动态代理究竟是怎么实现的呢?
//generateProxyClass()方法可以获取到这个类的字节码文件 //然后我们把它存到当前目录下,用反编译软件解析这个class文件 byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{jiaXiaoWang.getClass()}); try { FileOutputStream os = new FileOutputStream("$Proxy0.class"); os.write(data); os.close(); } catch (Exception e) { e.printStackTrace(); }
然后我们就会获得下面这个$Proxy0.class文件(已经将代码尽量精简):
public final class $Proxy0 extends Proxy implements Proxy0 { private static Method m7; private static Method m3; private static Method m6; private static Method m4; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final InvocationHandler getInvocationHandler(Object var1) throws IllegalArgumentException { try { return (InvocationHandler)super.h.invoke(this, m7, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String getName() throws { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final Object newProxyInstance(ClassLoader var1, Class[] var2, InvocationHandler var3) throws IllegalArgumentException { try { return (Object)super.h.invoke(this, m6, new Object[]{var1, var2, var3}); } catch (RuntimeException | Error var5) { throw var5; } catch (Throwable var6) { throw new UndeclaredThrowableException(var6); } } public final Ticket buyTicket() throws { try { return (Ticket)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m7 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getInvocationHandler", Class.forName("java.lang.Object")); m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getName"); m6 = Class.forName("com.sun.proxy.$Proxy0").getMethod("newProxyInstance", Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler")); m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("buyTicket"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
我们可以看到这个类是继承了一个名叫Proxy的类,这个类是不是有点眼熟?
敲黑板看这里,我们当时返回这个代理对象的时候就调用了这个Proxy类的newProxyInstance() 方法,当时返回值直接在用户出
XiaoWang xiaoWang = new XiaoWang(); Person jiaXiaoWang = (Person) new Scalper().getIntance(xiaoWang);
强转成Person。好了,当时介绍概念说是两个对象:执行者和被代理者,现在数下我们的参与者1.小王(被代理者)2.(假小王)执行者3.票贩子???有点乱,我们在捋捋。首先被代理者肯定是小王无疑,那这个假小王和票贩子是什么关系呢?切入点就在上面让注意的那个this,这个this就是黄牛。这是不是说明了黄牛和这个假小王之间关系很密切呢?我们现在进入到这个newProxyInstance()方法中,一眼就能发现(源代码我目前的经验实在是找不到)
/* @param h the invocation handler to dispatch method invocations to*/
说明中解释了这个this的意义,就是说黄牛只是来负责提供调用处理程序的,这也是为什么小王只能找黄牛买票的原因,因为假小王不仅有小王的所有资料,还有黄牛的门道(invoke方法),我们可以将黄牛和假小王理解为一个人,或者这个假小王本来也就是黄牛派出来的。那么这个假小王究竟是怎么进行工作的呢?还是要看这个$Proxy0.class文件。首先在文件底部可以看到一个静态代码块,里面用反射的方式将小王(这个是真的)的所有方法全部获取到,然后生成一个和这个方法名相同的方法(主要是便于伪装),方法实际进行的其实是 super.h.invoke()方法。super.h在此处就是指的黄牛,那么这个invoke方法也就是我们一开始写的那个invoke方法。
总结
由此我们可以看出代理模式可以在不改变用户代码的情况下就可以实现对代码的统一控制。aop思想是什么?想想真的是收获匪浅。