在学习代理模式的时候我首先要提出几个问题,
1、什么是代理模式?
举个例子吧:我们生活中的租房问题。假如我们去租个房子,我们大多数情况下是不会知道房主(就是真正租房,一手货源)的,我们是不是都是先去某些租房平台,或者去找当地的中介去询问何时的房子。我们通过九牛二虎之力在中介那里找到了个物美价廉的房子后,你的租金是不是交给了中介,中介还会收取一些额外的推荐费啦,押金啦、手续费等之类的,那么好,这样的一小段,就已经出来了其中两大核心对象了。
房主(把房子交给中介的人):被代理对象
中介(租给你房子的人):代理对象
2、代理模式有哪些作用?
1.可以隐藏目标的的具体实现(还是拿上面租房的例子来说,房主把房子交给了中介,并和中介谈好了价格我7你3啊。然后当我们去租房子的时候,是中介正面把房子租给了我们,而真正背后卖房子的并未出面,这样就隐藏了背后人的信息和提高了背后人的安全)
2.可以在不修改目标类代码的情况下,对其增加新的功能。(上面例子来说:房东把房子交给中介的时候价格可能只有1000,但是房东可以卖到5000,然后卖出去后在把1000给房东,自己收入4000,这样原房东不但收到了应由的钱,中介还收入了更多的额外费用)。
3、代理模式有哪几种?分别都有什么不同?
在我们Java程序中代理模式分为:静态代理和动态代理(动态代理又分为:JDK动态代理和CGLIB动态代理)
至于什么不同,接下来正式我们着重要学习的内容
什么是静态代理呢?
具体实现:
被代理类与代理类共同实现的接口
packagecn.arebirth.staticproxy;/***需要实现的共同接口*因为要保证代理类要不改变被代理类原来功能的基础上增加新的功能*/publicinterfaceRentalHouse{/***出租房子*/voidrent();}被代理类(房东)
packagecn.arebirth.staticproxy;/***房东(目标类、被代理类)*/publicclassHostimplementsRentalHouse{@Overridepublicvoidrent(){System.out.println("我是房东,出租500平米的大房子");}}代理类(中介)
packagecn.arebirth.staticproxy;/***静态代理类(中介、代理类)*注意:需要被代理类实现相同的接口*/publicclassStaticProxyimplementsRentalHouse{privateRentalHouserentalHouse;publicStaticProxy(RentalHouserentalHouse){this.rentalHouse=rentalHouse;}@Overridepublicvoidrent(){System.out.println("我是中介,收你500推荐费");//调用被代理类的租房方法rentalHouse.rent();System.out.println("我是中介,我又想收你1000块钱!");}}测试类
packagecn.arebirth.staticproxy;publicclassTest{publicstaticvoidmain(String[]args){//创建被代理度下行Hosthost=newHost();/***创建代理对象,并把被代理对象传递给代理对象,*因为他们都实现相同的接口,实现了相同的方法,这样的话传递的对象可以是房东1房东2...*/StaticProxyproxy=newStaticProxy(host);proxy.rent();}}输出结果:我是中介,收你500推荐费我是房东,出租500平米的大房子我是中介,我又想收你1000块钱!
试想一下,如果有两个房东,三个,四个,甚至更多个房东的话,我们怎么写?
被代理类
packagecn.arebirth.staticproxy;/***房东1(目标类、被代理类)*/publicclassHost1implementsRentalHouse{@Overridepublicvoidrent(){System.out.println("我是房东1,出租500平米的大房子");}}packagecn.arebirth.staticproxy;/***房东2(目标类、被代理类)*/publicclassHost2implementsRentalHouse{@Overridepublicvoidrent(){System.out.println("我是房东2,出租500平米的大房子");}}
代理类(中介)
packagecn.arebirth.staticproxy;/***静态代理类(中介、代理类)*注意:需要被代理类实现相同的接口*/publicclassStaticProxyimplementsRentalHouse{//什么价位的房子privateintmoneuy;publicAgent(intmoneuy){this.moneuy=moneuy;}@Overridepublicvoidrenting(){//出租房东的租房//中介调用的租房方法仍然是房东的租房方法System.out.println("收取50元推荐费");if(moneuy<=800){//金额小于等于800的时候Host1host=newHost1();host.rent();}else{Host2host=newHost2();host.rent();}System.out.println("收取500元押金费用");}}测试类
publicclassTest{publicstaticvoidmain(String[]args){StaticProxyproxy=newStaticProxy(1000);proxy.renting();}}输出结果:我是房东2,出租500平米的大房子
静态代理的缺点:
我们仔细来观察下,随着我们的被代理对象的增多,也是就是房东越来越多,那我们的被代理类就会越来越冗余,中介的压力也就会越来越大。
常用的动态代理又分为JDK动态代理和CGLIB动态代理
那么两者的使用场景又是什么呢??
如果目标对象实现了接口,就是上面我们举到的例子,房东和中介共同实现的接口类似,这样的话就采用JDK动态代理
如果目标对象没有实现接口,必须采用CGLIB动态代理
要实现的共同接口
packagecn.arebirth.jdkproxy;/***需要实现的共同接口*/publicinterfaceRentalHouse{/***出租房子*/voidrent();}房东(被代理对象)
packagecn.arebirth.jdkproxy;/***房东(目标类、被代理类)*/publicclassHostimplementsRentalHouse{@Overridepublicvoidrent(){System.out.println("我是房东,出租500平米的大房子");}}核心来了!(JDK动态代理实现类)packagecn.arebirth.jdkproxy;
packagecn.arebirth.jdkproxy;publicclassTest{publicstaticvoidmain(String[]args){//创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去JdkProxyjdkProxy=newJdkProxy(newHost());//获得代理类这里一定要写他们共同实现的接口利用java多态的特性,如果直接写真实类型是会报错的RentalHouseproxy=(RentalHouse)jdkProxy.getProxy();proxy.rent();}}输出结果:我是中介,收你500推荐费我是房东,出租500平米的大房子我是中介,我又想收你1000块钱!
我想当你看到这里一定会感到困惑,为什么我们获取到了代理对象后执行的执行代理对象的方法,明明是房东的方法,怎么显示的好像是JDK动态代理类里面的invoke()方法??或许还有其他的困惑,我们将在下边一一讲解如何实现的原理。
既然想知道如何实现的,那么我们就要从底层出发,来看看,底层的Proxy代理到底帮我们生成了一个怎样的代理类。
再开始之前我希望接下来的代码与操作你是跟着我同步进行的,这样子才会更深刻有更好的理解,当然不排除你是个人脑机器模拟器
开始吧!
我们想要看底层如何实现的,那么我们首先就要获得代理类的class文件
下面是我写的一个获取JDKProxy动态代理所生成的代理文件的工具类
packagecn.arebirth.jdkproxy;importsun.misc.ProxyGenerator;importjava.io.File;importjava.io.FileOutputStream;importjava.io.FileWriter;importjava.io.IOException;publicclassProxyUtil{/***@paramproxyName生成文件代理类的名字*@paraminterClass代理类所实现的接口的class数组形式*@parampath写出路径*/publicstaticvoidwriteProxyClassToHardDisk(StringproxyName,Class[]interClass,Stringpath){byte[]bytes=ProxyGenerator.generateProxyClass(proxyName,interClass);FileOutputStreamout=null;try{out=newFileOutputStream(path+File.separator+proxyName+".class");out.write(bytes);out.flush();}catch(IOExceptione){e.printStackTrace();}finally{try{if(out!=null)out.close();}catch(IOExceptione){e.printStackTrace();}}}}然后我们在测试类里面使用它
packagecn.arebirth.jdkproxy;publicclassTest{publicstaticvoidmain(String[]args){//创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去JdkProxyjdkProxy=newJdkProxy(newHost());//获得代理类RentalHouseproxy=(RentalHouse)jdkProxy.getProxy();proxy.rent();//写入JDK动态代理生成的代理类ProxyUtil.writeProxyClassToHardDisk(proxy.getClass().getSimpleName(),proxy.getClass().getInterfaces(),"F:\\Tools\\DevelopmentTools");}}
最后生成出来的代理类是一个class字节码文件,我们需要使用反编译工具来查看,我使用的是Luten(这个工具打开方式特殊,百度自行查找)
super.h.invoke(this,$Proxy0.m3,null);那么super是谁?就是它自动继承的Proxy类
那么h是什么?就是Proxy类的InvocationHandler
那么我们看这个InvocationHandler类是不是感觉有那么一丢丢的眼熟啊,回过头看,这就是我们在写JDK动态代理类的时候实现的那个接口。
!!!!!!!!!!!!!
然后它又调用了invoke(this,$Proxy0.m3,null);
第一个参数:代理类对象
第二个参数:要执行的代理类对象的方Method对象,这个Method对象的值已经由我们的编译器帮我们构建好了,我们只需要第一个次加载这个类的时候他就会自动赋值了(static代码块)
第三个参数:方法里面的参数,当没有参数的时候就是NULL
带着你的疑惑,从头在开始好好敲一遍代码,捋一遍,相信我,你将有更大的收货!!
下面是简略画的思路图,试着用画图来画出你的思维
是不是这个JDK动态代理必须要依赖接口才能实现,如果没有接口的话,那么JDK动态代理也就凉凉了对吧,!
那么好,接下来我们将说一种更加强大的动态代理方式CGLIB,它的实现原理只要我们懂了JDK动态代理,那么下面的就是小儿科啦哈哈~
CGLIB的代理将不再需要接口也可以生成代理类,但是它需要导包!
当然,你也可以选择其他版本的JAR包!
===
开始
packagecn.arebirth.cglibproxy;/***被代理类*/publicclassHost{publicvoidrent(){System.out.println("Host:rentalhouse");}}生成代理对象的cglib类
packagecn.arebirth.cglibproxy;importnet.sf.cglib.proxy.Enhancer;importnet.sf.cglib.proxy.MethodInterceptor;importnet.sf.cglib.proxy.MethodProxy;importjava.lang.reflect.Method;publicclassCglibProxyimplementsMethodInterceptor{/***获取代理类**@return*/publicObjectgetProxy(){Enhancerenhancer=newEnhancer();//设置被代理对象enhancer.setSuperclass(Host.class);//设置回调方法当前对象enhancer.setCallback(this);//创建代理对象Objecto=enhancer.create();returno;}/***@paramo被代理对象*@parammethod被代理对象方法*@paramobjects被代理对象方法中的参数*@parammethodProxy代理类中的方法*@return*@throwsThrowable*/@OverridepublicObjectintercept(Objecto,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{System.out.println("中介收取手续费");Objecto1=methodProxy.invokeSuper(o,objects);System.out.println("中介收取中介费");returno1;}}测试类
packagecn.arebirth.cglibproxy;publicclassTest{publicstaticvoidmain(String[]args){CglibProxycglibProxy=newCglibProxy();Hostproxy=(Host)cglibProxy.getProxy();//强制转换为我们的房主proxy.rent();}}输出结果:中介收取手续费Host:rentalhouse中介收取中介费
我们动态代理的最大好处就是,可以在没有接口的情况下,只有一个类,我们就可以动态的代理,在程序运行的时候动态的为他创建代理类
最后让我们大概的总结下:
代理模式:静态代理动态代理:JDK动态代理CGLIB动态代理
代理模式的三个要素A.抽象的类或接口完成一件怎样的事情B被代理对象事情操作具体内容C代理对象帮助我们完成事情的同时可以增加其他的东西
具体的列子:我们找中介租房子A抽象的类或者接口租房子B被代理对象房东C代理对象中介
代理模式的好处A房东可以安心的做自己的事情(被代理对象可以做自己的事情)B我们有了问题可以直接找中介(被代理对象变得比较安全)C可以增强代码的扩展性
JDK动态代理和CGLIB动态代理的使用场景
我们可以这样记一下,只要被代理类没有实现接口,我们就必须使用CGLIB动态代理