1.代理模式的本质:
代理模式是在访问实际对象时引入一定程度的间接性,因为这种间接性可以添加多种用途。
2.代理模式的用途:
代理与委托有相同的界面。代理主要负责委托预处理信息、过滤信息、将信息转发给委托和事后处理信息。
3.代理模式的特点
代理类和委托类之间通常有关联,一个代理类对象与一个委托类对象有关,代理对象本身并没有真正实现服务,而是通过调用委托对象的相关方法来提供特定的服务。
4.静态代理
程序员创建或自动生成特定工具的源代码界面、代理、代理等已在编译过程中确定。在程序运行之前,代理.class文件已经生成。
5.动态代理
程序运行中创建的代理模式。动态代理的优点是,在不修改每个代理类别的方法的情况下,可以方便地统一处理代理函数。
6.动态代理的实现方式
JDK动态代理:
基于java的核心组件:.lang.Proxy类和InvocationHandler接口在reflect包下提供,生成动态代理类和动态代理对象。
核心实现过程:
1.代理类的生成
通过Proxy创建代理对象
主要有三个参数:代理加载器、代理接口数组、代理相关InvocationHandler实现对象。请注意,这里必须是一个对象。
Proxy.newProxyInstance(代理加载器, 代理类实现接口,代理类InvocationHandler实现类对象 );
2.实现代理方法
创建InvocationHandler实现类,持有代理对象的实例target。InvocationHandler中有一种invoke方法,所有执行代理对象的方法都将被执行invoke方法所取代。在invoke方法中执行代理对象target的相应方法。在代理过程中,我们在真正执行代理对象的方法之前加入了自己的其他处理。
public Object invoke(Object proxy, Method method, Object[] args)
3.代理产生的原理分析
使用Proxynewproxyinstance方法创建了一个动态代理对象,查看该方法的源代码,发现它只包装了创建动态代理的步骤:
final Class<?>[] intfs = interfaces.clone();Class<?> cl = getproxyclass(loader, intfs);final Constructor<?> cons = cl.getConstructor(constructorParams);return cons.newInstance(new Object[]{h});
最重要的是要注意 Class<?> cl = getproxyclass(loader, intfs);这句话在这里生成了代理类,代码后面的构造器也是通过这里生成的类获得的。可以看出,这类文件的生成是整个动态代理的关键。因为是动态生成的文件,只需要知道这类文件缓存在java虚拟机中。我们可以通过以下方法将其打印到文件中,看看真相:
byte[] classFile = ProxyGenerator.generateProxyClass($Proxy0”,Student.class.getInterfaces());String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";try(FileOutputStream fos = new FileOutputStream(path)) { fos.write(classFile); fos.flush(); System.out.println(成功写入代理class文件);} catch (Exception e) { System.out.println(“文件错误”);}
反编译这个class文件
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import proxy.Person; public final class $Proxy0 extends Proxy implements Person { private static Method m1; private static Method m2; private static Method m3; private static Method m0; /** *请注意,这里是生成代理类的结构方法,方法参数为InvocationHandler类型。看到这个,你有点清楚吗? *为什么代理对象的调用方法是执行InvocationHandler中的invoke方法,而InvocationHandler持有另一个? *被代理对象的例子不禁想,是不是?..? 是的,就是你想的。 是的,就是你想的。 * *super(paramInvocationHandler),调用父类Proxy的结构方法。 *父类持有:protected InvocationHandler h; *Proxy结构方法: * protected Proxy(InvocationHandler h) { * Objects.requireNonNull(h); * this.h = h; * } * */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } //这个静态块最初是在最后,我把它拿到前面,方便描述 static { try { ///看看这里的静态块里有什么,是否找到了givemoney的方法。请记住givemoney通过反射获得的名字m3,其他人不在乎 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } /** * *这里调用代理对象的giveMoney方法,InvocationHandler中的invoke方法直接调用,m3传输。 *this.h.invoke(this, m3, null);这里简单明了。 *this.h.invoke(this, m3, null);这里简单明了。 *再想想,代理对象持有InvocationHandler对象,InvocationHandler对象持有代理对象, *再联系InvacationHandler中的invoke方法。嗯,就是这样。 */ public final void giveMoney() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } //注意,为了节省空间,这里省去了tostring,hashCode、equals方法的内容。原理和giveMoney方法一样。原理和giveMoney方法一样。}
通过对上述源码的分析,得出的关键结论如下:
proxy代理的核心结构是中介InvocationHandle的结构,以及当方法被调用时映射到InvocationHandler的invoke方法
Invocationhandler被视为中介类,中介类持有被代理对象,并在invoke方法中调用被代理对象的相应方法。通过聚合持有被代理对象的引用,最终将外部对invoke的调用转化为被代理对象的调用。
代理调用自己的方法时,通过自己持有的中介对象调用中介对象的invoke方法,从而实现代理执行被代理对象的方法。也就是说,动态代理通过中介实现了具体的代理功能。
7.JDK动态代理和CGlib的特点
总结JDK和CGLIB动态代理
JDK动态代理只能生成实现接口的代理,而不是针对类别 ,使用的是 实现Java反射技术,生成过程相对高效。
CGLIB是针对类实现代理,主要是为指定类生成子类,覆盖其中的方法 ,实现asm字节码框架,相关执行过程更有效,生成过程可以通过缓存来弥补,因为它是继承,所以最好不要声明这种或方法是final
JDK代理不需要第三方库的支持,只需要JDK环境就可以代理。使用条件:实现InvocationHandler + 使用Proxy.newproxyinstance生成代理对象 + 代理对象必须实现接口
CGLib必须依靠CGLib的类库,但它需要类来实现任何接口代理。指定类生成子类,覆盖其中的方法是在接口编程环境下继承但推荐JDK的代理;