Java动态代理和CGLIB动态代理

面试中被问到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机制来执行被代理类的方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • https://blog.csdn.net/luanlouis/article/details/24589193 ...
    小陈阿飞阅读 848评论 1 1
  • 动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。 我们首先来谈谈什么是代理...
    Haozz_1994阅读 303评论 0 2
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 12,272评论 6 86
  • 移动页面在设定页面为一屏页面的时安卓手机调取软键盘会出现软键盘遮挡住页面内容的情况。这样的用户体验肯定不好。那怎么...
    yyshang阅读 1,963评论 0 6
  • 每种动物都有他们自己的旅途,这是一只兔子的。 现在开始
    Rabbitking阅读 118评论 0 0