修炼之旅Spring基础课堂

Java动态代理和CGLIB动态代理

2018-08-19  本文已影响593人  Bre_eze

面试中被问到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动态代理的使用步骤:

可以看到,核心类有两个: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);
        }
    }

参数介绍:

其中关键的代码:

        /*
         * 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());
    }
}

参数的意义:

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

Enhancer类:

Enhancer.png
使用的步骤:

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

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

上一篇下一篇

猜你喜欢

热点阅读