详解JAVA动态代理
文档更新说明
2018年09月24日v1.0初稿
代理在生活中很常见,比如说婚介网站,其实就是找对象的代理;还有社保代理、人事代理;还有找黄牛抢票,其实也是一种代理;而这些代理,在JAVA中也是有对应实现的。
1、为什么要动态代理
动态代理的作用其实就是在不修改原代码的前提下,对已有的方法进行增强。
关键点:
不修改原来已有的代码(满足设计模式的要求)
对已有方法进行增强
2、举个栗子
我们用一个很简单的例子来说明:Hello类,有一个introduction方法。
现在我们的需求就是不修改Hello类的introduction方法,在introduction之前先sayHello,在introduction之后再sayGoodBye
3、实现方式
JAVA中,实现动态代理有两种方式,一种是JDK提供的,一种是第三方库CgLib提供的。特点如下:
JDK动态代理:被代理的目标类需要实现接口
CgLib方式:可以对任意类实现动态代理
3.1、JDK动态代理
JDK动态代理需要实现接口,然后通过对接口方法的增强来实现动态代理
所以要使用JDK动态代理的话,我们首先要创建一个接口,并且被代理的方法要在这个接口里面
3.1.1、创建一个接口
我们创建一个接口如下:
Personal.java
publicinterfacePersonal{ /** *被代理的方法 */ voidintroduction(); }
3.1.2、实现接口
创建接口实现类,并且完成introduction方法
PersonalImpl.java
publicclassPersonalImplimplementsPersonal{ @Override publicvoidintroduction(){ System.out.println("我是程序员!"); } }
3.1.3、创建代理类
JDK代理的关键就是这个代理类了,需要实现InvocationHandler
在代理类中,所有方法的调用都好分发到invoke方法中。我们在invoke方法完成对方法的增强即可
JDKProxyFactory.java
importjava.lang.reflect.InvocationHandler; importjava.lang.reflect.Method; importjava.lang.reflect.Proxy; publicclassJDKProxyFactoryimplementsInvocationHandler{ /** *目标对象 */ privateTtarget; /** *构造函数传入目标对象 * *@paramtarget目标对象 */ publicJDKProxyFactory(Ttarget){ this.target=target; } /** *获取代理对象 * *@return获取代理 */ publicTgetProxy(){ return(T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } @Override publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{ //对方法增强 System.out.println("大家好!"); //调用原方法 Objectresult=method.invoke(target,args); //方法增强 System.out.println("再见!"); returnresult; } }
就这样,JDK动态代理的代码就完成了,接下来写一份测试代码
3.1.4、编写测试代码
为了方便测试,我们编写一个test方法
同时为了查看class文件,还添加了一个generatorClass方法,这个方法可以将动态代理生成的.class输出到文件
ProxyTest.java
importorg.junit.Test; importsun.misc.ProxyGenerator; importjava.io.FileOutputStream; importjava.io.IOException; publicclassProxyTest{ @Test publicvoidtestJdkProxy(){ //生成目标对象 Personalpersonal=newPersonalImpl(); //获取代理对象 JDKProxyFactoryproxyFactory=newJDKProxyFactory<>(personal); Personalproxy=proxyFactory.getProxy(); //将proxy的class字节码输出到文件 generatorClass(proxy); //调用代理对象 proxy.introduction(); } /** *将对象的class字节码输出到文件 * *@paramproxy代理类 */ privatevoidgeneratorClass(Objectproxy){ FileOutputStreamout=null; try{ byte[]generateProxyClass=ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(),newClass[]{proxy.getClass()}); out=newFileOutputStream(proxy.getClass().getSimpleName()+".class"); out.write(generateProxyClass); }catch(Exceptione){ e.printStackTrace(); }finally{ if(out!=null){ try{ out.close(); }catch(IOExceptione){ //TODOAuto-generatedcatchblock } } } } }
3.1.5、查看运行结果
可以看到,运行test方法之后,控制台打印出如下:
大家好!
我是程序员!
再见!
我们在introduction方法前和后都成功增加了功能,让这个程序员的自我介绍瞬间变得更加有礼貌了。
3.1.6、探探动态代理的秘密
动态代理的代码并不多,那么JDK底层是怎么帮我们实现的呢?
在测试的时候我们将动态生成的代理类的class字节码输出到了文件,我们可以反编译看看。
结果有点长,就不全部贴出来了,不过我们可以看到,里面有一个introduction方法如下:
/** *theinvocationhandlerforthisproxyinstance. *@serial */ protectedInvocationHandlerh; protectedProxy(InvocationHandlerh){ Objects.requireNonNull(h); this.h=h; } publicfinalvoidintroduction()throws{ try{ super.h.invoke(this,m3,(Object[])null); }catch(RuntimeException|Errorvar2){ throwvar2; }catch(Throwablevar3){ thrownewUndeclaredThrowableException(var3); } }
原来,生成的代理对象里面,引用了我们的InvocationHandler,然后在将introduction方法里面调用了InvocationHandler的introduction,而InvocationHandler是由我们编写的代理类,在这里我们增加了sayHello和sayGoodBye操作,然后还调用了原对象的introduction方法,就这样完成了动态代理。
3.2、CgLib动态代理
CgLib动态
3.2.1、创建被代理对象
由于CgLib不需要实现接口,所以我们不需要创建接口文件了(当然,你要有接口也没有问题)
直接创建目标类,实现introduction方法
PersonalImpl.java
publicclassPersonalImpl{ publicvoidintroduction(){ System.out.println("我是程序员!"); } }
3.2.2、创建代理类
同样,我们也需要创建代理类,并且在这里实现增强的逻辑,这次我们不是实现InvocationHandler接口了,而是实现CgLib提供的接口MethodInterceptor,都是类似的,MethodInterceptor中,全部方法调用都会交给intercept处理,我们在intercept添加处理逻辑即可。
CgLibProxyFactory.java
importnet.sf.cglib.proxy.Enhancer; importnet.sf.cglib.proxy.MethodInterceptor; importnet.sf.cglib.proxy.MethodProxy; importjava.lang.reflect.Method; publicclassCgLibProxyFactoryimplementsMethodInterceptor{ /** *获取代理对象 * *@paramtClass被代理的目标对象 *@return代理对象 */ publicTgetProxyByCgLib(Class tClass){ //创建增强器 Enhancerenhancer=newEnhancer(); //设置需要增强的类的类对象 enhancer.setSuperclass(tClass); //设置回调函数 enhancer.setCallback(this); //获取增强之后的代理对象 return(T)enhancer.create(); } /** *代理类方法调用回调 * *@paramobj这是代理对象,也就是[目标对象]的子类 *@parammethod[目标对象]的方法 *@paramargs参数 *@paramproxy代理对象的方法 *@return返回结果,返回给调用者 *@throwsThrowable */ @Override publicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{ System.out.println("大家好!"); Objectresult=proxy.invokeSuper(obj,args); System.out.println("再见!"); returnresult; } }
3.2.3、编写测试代码
在刚才的测试方法中,我们添加一个cglib的测试方法:
@Test publicvoidtestCgLibProxy(){ //生成被代理的目标对象 PersonalImplpersonal=newPersonalImpl(); //获取代理类 CgLibProxyFactoryproxyFactory=newCgLibProxyFactory<>(); PersonalImplproxy=proxyFactory.getProxyByCgLib((Class )personal.getClass()); //将proxy的class字节码输出到文件 generatorClass(proxy); //调用代理对象 proxy.introduction(); }
3.2.4、查看运行结果
运行测试用例,可以看到跟JDK的实现一样的效果
大家好!
我是程序员!
再见!
3.2.5、探探动态代理的秘密
跟JDK的测试一样,我们也来看看生成的class文件
publicfinalvoidintroduction()throws{ try{ super.h.invoke(this,m7,(Object[])null); }catch(RuntimeException|Errorvar2){ throwvar2; }catch(Throwablevar3){ thrownewUndeclaredThrowableException(var3); } }
可以发现,与JDK的动态代理并没有区别。
4、如何选择
既然有两种实现方式,那么到底应该怎么选择呢?
就两个原则:
目标类有接口实现的,JDK和CgLib都可以选择,你开心就好
目标类没有实现任何接口,那只能用CgLib了
5、后记
其实在第一次看到动态代理的时候,我就想不明白,我们都把目标类new出来了,为什么还要将目标类丢给代理类呢?为什么不直接调用目标类对应的方法呢?
后来才发现,原来我没搞清楚动态代理的使用场景,场景很清晰,就是:
不修改原来已有的代码(满足设计模式的要求)
对已有方法进行增强
关键是增强,代理类里面我们是可以添加很多处理逻辑的,从而实现增强效果。就像黄牛抢票比我们厉害些一样。
以上所述是小编给大家介绍的JAVA动态代理详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!