当前位置: 首页 > 科技观察

动态代理总结,面试你要知道的都在这里,废话不多说!

时间:2023-03-21 14:00:09 科技观察

本文转载自微信公众号“月亮与飞鱼”,作者日常加油站。转载本文请联系月版飞语公众号。代理模式代理模式是一种提供对目标对象的额外访问的设计模式,即通过代理对象访问目标对象,从而在不修改原有目标对象的情况下,提供额外的功能操作,扩展目标对象。功能举例:租房时,有的人会直接通过房东出租,有的人会通过中介出租。这两种情况哪种更方便?当然通过中介更方便。这里的中介相当于代理。用户通过中介完成租房的一系列操作(看房、交押金、租房、清洁)。代理模式可以有效地将具体实现与调用者解耦,通过接口将代码的具体实现完全隐藏在里面。分类:StaticProxy:在编译时已经实现。编译后,代理类就是一个实际的类文件。DynamicProxy:是运行时动态生成的,即编译后没有实际的class文件,而是运行时动态生成的。类字节码,加载到JVM静态代理使用方法创建接口,然后创建代理类实现接口,实现接口中的抽象方法。然后创建一个代理类,让它也实现这个接口。在代理类中持有对代理对象的引用,然后在代理类方法中调用该对象的方法。publicinterfaceUserDao{voidsave();}publicclassUserDaoImplimplementsUserDao{@Overridepublicvoidsave(){System.out.println("Savinguser...");}}publicclassTransactionHandlerimplementsUserDao{//目标代理对象privateUserDaotarget;//构造代理对象时输入目标对象publicTransactionHandler(UserDaotarget){this.target=target;}@Overridepublicvoidsave(){//调用目标方法前的处理System.out.println("Opentransactioncontrol...");//调用目标对象的方法target.save();//调用目标方法后的处理);//创建一个代理对象并使用接口引用它UserDaouserDao=newTransactionHandler(target);//为接口调用userDao.save();}}使用JDK静态代理轻松完成一个类的代理操作.但是JDK静态代理的缺点也暴露出来了:由于代理只能服务一个类,如果需要代理的类很多,那么需要编写大量的代理类,比较麻烦。JDK动态代理使用JDK动态代理的五步:通过实现InvocationHandler接口自定义自己的InvocationHandler;通过Proxy.getProxyClass获取动态代理类;通过反射机制获取代理类的构造函数,方法签名为getConstructor(InvocationHandler.class);通过构造函数获取代理对象,并使用自定义的InvocationHandler实例对象作为参数传递;通过代理对象调用目标方法;publicinterfaceIHello{voidsayHello();}publicclassHelloImplimplementsIHello{@OverridepublicvoidsayHello(){System.out.println("Helloworld!");}importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.方法;publicclassMyInvocationHandlerimplementsInvocationHandler{/**targetobject*/privateObjecttarget;publicMyInvocationHandler(Objecttarget){this.target=target;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowablen{System.out.printl("------Insertpre-notificationcode------------");//执行对应的目标方法Objectrs=method.invoke(target,args);System.out.println("------插入后处理代码------------");returnrs;}}importjava.lang.reflect.Constructor;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.InvocationTargetException;importjava.lang.reflect.Proxy;publicclassMyProxyTest{publicstaticvoidmain(String[]args)throwsNoSuchMethodException,IllegalAccessException{InstantiationException/get/==Invocation=Tar======================第一种============================//1。生成$Proxy0的类文件System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//2.获取动态代理类ClassproxyClazz=Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);//3.获取代理类的构造函数,传入参数类型InvocationHandler.classConstructorconstructor=proxyClazz.getConstructor(InvocationHandler.class);//4.通过构造函数创建一个动态代理对象,将自定义的InvocationHandler实例传入IHelloiHello1=(IHello)constructor.newInstance(newMyInvocationHandler(newHelloImpl()));//5,通过代理对象调用目标方法iHello1.sayHello();//============================第二种===============================/***Proxy类也有一个方便的方法封装了创建动态代理对象的步骤2~4,*其方法签名为:newProxyInstance(ClassLoaderloader,Class[]instance,InvocationHandlerh)*/IHelloiHello2=(IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(),//加载接口类加载器newClass[]{IHello.class},//一组接口newMyInvocationHandler(newHelloImpl()));//自定义InvocationHandleriHello2.sayHello();}}JDK静态代理和JDK动态代理有一些相似之处,比如必须创建代理类,而代理类的区别诸如asinterfacesmustbeimplemented:在静态代理中,我们需要为哪个接口和哪个代理类创建代理类,所以需要代理类在编译前实现与代理类相同的接口,直接在实现的对应方法中调用了代理类的方法;但是动态代理不同,我们不知道为哪个接口和哪个代理类创建代理类,因为它是在运行时创建的。一句话总结JDK静态代理和JDK动态代理的区别:JDK静态代理是通过直接编码创建的,而JDK动态代理使用反射机制在运行时创建代理类。其实在动态代理中,核心就是InvocationHandler。每个代理实例都会有一个关联的调用处理程序(InvocationHandler)。当代理实例被调用时,方法调用将被编码并分配给它的调用处理程序(InvocationHandler)的invoke方法。代理对象实例方法的调用是通过InvocationHandler中的invoke方法完成的,invoke方法根据传入的代理对象、方法名和参数来决定调用代理的哪个方法。CGLIBCGLIB包的底层是使用一个小而快的字节码处理框架ASM对字节码进行转换,生成一个新的CGLIB-likeagent。实现如下:首先实现一个MethodInterceptor,方法调用会被转发到intercept()方法。然后在需要使用的时候,通过CGLIB动态代理获取代理对象。用例publicclassHelloService{publicHelloService(){System.out.println("HelloService构造");}/***该方法不能被子类覆盖,Cglib不能代理最终修改的方法*/finalpublicStringsayOthers(Stringname){System.out.println("HelloService:sayOthers>>"+name);returnnull;}publicvoidsayHello(){System.out.println("HelloService:sayHello");}}importnet.sf.cglib.proxy.MethodInterceptor;importnet.sf.cglib.proxy.MethodProxy;importjava.lang.reflect.Method;/***CustomMethodInterceptor*/publicclassMyMethodInterceptorimplementsMethodInterceptor{/***sub:cglib生成的代理对象*method:代理对象方法*objects:方法入参*methodProxy:代理方法*/@OverridepublicObjectintercept(Objectsub,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{System.out.println("======插入前置通知======");Objectobject=methodProxy.invokeSuper(sub,objects);System.out.println("======插入后面的通知======");returnobject;}}importnet.sf.cglib.core.DebuggingClassWriter;importnet.sf.cglib.proxy.Enhancer;公共课Cclient{publicstaticvoidmain(String[]args){//代理类文件存放在本地磁盘,方便我们反编译查看源码System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\code");//通过CGLIB动态代理获取代理对象的进程enhancerenhancer=newEnhancer();//设置增强器对象的父类enhancer.setSuperclass(HelloService.class);//设置增强器的回调对象enhancer.setCallback(newMyMethodInterceptor());//创建代理对象HelloServiceproxy=(HelloService)enhancer.create();//通过代理对象调用目标方法proxy.sayHello();}}JDK代理要求被代理类必须实现接口,有很强的局限性,而CGLIB动态代理没有这样的强制性。简单的说,CGLIB会让生成的代理类继承代理类,在代理类中加强代理方法(预处理,后处理等)。总结一下CGLIB在代理时做了哪些工作。生成的代理类继承代理类。这里需要注意一点:如果委托类是final修饰的,则不能被继承,即不能被委托;同理,如果委托类中有final修饰的方法,则不能委托该方法。代理类会为委托方法生成两个方法,一个是与委托方法签名相同的方法,会在方法中通过super调用委托方法;另一个是代理类特有的方法。在执行代理对象的方法时,会先判断是否有实现CGLIB$CALLBACK_0;MethodInterceptor接口的,如果存在,则调用MethodInterceptor中的intercept方法。在拦截方法中,除了调用委托方法外,我们还会进行一些增强操作。在SpringAOP中,典型的应用场景是在CGLIB中记录一些敏感方法执行前后的操作日志。方法调用不通过反射,而是直接调用方法:通过FastClass机制对Class对象进行特殊处理,比如会用一个数组保存方法的引用,每次调用方法,一个索引下标用于保持对方法Fastclass机制的引用CGLIB采用FastClass机制实现对拦截方法的调用。FastClass机制是对一个类的一个方法进行索引,通过索引直接调用对应的方法首先根据代理对象Index的getIndex方法获取目标方法,//然后调用代理对象的invoke方法直接调用目标类的方法,避免反射publicstaticvoidmain(String[]args){Testtt=newTest();Test2fc=newTest2();intindex=fc.getIndex("f()V");fc.invoke(index,tt,null);}}classTest{publicvoidf(){System.out.println(“fmethod”);}publicvoidg(){System.out.println(“gmethod”);}}classTest2{publicObjectinvoke(intindex,Objecto,Object[]ol){Testt=(Test)o;switch(index){case1:t.f();returnull;case2:t.g();returnull;}returnnull;}//这个方法索引了Test类中的方法publicintgetIndex(Stringsignature){switch(signature.hashCode()){case3078479:return1;case3108270:return2;}return-1;}}在上面的例子中,Test2是在Test的Fastclass中,在Test2中有两个方法getIndex和invoke。在getIndex方法中为Test的每个方法创建一个索引,并根据入参(方法名+方法描述符)返回对应的索引。invoke根据指定的index调用以ol为入参的对象O的方法。这样就避免了反射调用,提高了效率。对比三种代理方式,实现了代理方式的优缺点。该类需要硬编码接口,在实际应用方法中进行动态代理可能会导致重复编码,浪费存储空间,效率低下,该方法将在invoke方法中进行增强,不需要硬编码接口,而且代码复用率高只能代理实现接口的委托类。底层使用反射机制调用方法CGLIB动态代理代理类将委托类作为自己的父类,为其中的非final委托方法创建两个方法,一个是与委托签名相同的方法方法,会在方法中通过super调用委托方法;另一个是委托类方法所独有的。在代理方法中,会判断是否有实现了MethodInterceptor接口的对象。如果存在,它会调用拦截方法来代理委托方法。类或接口可以在运行时增强,委托类不需要实现接口。最终类和最终方法用于代理。底层将所有方法存储在一个数组中,通过数组索引直接调用方法。问题CGlib比JDK快吗?使用CGLiB实现动态代理,CGLib底层使用ASM字节码生成框架,使用字节码技术生成代理类,比jdk6之前使用Java反射效率更高。唯一需要注意的是CGLib不能代理声明为final的方法,因为CGLib的原理是动态生成代理类的子类。经过jdk6、jdk7、jdk8逐步优化JDK动态代理,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有在大量调用的时候,jdk6和jdk7的效率不如CGLIB代理,但是到了jdk8,jdk代理的效率要高于CGLIB代理。总之,每次jdk版本升级,jdk代理的效率都提高了。这个消息有点不合时宜。Spring如何选择使用JDK还是CGLIB?当Bean实现接口时,Spring会使用JDK的动态代理。当bean不实现接口时,Spring使用CGlib实现。可以强制使用CGlib

最新推荐
猜你喜欢