关于代理:Java基础动态代理

4次阅读

共计 11201 个字符,预计需要花费 29 分钟才能阅读完成。

前言

Mybatis的源码实现中,应用到了动静代理的设计思维,为了搞明确 Mybatis 中的动静代理,本篇文章会联合实例和源码对 JDK 动静代理 进行学习,并会在最初总结 JDK 动静代理与 CGLIB 动静代理的区别,以帮忙更好的了解动静代理,为 Mybatis 源码的学习打下基础。

注释

一. 代理模式

在学习动静代理之前,先回顾一下设计模式中的 代理模式。代理模式定义为,给被代理对象提供一个代理对象以管制对被代理对象的拜访,即拜访对象不适宜或者不能间接援用被代理对象时,代理对象作为拜访对象和被代理对象之间的中介。代理模式中,有三种角色,别离为形象主题(AbstractSubject),实在主题(RealSubject )和代理(Proxy),三种角色含意如下表所示。

角色 含意
形象主题(AbstractSubject 通过接口或者抽象类申明实在主题和代理须要实现的业务办法。
实在主题(RealSubject 实现了形象主题中的业务办法,是代理所代表的实在对象,是最终要援用的对象。
代理(Proxy 实现了形象主题,提供了与实在主题雷同的办法,其外部含有对实在主题的援用,能够拜访,管制或扩大实在主题的性能。

代理模式的三种角色的关系用类图示意如下。

二. 动态代理

依据代理模式中的代理类的字节码文件的创立机会,能够将代理分为 动态代理 动静代理 :动态代理在程序运行前,代理类的字节码文件就曾经存在,而动静代理则是在程序运行期间JVM 通过反射机制为代理类生成字节码文件。本大节以一个例子对动态代理进行学习。

定义形象主题,如下所示。

public interface TestServiceA {void executeTestA();
    void submitTestA();}

public interface TestServiceB {void executeTestB();
    void submitTestB();}

上述定义了两个接口作为形象主题,上面定义一个实在主题来实现形象主题,如下所示。

public class RealObject implements TestServiceA, TestServiceB {

    @Override
    public void executeTestA() {System.out.println("Test A execute.");
    }

    @Override
    public void submitTestA() {System.out.println("Test A submit.");
    }

    @Override
    public void executeTestB() {System.out.println("Test B execute.");
    }

    @Override
    public void submitTestB() {System.out.println("Test B submit.");
    }

}

再定义一个代理类,如下所示。

public class ProxyObject implements TestServiceA, TestServiceB {

    private RealObject realObject;

    public ProxyObject(RealObject realObject) {this.realObject = realObject;}

    @Override
    public void executeTestA() {before();
        realObject.executeTestA();
        after();}

    @Override
    public void submitTestA() {before();
        realObject.submitTestA();
        after();}

    @Override
    public void executeTestB() {before();
        realObject.executeTestB();
        after();}

    @Override
    public void submitTestB() {before();
        realObject.submitTestB();
        after();}

    private void before() {System.out.println("Begin to do.");
    }

    private void after() {System.out.println("Finish to do.");
    }

}

能够看到,实在主题 RealObject 和代理 ProxyObject 均实现了形象主题,同时代理 ProxyObject 还持有实在主题 RealObject 的援用,因而须要通过 ProxyObject 能力拜访到 RealObject,同时ProxyObject 在执行 RealObject 的办法时,还能够执行一些额定的逻辑来扩大 RealObject 的性能。编写一个客户端程序,如下所示。

public class ClientOne {public static void main(String[] args) {RealObject realObject = new RealObject();
        ProxyObject proxyObject = new ProxyObject(realObject);
        proxyObject.executeTestA();
        proxyObject.submitTestA();
        proxyObject.executeTestB();
        proxyObject.submitTestB();}

}

运行后果如下所示。

三. JDK 动静代理

思考一下,在第二大节中的动态代理,在理论应用中,存在什么有余?这里归纳如下。

  • 如果让一个代理类代理多个被代理类,那么会导致代理类变得过大;
  • 如果每个被代理类都对应一个代理类,那么会导致代理类变得过多;
  • 因为被代理类和代理类都须要实现雷同的接口,当接口定义的办法减少或缩小时,被代理类和代理类须要一起批改,不易于代码保护。

上述动态代理存在的问题,能够由动静代理来解决,即在程序运行期间,才决定代理类的生成。上面先依据一个基于 JDK 动静代理的例子来阐明动静代理的应用办法,而后再基于源码剖析 JDK 动静代理的实现机制以及为什么能够动静的生成代理类。

JDK动静代理次要是基于两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler,所有基于JDK 动静代理生成的代理类均会继承于 Proxy,同时代理类会持有InvocationHandler 的援用,而 InvocationHandler 中会持有被代理类的援用,因而能够将 InvocationHandler 了解为代理类与被代理类的中介。首先创立一个类实现 InvocationHandler 接口,如下所示。

public class TestInvocationHandler implements InvocationHandler {

    private Object realObject;

    public TestInvocationHandler(Object realObject) {this.realObject = realObject;}

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();
        Object invokeResult = method.invoke(realObject, args);
        after();
        return invokeResult;
    }

    private void before() {System.out.println("Begin to do.");
    }

    private void after() {System.out.println("Finish to do.");
    }

}

如上所示,TestInvocationHandler实现了 InvocationHandler 接口,同时 TestInvocationHandler 中有一个名为 realObject 的成员变量,该变量就是被代理类,当代理类执行代理办法时,就会通过 TestInvocationHandler 来调用被代理类的办法,同时 TestInvocationHandler 中还能够本人定义一些办法来实现性能扩大,在下面例子中就定义了 before()after()两个办法,别离用于在被代理类办法执行前和执行后做一些事件。

创立好了 TestInvocationHandler 之后,就能够开始创立动静代理类了,其中被代理类还是沿用第二大节中的RealObject,创立动静代理类的逻辑如下所示。

public class ClientTwo {public static void main(String[] args) {
        // 保留动静代理类的字节码文件
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // 创立被代理类
        RealObject realObject = new RealObject();
        // 获取被代理类的类加载器
        ClassLoader classLoader = realObject.getClass()
                .getClassLoader();
        // 获取被代理类实现的接口的 Class 对象
        Class<?>[] interfaces = realObject.getClass()
                .getInterfaces();
        // 以被代理类作为入参创立 InvocationHandler
        InvocationHandler invocationHandler
                = new TestInvocationHandler(realObject);
        // 通过调用 Proxy 的 newProxyInstance()办法创立动静代理对象
        Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        ((TestServiceA) proxyInstance).executeTestA();
        ((TestServiceA) proxyInstance).submitTestA();
        ((TestServiceB) proxyInstance).executeTestB();
        ((TestServiceB) proxyInstance).submitTestB();}

}

运行上述程序,执行后果如下所示。

工程目录 /com/sun/proxy下查看生成的代理类的字节码文件,反编译如下所示。

public final class $Proxy0 extends Proxy implements TestServiceA, TestServiceB {

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m6;
    private static Method m5;
    private static Method m0;
    private static Method m4;

    public $Proxy0(InvocationHandler var1) throws  {super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);
        }
    }

    public final void executeTestA() throws  {
        try {super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    public final void executeTestB() throws  {
        try {super.h.invoke(this, m6, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    public final void submitTestB() throws  {
        try {super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    public final void submitTestA() throws  {
        try {super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("executeTestA");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m6 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("executeTestB");
            m5 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceB").getMethod("submitTestB");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m4 = Class.forName("cn.sakura.sacrifice.dynamic.TestServiceA").getMethod("submitTestA");
        } catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());
        }
    }

}

能够看到,生成的代理类,继承于 Proxy,同时也实现了被代理类实现的接口,当代理类执行代理办法时,会通过其继承于ProxyInvocationHandler来调用到被代理类的实在办法,至此,JDK动静代理的一个例子就介绍到这里。当初看一下 Proxy.newProxyInstance() 办法做了哪些事件,来搞明确为什么能够动静的生成代理类,办法源码如下所示。

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException {Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    // 生成代理类的 Class 对象
    Class<?> cl = getProxyClass0(loader, intfs);

    try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        // 获取代理类的结构器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);
                    return null;
                }
            });
        }
        // 生成代理对象
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {Throwable t = e.getCause();
        if (t instanceof RuntimeException) {throw (RuntimeException) t;
        } else {throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);
    }
}

getProxyClass0()办法会生成代理类的 Class 对象,生成过的代理类的 Class 对象会缓存在 Proxy 的类变量 proxyClassCache 中,所以 getProxyClass0() 办法会先在 proxyClassCache 中获取代理类 Class 对象,如果获取不到,则会通过 ProxyClassFactory 来生成代理类 Class 对象。

ProxyClassFactoryProxy 的动态外部类,其次要实现两件事件。

  • 生成代理类的字节码文件;
  • 调用 native 办法 defineClass0() 来解析代理类的字节码文件并生成代理类的 Class 对象。

ProxyClassFactory中生成代理类的字节码文件时,是调用的 ProxyGeneratorgenerateProxyClass()办法,并且在生成字节码文件前,会将 ObjecthashCode()equals()toString() 办法以及被代理类实现的接口所定义的办法增加到代理类的办法中。

至此,能够对 JDK 动静代理如何动静生成代理类进行如下的图示演绎。

四. CGLIB 动静代理

JDK 动静代理中,要求被代理类须要实现接口,这一点限度了 JDK 动静代理的应用,当被代理类未实现接口时,想要动静生成代理类,能够应用 CGLIB 动静代理,应用 CGLIB 生成的代理类是被代理类的子类,本大节将联合例子对 CGLIB 的应用进行阐明。

首先创立一个被代理类,如下所示。

public class RealService {public void execute(String flag) {System.out.println("Test" + flag + "execute.");
    }

    public void submit(String flag) {System.out.println("Test" + flag + "submit.");
    }

}

而后创立一个 办法拦截器,办法拦截器须要继承于MethodInterceptor,用于在代理对象执行办法时进行拦挡,如下所示。

public class TestInterceptor implements MethodInterceptor {

    /**
     * @param o 代理对象
     * @param method 被代理对象的办法
     * @param objects 被代理对象的办法参数类型
     * @param methodProxy 代理对象的办法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    private void before() {System.out.println("Begin to do.");
    }

    private void after() {System.out.println("Finish to do.");
    }

}

上述办法拦截器会在每一个代理对象的办法执行时进行拦挡,而后顺次执行 before() 办法,被代理对象的办法和 after() 办法,以达到对被代理对象的办法的加强成果,同时 intercept() 办法的第一个参数是代理对象,所以想要执行被代理对象的办法须要应用invokeSuper()

最初创立客户端程序来测试成果,如下所示。

public class ClientThree {public static void main(String[] args) {Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealService.class);
        enhancer.setCallback(new TestInterceptor());
        Object proxyObject = enhancer.create();

        ((RealService) proxyObject).execute("cglib");
        ((RealService) proxyObject).submit("cglib");
    }

}

运行后果如下所示。

CGLIB动静代理也能保留代理类的字节码文件,只须要做如下设置。

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 保留门路);

当初通过 IDEA 反编译字节码文件之后,能够查看生成的代理类,上面截取一部分来阐明代理办法的调用和加强,如下所示。

public class RealService$$EnhancerByCGLIB$$64276695 extends RealService implements Factory {
    
    ......

    static void CGLIB$STATICHOOK1() {
        ......
        CGLIB$execute$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "execute", "CGLIB$execute$0");
        ......
    }

    ......

    public final void execute(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {var10000.intercept(this, CGLIB$execute$0$Method, new Object[]{var1}, CGLIB$execute$0$Proxy);
        } else {super.execute(var1);
        }
    }
    
    ......

}

查看反编译失去的代理类能够晓得,CGLIB动静代理生成的代理类是被代理类的子类,以及当代理类调用办法时,会通过 MethodInterceptor 来调用被代理类的办法和加强办法。

至此,CGLIB动静代理的例子介绍结束,相较于 JDK 动静代理,CGLIB动静代理是通过字节码解决框架 ASM 来动静生成代理类的字节码文件并加载到 JVM 中,上面是 JDK 动静代理和 CGLIB 动静代理的一个简略比照。

JDK动静代理

  • JDK动静代理中,代理类调用被代理类的办法依赖 InvocationHandler 接口;
  • JDK动静代理要求被代理类须要实现一个或多个接口;
  • JDK动静代理是基于反射来动静生成代理类的字节码文件。

CGLIB动静代理

  • CGLIB动静代理中,代理类调用被代理类的办法依赖 MethodInterceptor 接口;
  • CGLIB动静代理要求被代理类不能为final,但不要求被代理类须要实现接口;
  • CGLIB动静代理无奈为被代理类中的 final 办法进行代理;
  • CGLIB动静代理是基于 ASM 框架来动静生成代理类的字节码文件。

总结

本篇文章对 代理设计模式 动态代理 JDK 动静代理 CGLIB动静代理 进行了探讨。动态代理实现最为简略,在程序编译实现之后,代理类的字节码文件曾经生成,能够间接被 JVM 加载到内存中,应用效率高,省去了动静代理中的生成字节码文件的工夫,然而毛病就是动态代理中通常一个代理类只代理一个被代理类,如果被代理类过多,会导致代理类也过多。动静代理能够解决代理类过多的问题,其中 JDK 动静代理能够在程序运行期间基于反射来动静生成代理类的字节码文件,然而要求被代理类实现接口,这限度了 JDK 动静代理的应用场景,而 CGLIB 动静代理不要求被代理类实现接口,其底层是基于 ASM 框架来动静生成代理类的字节码文件,CGLIB创立的代理类是被代理类的子类,所以 CGLIB 动静代理要求被代理类不能是 final 的。

正文完
 0