@Catyee
2021-07-23T02:21:35.000000Z
字数 9777
阅读 641
设计模式
代理模式就是为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
所谓静态代理就是在程序运行前就已经存在代理类的字节码文件。
/*** 1、抽象对象接口*/public interface Subject {// 业务A抽象接口void businessA();// 业务B抽象接口void businessB();}/*** 2、被代理类(也是接口实现类,实现具体的业务逻辑)*/public class RealSubject implements Subject {// 业务A的具体实现逻辑@Overridepublic void businessA() {System.out.println("this is a business.");}// 业务B的具体实现逻辑@Overridepublic void businessB() {System.out.println("this is b business");}}/*** 3、代理类 (代理类一般会实现抽象接口,或者继承被代理类)*/public class SubjectProxy implements Subject {private RealSubject realSubject;public SubjectProxy() {this.realSubject = new RealSubject();}// 代理并扩展业务A@Overridepublic void businessA() {// 调用前扩展...try {// 调用业务A方法this.realSubject.businessA();} catch (Exception e) {// 捕获异常扩展...}// 调用后扩展...}// 代理并扩展业务B@Overridepublic void businessB() {// 调用前扩展...try {// 调用业务B方法this.realSubject.businessB();} catch (Exception e) {// 捕获异常扩展...}// 调用后扩展...}}/*** 4、使用类*/public class Client {public static void main(String[] args) {Subject subject = new SubjectProxy();subject.businessA();subject.businessB();}}
以上就是一个完整的静态代理的代码示例,可以看到代理类是由程序员直接编写的,就是一个普通的类,在程序运行之前需要编译成具体的字节码文件。
静态代理模式的重用性并不强,主要体现在两方面,一是如果代理对象过多,程序员得手写1:1比例的代理类,繁琐、重复且难以维护。二是假如增强动作都比较类似,那代理类中不同的代理方法中还会有很多类似的代码。
静态代理的另一个缺点是一旦被代理类增加或者删除一个方法,那每个代理类都要手动修改,增加了维护的难度。
为了克服静态代理的缺点,就用了动态代理,所谓动态代理就是不需要再由程序员手动编写代理类,而是将代理类的生成延迟到JVM运行过程中,在需要用到代理类的时候才会按照代理设置自动生成代理类,然后实例化生成代理对象。
现在最为主流的动态代理是JDK的动态代理和CgLib动态代理,他们在实现上略有差异,JDK动态代理产生的代理类和被代理类实现了相同的接口,而cglib动态代理生成的代理类则是继承了被代理类,也就是说代理类是被代理类的子类。
以下是JDK动态代码的使用示例:
/*** 1、抽象对象接口*/public interface Subject {// 业务A抽象接口void businessA();// 业务B抽象接口void businessB();}/*** 2、被代理类(也是接口实现类,实现具体的业务逻辑)*/public class RealSubject implements Subject {// 业务A的具体实现逻辑@Overridepublic void businessA() {System.out.println("this is a business.");}// 业务B的具体实现逻辑@Overridepublic void businessB() {System.out.println("this is b business");}}/*** 3、拦截器用于功能增强*/public class SubjectInterceptor<T extends Subject> implements InvocationHandler {//目标类private final T target;public SubjectInterceptor(T target) {this.target = target;}// 这里proxy就是代理对象本身,所以千万不要这样调用:method.invoke(proxy, args),会导致无限循环@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("执行前扩展"); // 执行前扩展Object result = method.invoke(this.target, args);System.out.println("执行后扩展"); // 执行后扩展return result;}}/*** 4、生成代理对象,执行业务*/public class Client {public static void main(String[] args) {Subject subject = new RealSubject();SubjectInterceptor<Subject> subjectInterceptor = new SubjectInterceptor<>(subject);Subject subjectProxy = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(), subjectInterceptor);subjectProxy.businessA();subjectProxy.businessB();// 将生成的代理类持久化到文件byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0.class", new Class[] {Subject.class});try (FileOutputStream fos = new FileOutputStream("/tmp/$Proxy0.class")) {fos.write(bytes);;fos.flush();}}}
首先关注InvocationHandler中的invoke方法,这个方法的第一个参数就是代理对象本身,第二个参数是当前调用方法的Method对象,第三个参数是方法的参数,我们要注意不能在代码中这样调用:
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// ...Object result = method.invoke(proxy, args);// ...return result;}
这样调用就会导致方法一直被拦截,然后再调用再拦截,成为死循环。
第56行Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h), 可以看到这个方法有三个参数,第一个参数是被代理对象的classloader,第二个参数是被代理对象实现的接口,第三个参数是拦截器。这里重点关注第二个参数,他是被代理对象所有实现的接口,生成代理对象的时候,代理对象也会实现这些接口,所以JDK的动态代理最终代理类和被代理类都会实现相同的接口,从另一方面来说要想使用JDK动态代理,必须要有至少一个抽象接口,这也是JDK动态代理的使用限制
JDK动态代理采用的是字节重组的方式,重新生成对象来替代原始对象,以达到动态代理的目的。JDK动态代理生成对象的步骤如下:
(1)JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
(2)编译新生成的Java代码.class文件。
(3)重新加载到JVM中运行。
以上过程就叫字节码重组。JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的,代理类就是自动生成的,可以通过打断点的方式进行调试查看。我们还可以将生成的代理类持久化到磁盘中,然后使用反编译软件进行查看(使用idea即可直接查看字节码文件)方法见上面第61及行之后的代码。
将动态生成的代理类反编译之后会发现:代理类继承了Proxy类,同时实现了所有被代理对象实现的那些接口。生成的代理类中有一个静态代码块,代码块中通过反射获取了被代理类的所有方法,代理对象重写了代理类的所有方法,在重写的方法中用反射调用代理对象的方法。
// 反编译代理类的字节码文件的结果public final class class extends Proxy implements Subject {private static Method m1;private static Method m4;private static Method m3;private static Method m2;private static Method m0;// 以拦截器作为构造参数public class(InvocationHandler var1) throws {super(var1);}// 重写代理对象的方法public final void businessA() throws {try {// super.h就是拦截器,这里其实就是调用了拦截器的invoke方法// 在拦截器的invoke方法中会调用增强的代码,以及代理对象的方法super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}// 省略部分代码public final void businessB() throws {...}public final String toString() throws {...}public final int hashCode() throws {...}public final boolean equals(Object var1) throws {...}// 静态代码块:反射获取代理对象的所有方法,并保存引用static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m4 = Class.forName("com.catyee.experiment.design.proxy.Subject").getMethod("businessB");m3 = Class.forName("com.catyee.experiment.design.proxy.Subject").getMethod("businessA");m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}}
以下是CGLib动态代理的使用示例:
/*** 1、被代理类(实现具体的业务逻辑)*/public class RealSubject {// 业务A的具体实现逻辑public void businessA() {System.out.println("this is a business.");}// 业务B的具体实现逻辑public void businessB() {System.out.println("this is b business");}}/*** 2、拦截器用于功能增强*/public class CGSubjectInterceptor implements MethodInterceptor {// 生成代理对象public <T extends Subject> T getProxy(Class<T> clazz) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(clazz);enhancer.setCallback(this);return (T) enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {// 执行前扩展System.out.println("执行前扩展");Object result = methodProxy.invokeSuper(o, objects);// 执行后扩展System.out.println("执行后扩展");return result;}}/*** 3、生成代理对象,执行业务*/public class CGClient {public static void main(String[] args) {// 通过cglib提供的接口将动态生成的代理类的字节码持久化到文件System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/cglib_proxy_class");// 生成代理对象,并执行业务RealSubject subjectProxy = new CGSubjectInterceptor().getProxy(RealSubject.class);subjectProxy.businessA();subjectProxy.businessB();}}
可以看到CGLib是使用Enhancer来生成代理对象的,在生成之前需要调用setSupperclass方法,作用是设置代理类的父类,这个父类就是被代理类。所以CGLib的被代理类不需要实现任何接口,代理类继承被代理类然后重写被代理类中的所有方法,在方法中调用父类的原方法。
实例化Enhancer对象之后需要设置callback,callback从名字来看含义是"回调",但并不是"原方法执行完了再调用callback",而是"调用原方法的时候会调用到callback",callback全面管理了原方法和增强逻辑的执行,示例中设置的callback是MethodInterceptor,也就是方法拦截器,除了MethodInterceptor,CGLIB还定义了NoOp、LazyLoader等多种callback,具体如下:
我们同样将动态生成的代理类的字节码持久化到文件(方法见上面第46行代码),会发现生成了三个字节码文件,通过代理类的源码可以看到,代理类会重写所有从父类继承来的方法,并且还会生成一个对应的代理方法。
// 重写被代理对象的原方法public final void businessA() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; // 拦截器if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {// 调用拦截器的intercept方法// 在拦截器中会调用增强代码,并调用methodProxy.invokeSuper方法// invokeSuper方法会调到上面的CGLIB$businessA$0()方法,该方法内部调到被代理对象的原方法var10000.intercept(this, CGLIB$businessA$0$Method, CGLIB$emptyArgs, CGLIB$businessA$0$Proxy);} else {super.businessA();}}// 生成一个新的与原方法对应的代理方法final void CGLIB$businessA$0() {// 调用被代理对象的原方法super.businessA();}
所以整个调用流程就是subjectProxy.businessA()方法(代理对象重写的方法) -> 拦截器的intercept()方法 -> methodProxy.invokeSuper()方法 -> 与原方法对应的CGLIB$businessA$0()代理方法 -> 被代理对象的businessA()方法(被代理对象的原方法)。
这里比较关键的在于MethodProxy的invokeSuper()方法,看一下源码:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {init();FastClassInfo fci = fastClassInfo;return fci.f2.invoke(fci.i2, obj, args);} catch (InvocationTargetException e) {throw e.getTargetException();}}
可以看到它是先获取FastClassInfo,然后通过FastClassInfo来调用到与原方法对应的代理方法的,这个FastClassInfo是CGLib代理的关键,之前说过CGLIB动态代理生成了三个字节码文件,一个是代理类的字节码文件,另外两个一个是代理类的FastClass,一个是被代理类的FastClass,CGLib代理执行代理方法的效率之所以比JDK的高,是因为CGlib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个FastClass类,这两个类会为代理类和被代理类的方法分配一个index(int类型),这个index当作一个入参,FastClass就可以直接定位要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高。
以下是反编译的RealSubject的FastClass类:
public class RealSubject$$FastClassByCGLIB$$f5f16011 extends FastClass {public RealSubject$$FastClassByCGLIB$$f5f16011(Class var1) {super(var1);}// 获取方法的indexpublic int getIndex(Signature var1) {String var10000 = var1.toString();switch(var10000.hashCode()) {case -673380524:if (var10000.equals("businessA()V")) {return 0;}break;case -673350733:if (var10000.equals("businessB()V")) {return 1;}break;// 省略部分代码...}return -1;}// 省略部分代码...// var1即为方法的index, var2是被代理对象, var3是参数// 通过index直接找到对应的方法,然后执行public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {RealSubject var10000 = (RealSubject)var2;int var10001 = var1;try {switch(var10001) {case 0:var10000.businessA();return null;case 1:var10000.businessB();return null;// 设略部分代码...}} catch (Throwable var4) {throw new InvocationTargetException(var4);}throw new IllegalArgumentException("Cannot find matching method/constructor");}// 省略部分代码...}
(1)JDK动态代理实现了被代理对象的接口(JDK动态代理的被代理对象必须要实现至少一个抽象接口),CGLib代理继承了被代理对象。
(2)JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGlib代理实现更复杂,生成代理类比JDK动态代理效率低。
(3)JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高。