代理模式
杀鸡不想用牛刀-用代理
大家好,我是小赵,求职的路虽然难,但最终还是有个着落,我现在进了藏剑山庄任职铸剑师,不过没意思,因为活都是低级的活,批量铸些普通的匕首、短剑之类,一天到晚忙个没完,这藏剑山庄果然是个大厂,订单超级多。
做着做着我就没动力了,没啥技术含量,虽然是计件,但还不如我的打印机业务赚钱来的多,于是我就私底下请一些有空的同事帮我干活,而我就每天打个卡,然后就在家发展我的打印事业。
其实主要原因呢,还是因为我暂时不打算离职,先静观其变,等待机会,毕竟这是个大企业。
现在,是写日记的时候,我要记录一下我请同事干活的过程。
首先,同事给我干活,那就必须要有和我一样的技能,比如调剂、熔炼、浇铸、加工这些铸剑流程,所以铸剑的流程应该抽象出来。而同事给我干活,天知地知,虽然是他干的活,但这些剑的创建者名字必须是我。
思考完毕之后,我画下了类图:
接下来我就根据这个类图来写程序
铸剑流程抽象:
public interface IMakeSword { //调剂 void adjust(); //熔炼 void smelt(); //浇铸 void casting(); //加工 void process(); }
我:
public class User implements IMakeSword { //名字 private String name = ""; public User(String name) { this.name = name; } @Override public void adjust() { System.out.println(this.name + "正在调剂..."); } @Override public void smelt() { System.out.println(this.name + "正在熔炼..."); } @Override public void casting() { System.out.println(this.name + "正在浇铸..."); } @Override public void process() { System.out.println(this.name + "正在加工..."); } }
同事:
public class Colleague implements IMakeSword{ private IMakeSword user; //构造函数把我传进来 public Colleague(IMakeSword user) { this.user = user; } @Override public void adjust() { this.user.adjust(); } @Override public void smelt() { this.user.smelt(); } @Override public void casting() { this.user.casting(); } @Override public void process() { this.user.process(); } }
差不多了,现在我的同事要开始给我干活了:
public static void main(String[] args) { IMakeSword xiaoZhao = new User("小赵"); IMakeSword colleague = new Colleague(xiaoZhao); colleague.adjust(); colleague.smelt(); colleague.casting(); colleague.process(); }
输出:
小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
这个活干的是非常的好,工资我当然会转给同事,但是我的工作量在山庄里还不至于难看。
随手一招代理模式,暂时保着这份工作,赚个五险一金。
严查之下-强制代理
后来,不知道是哪里传出的风声,山庄高层领导好像知道了什么,安排了专案小组暗地里对一些不正之风进行严查严打。
我不怕顶风作案,就怕做的不严实。
如果,专案小组假扮我的同事,那我不就嗝屁了吗?于是,我决定把我和我同事的关系隐藏到内部,不让人看到,也不接受外面传入。
来看看我更改后的程序:
铸剑流程抽象:
public interface IMakeSword { //调剂 void adjust(); //熔炼 void smelt(); //浇铸 void casting(); //加工 void process(); //获取我的代理 IMakeSword getProxy(); }
加一个getProxy方法,获取我的代理,就是我的同事嘛,增加这个方法是为了防止外人假冒。
我:
public class User implements IMakeSword { //名字 private String name = ""; //我的同事 private IMakeSword proxy = null; public User(String name) { this.name = name; } @Override public void adjust() { if(null == this.proxy){ System.out.println("你好,"+this.name+"在休息。"); return; } System.out.println(this.name + "正在调剂..."); } @Override public void smelt() { if(null == this.proxy){ System.out.println("你好,"+this.name+"在休息。"); return; } System.out.println(this.name + "正在熔炼..."); } @Override public void casting() { if(null == this.proxy){ System.out.println("你好,"+this.name+"在休息。"); return; } System.out.println(this.name + "正在浇铸..."); } @Override public void process() { if(null == this.proxy){ System.out.println("你好,"+this.name+"在休息。"); return; } System.out.println(this.name + "正在加工..."); } @Override public IMakeSword getProxy() { //我和同事的小黑屋 this.proxy = new Colleague(this); return this.proxy; } }
在getProxy方法里,我在小黑屋里自己联系我的同事,并且在每个动作执行的时候,先检测我有没有代理,没代理的话千万别乱动。
同事:
public class Colleague implements IMakeSword{ private IMakeSword user; //构造函数把我传进来 public Colleague(IMakeSword user) { this.user = user; } @Override public void adjust() { this.user.adjust(); } @Override public void smelt() { this.user.smelt(); } @Override public void casting() { this.user.casting(); } @Override public void process() { this.user.process(); } @Override public IMakeSword getProxy() { return this; } }
同事类则几乎没变化,getProxy方法也不需要的,返回同事自己就可以了。
来,现在再试一下合作:
public static void main(String[] args) { IMakeSword xiaoZhao = new User("小赵"); IMakeSword colleague = xiaoZhao.getProxy(); colleague.adjust(); colleague.smelt(); colleague.casting(); colleague.process(); }
输出:
小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
这是新的合作方式,谁也不知道我的代理是谁。
如果专案小组来假冒同事呢:
public static void main(String[] args) { IMakeSword xiaoZhao = new User("小赵"); IMakeSword colleague = new Colleague(xiaoZhao); colleague.adjust(); colleague.smelt(); colleague.casting(); colleague.process(); }
输出:
你好,小赵在休息。
你好,小赵在休息。
你好,小赵在休息。
你好,小赵在休息。
这就是强制代理的手法,代理由自己管理,从此以后,我又可以高枕无忧了。
降低合作门槛-动态代理
我没想到,我那个同事不仅接我的活,还同时接别人的活,可别人就没我这么聪明啊,天天对着他new、new、new!结果没过几天就被专案小组给逮着了,直接翻船,同事也被供出来了,被劝退。
随着我同事的牺牲,山庄的严打行动逐渐结束,不久后专案小组解散,紧张的氛围舒缓而开。
但是我得从新找合作人啊,私底下接触了几个有意向的同事,都觉得我的合作方式太过麻烦,必须要有代理类,就是Colleague类,要实现一箩筐方法,而且创建了类就是留下了证据,将来就有被查到的风险。
在我的一阵子研究之后,发现JDK有一个InvocationHandler接口可以用,于是乎,我重新写了一个程序。
铸剑流程抽象:
public interface IMakeSword { //调剂 void adjust(); //熔炼 void smelt(); //浇铸 void casting(); //加工 void process(); }
我:
public class User implements IMakeSword { //名字 private String name = ""; public User(String name) { this.name = name; } @Override public void adjust() { System.out.println(this.name + "正在调剂..."); } @Override public void smelt() { System.out.println(this.name + "正在熔炼..."); } @Override public void casting() { System.out.println(this.name + "正在浇铸..."); } @Override public void process() { System.out.println(this.name + "正在加工..."); } }
动态代理类:
public class ColleagueHandler implements InvocationHandler { Object object = null; //代理的目标 public ColleagueHandler(Object object) { this.object = object; } //调用目标方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(this.object,args); } }
开始干活:
public static void main(String[] args) throws Throwable{ IMakeSword xiaoZhao = new User("小赵"); InvocationHandler handler = new ColleagueHandler(xiaoZhao); handler.invoke(null,xiaoZhao.getClass().getMethod("adjust", null),null); handler.invoke(null,xiaoZhao.getClass().getMethod("smelt", null),null); handler.invoke(null,xiaoZhao.getClass().getMethod("casting", null),null); handler.invoke(null,xiaoZhao.getClass().getMethod("process", null),null); }
输出:
小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
很好,程序实现了,从始至终都没有创建代理类,虽然很多人都会说这是动态代理,但实际上却不是,这只是反射而已。
对于一个优秀的架构师来讲,这简直就是渣渣玩法,一箩筐方法名都写死在外面了,去他大爷的JDK。
在寻求优化的过程中,听说JDK还提供了Proxy类,里面有个newProxyInstance方法用来创建一个对象的代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。
于是乎,我又舔着大逼脸去使用JDK的接口了。
增加一个动态代理类:
public class MyProxy { public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){ T newProxyInstance = (T) Proxy.newProxyInstance(loader,interfaces, h); return newProxyInstance; } }
开始干活:
public static void main(String[] args) throws Throwable{ IMakeSword xiaoZhao = new User("小赵"); ClassLoader cl = xiaoZhao.getClass().getClassLoader(); IMakeSword proxy = MyProxy.newProxyInstance(cl,new Class[] {IMakeSword.class}, new ColleagueHandler(xiaoZhao)); proxy.adjust(); proxy.smelt(); proxy.casting(); proxy.process(); System.out.println(xiaoZhao.equals(proxy)); }
输出:
小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
false
最后一个比较两个对象是否相同,证明了proxy对象并不是我,但实际上却又是我在干活,这才叫动态代理。
通知机制-切面
其实我们细看之下,已经发现了一个情况,就是铸剑相关的类已经和代理解耦了。
第一条线:IMakeSword接口->User类。
第二条线:InvocationHandler接口->ColleagueHandler类->MyProxy类。
动态代理实现代理的职责,业务逻辑负责功能实现。
现在,为了安全起见,我希望做一件事情,就是每次干活的时候,要给我发一个通知,让我知道。
其实非常的简单,我们在创建动态代理的时候发这个通知就行了。
先创建一个通知抽象,因为以后可能会有前置通知、后置通知、各种切入的通知等等,所以通知也是一条独立发展的线。
通知抽象:
public interface IAdvice { void exec(); }
前置通知:
public class BeforeAdvice implements IAdvice { @Override public void exec() { System.out.println("准备要干活了"); } }
动态代理类:
public class MyProxy { public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){ new BeforeAdvice().exec(); T newProxyInstance = (T) Proxy.newProxyInstance(loader,interfaces, h); return newProxyInstance; } }
开始干活:
public static void main(String[] args) throws Throwable{ IMakeSword xiaoZhao = new User("小赵"); ClassLoader cl = xiaoZhao.getClass().getClassLoader(); IMakeSword proxy = MyProxy.newProxyInstance(cl,new Class[] {IMakeSword.class}, new ColleagueHandler(xiaoZhao)); proxy.adjust(); proxy.smelt(); proxy.casting(); proxy.process(); System.out.println(xiaoZhao.equals(proxy)); }
输出:
准备要干活了
小赵正在调剂...
小赵正在熔炼...
小赵正在浇铸...
小赵正在加工...
false
如果有的代理需要通知,有的时候不需要通知呢?很简单,传个参数,写个if条件即可,如果不想传参数呢?一般AOP的做法是使用注解的方式标记切入位置,然后在动态代理类里面对注解进行判断。
获取注解也可以使用反射机制实现,类头上的注解、方法头上的注解、参数上的注解都可以获取,有兴趣的朋友可以自行研究。
最后声明
本故事写到后面有点怪,原因是动态代理和静态代理的使用场景是不同的,用同一个故事延续下来导致需求逻辑上出现了一些bug,在此致歉。