美文网首页修炼之旅Spring基础课堂
Java动态代理和CGLIB动态代理

Java动态代理和CGLIB动态代理

作者: Bre_eze | 来源:发表于2018-08-19 21:44 被阅读593次

    面试中被问到spring aop的实现原理,说了动态代理,面试关接着问动态代理的原理是什么。。。一脸懵逼,自己还是太菜,所以借鉴了一些博客,对照部分源码,对动态代理做一个自己的理解。

    针对spring的源码,说实话,看不懂。。。目前了解的是:

    Spring提供了两种方式来生成代理对象: JdkProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

    所以这里要做的就是对这两哥们进行介绍。

    JDK动态代理

    之所以使用动态代理,是因为在方法的调用中,我们不想让方法的调用者和真正的执行者有过多的接触,并且我们希望调用者调用的方法对原始的方法有着一定的增强。

    本着加强自己印象的作用,这里给出了静态代理的一个示例:

    interface HouseSale{
        void sale();
    }
    
    class HouseOfJack implements HouseSale{
    
        @Override
        public void sale() {
            System.out.println("给我100万,房子归你");
        }
    }
    
    class SaleProxy implements HouseSale{
    
        private HouseOfJack jack = new HouseOfJack();
    
        @Override
        public void sale() {
            System.out.println("现有房源,欢迎选购!");
            jack.sale();
            System.out.println("恭喜这位爷!");
            System.out.println("成功卖出,收取佣金10%,美滋滋!");
        }
    }
    
    public class Market {
        public static void main(String[] args) {
            System.out.println("老子有钱,要买房");
            SaleProxy xiaoZhang = new SaleProxy();
            xiaoZhang.sale();
        }
    }
    

    静态代理时,针对一个卖房主就要新建一个代理类,很不Java,而动态代理可以根据代理的对象动态的自动生成代理类,以下是demo:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    class MyInvocationHandler implements InvocationHandler{
    
        private Object houseHolder;
    
        public MyInvocationHandler(Object object){
            this.houseHolder = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("现有房源,欢迎选购!");
            //jack.sale();
            method.invoke(houseHolder,args);
            System.out.println("恭喜这位爷!");
            System.out.println("成功卖出,收取佣金10%,美滋滋!");
            return null;
        }
    }
    
    public class Market {
        public static void main(String[] args) {
            HouseOfJack houseOfJack = new HouseOfJack();
            System.out.println("老子有钱,要买房");
            MyInvocationHandler saleProxy = new MyInvocationHandler(houseOfJack);
            HouseSale houseSale = (HouseSale) Proxy.newProxyInstance(houseOfJack.getClass().getClassLoader(), houseOfJack.getClass().getInterfaces(), saleProxy);
            houseSale.sale();
        }
    }
    

    JDK动态代理的使用步骤:

    • 定义接口及其实现类;
    • 创建*InvocationHandler类,该类实现InvocationHandler接口,重写invoke(...)方法.
    • 创建需要代理的对象,执行Proxy的newProxyInstance(...)方法
    • 执行接口中定义的方法。

    可以看到,核心类有两个:ProxyInvocationHandler

    Proxy类:
    以下是Proxy类中包含的方法:

    Proxy.png
    newProxyInstance(...)方法如下:
    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);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            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);
            }
        }
    

    参数介绍:

    • loader:被代理对象所使用的类加载器
    • interfaces:被代理类实现的接口,可以为多个
    • h:被重定向时的句柄对象

    其中关键的代码:

            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    

    该方法会根据提供的类加载器和接口,创建一个代理类,创建的过程可总结为三步:

    • 验证
    • 缓存所创建代理类的结构,如果创建过,则直接返回
    • 如果没有创建过则新建代理类

    创建的代码如下:

        long num; 
       //获得代理类数字标识 
       synchronized (nextUniqueNumberLock) { 
         num = nextUniqueNumber++; 
        } 
        //获得创建新类的类名$Proxy,包名为接口包名,但需要注意的是,如果有两个接口而且不在同一个包下,也会报错 
        String proxyName = proxyPkg + proxyClassNamePrefix + num; 
        //调用class处理文件生成类的字节码,根据接口列表创建一个新类,这个类为代理类, 
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces); 
        //通过JNI接口,将Class字节码文件定义一个新类 
         proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
    

    最终生成的代理类通过反编译的结构如下:

    public class $Proxy0 extends Proxy implements HouseSale{ 
        ...
        public $Proxy0(InvocationHandler h){
            super(h);
        }
        ...
        public final void sale(){
            ...
        }
    } 
    

    生成的代理类继承了Proxy类,所以JDK动态代理只能代理接口,不能代理类,(?为什么代理类一定要继承Proxy类,google了半天也没得结果,求有缘人指点)就酱~

    Cglib动态代理

    因为JDK不可以代理类,所以Spring AOP中引入了Cglib动态代理,所以它既可以代理接口,也可以代理类。

    以下是Cglib代理的示例:

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    class HelloConcrete{
        public String sayHello(String str){
            return "HelloConcrete: " + str;
        }
    }
    
    class MyMethodInterceptor implements MethodInterceptor{
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            //System.out.println("class of o : " + o.getClass());
            System.out.println("here is interceptor");
    
            return methodProxy.invokeSuper(o, objects);
        }
    }
    
    public class CGLibProxy {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(HelloConcrete.class);
            enhancer.setCallback(new MyMethodInterceptor());
    
            HelloConcrete helloConcrete = (HelloConcrete) enhancer.create();
            System.out.println(helloConcrete.sayHello(" I love U"));
            System.out.println(helloConcrete.getClass().getName());
            System.out.println(helloConcrete.getClass().getSuperclass().getName());
        }
    }
    

    参数的意义:

    • @para1 o :代理对象本身
    • @para2 method : 被拦截的方法对象
    • @para3 objects:方法调用入参
    • @para4 methodProxy:用于调用被拦截方法的方法代理对象

    同样是两个重要的类(接口):EnhancerMethodInterceptor

    Enhancer类:

    Enhancer.png
    使用的步骤:
    • 创建需要代理的类或接口
    • 创建MethodInterceptor()的子类,并实现interceptor(...)方法。
    • 创建Enhancer对象,设置类信息和Callback信息。

    通过输出的信息可以看到,代理类继承了原始类,通过反编译,代理类的结构如下:

    public class HelloConcrete$$EnhancerByCGLIB$$4d552cc extends HelloConcrete implements Factory {
        ...
        Class localClass1 = Class.forName("net.sf.cglib.test.HelloConcrete$$EnhancerByCGLIB$$4d552cc");
        Class localClass2;
        Method[] tmp60_57 = ReflectUtils.findMethods(new String[] { "sayHello", "()V" }, (localClass2 = Class.forName("net.sf.cglib.test.HelloConcrete")).getDeclaredMethods());
        ...
        final void CGLIB$sayHello$0() {
          super.sayHello();
        }
         
        public final void sayHello(){
           MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
           if (tmp4_1 == null)
           {
               CGLIB$BIND_CALLBACKS(this);
               tmp4_1 = this.CGLIB$CALLBACK_0;
           }
           if (this.CGLIB$CALLBACK_0 != null) {
               tmp4_1.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
           }
           else{
               super.sayHello();
           }
         }
    }
    

    注:代理类名称末尾的序列为hashCode;

    最终在实际方法被调用的时候,代理对象会把执行的函数重定向到interceptor(...)这里,这与JDK的动态代理是一致的,但是JDK动态代理是通过反射来对原始方法进行执行,而Cglib不然;

    因为反射的效率会比较低,Cglib通过FastClass的机制来实现对被拦截方法的调用。

    FastClass机制:
    简单来说就是对类中的方法建立索引,然后通过索引来调用函数,有点类似HashMap。

    以下是样例代码:

    class FTest{
        public void f(){
            System.out.println("f method");
        }
    
        public void g(){
            System.out.println("g method");
        }
    }
    
    class FFTest{
        public Object invoke(int index, Object o, Object[] ol){
            FTest t = (FTest) o;
            switch (index){
                case 1:
                    t.f();
                    return null;
                case 2:
                    t.g();
                    return null;
            }
            return null;
        }
    
        public int getIndex(String signature){
    
            switch (signature.hashCode()){
                case 3078479:
                    return 1;
                case 3108270:
                    return 2;
            }
            return -1;
        }
    }
    
    public class FastTest {
        public static void main(String[] args) {
            FTest test = new FTest();
            FFTest ffTest = new FFTest();
            int indexOff = ffTest.getIndex("f()V");
            ffTest.invoke(indexOff, test, null);
            int indexOfg = ffTest.getIndex("g()V");
            ffTest.invoke(indexOfg, test, null);
    
        }
    }
    

    也即是说,根据自己定义的方法的标识符预先计算好hashCode,然后根据这个hashCode直接调用原始类中的方法。这样就避免了反射机制。

    在Cglib动态代理中,MethodProxy的一个内部类如下:

    private static class FastClassInfo
        {
            FastClass f1; // net.sf.cglib.test.HelloConcrete的fastclass
            FastClass f2; // HelloConcrete$$EnhancerByCGLIB$$4d552cc 的fastclass
            int i1; //方法sayhello在f1中的索引
            int i2; //方法CGLIB$sayHello$0在f2中的索引
        }
    

    真实调用的代码中,invokeSuper(...)的代码如下:

        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    

    至此,两个动态代理的分析就结束了,下面给出了自己理解的面试答案:

    Q:谈谈你对Spring Aop的理解?
    A:AOP,即面向切面编程,是对面向对象编程的一个补充,它可以在不影响源代码的情况下对其进行增强,比如:日志,事务,权限控制等。Spring AOP是基于动态代理实现的,在不同的情景中,有两种动态代理可以选择,即JDK动态代理和Cglib动态代理,Spring Aop的默认策略是,代理接口的时候采用JDK动态代理,其他使用Cglib;JDK动态代理是根据传入的类加载器,接口和handler来构建一个新的代理类,代理类继承Proxy类,并实现传入的接口,在代理对象调用接口方法时,会被转发到handler中,然后通过反射来执行被代理类的方法;Cglib是通过继承被代理类实现的,通过构建字节码来构建代理类,在转发到interceptor方法中时,通过FastClass机制来执行被代理类的方法。

    相关文章

      网友评论

      本文标题:Java动态代理和CGLIB动态代理

      本文链接:https://www.haomeiwen.com/subject/rzfhiftx.html