(转) 动态代理的底层原理

转自:https://blog.csdn.net/leave417/article/details/79167936

SpringAOP中可以用那些方式实现,区别?

  1. cglib
  2. java Proxy
  3. Aspectj
  4. instrumentation

区别:

cglib和JDK代理 都是动态构建字节码工具的形式实现,会构造一个全新的类;其中JDK动态代理只能针对实现了接口的类生成代理。而cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。

如果目标对象没有实现接口,则默认会采用CGLIB代理;

如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入)。

Aspectj是Java支持的切面编程,原理:在编译期间,将字节码编译到代理对象中.
CGLIB动态生成class,
Aspectj静态的,在程序编译期间,将代理字节加入到目标对象中.(修改代理目标类的字节,织入代理的字节码);性能比CGLIB和JDK代理 好一些.

instrumentation修改目标类的字节码,在类装载时,进行动态拦截去修改.

动态代理:

a. 无论哪种方式实现动态代理,本质其实都是对字节码的修改,区别是从哪里进行切入修改字节码.

1. cglib–IOC beanFactory获取bean时,动态构建字节码,生成这个类

2. JDK代理– 获取或装载bean时,对字节码进行动态构建,装载,实例化

3. Aspectj– 代码编译时,进行织入修改字节码

4. instrumentation– 在类装载时.

总结:

1.动态代理可以由cglib/java Proxy/Aspectj/instrumentation等多种形式实现;
2.动态代理的本质是对class字节码进行动态构建或者修改;
a.修改的工具有ASM(= =比较难用,还是需要知道JVM指令)/javavssist(已经进行封装)
3.多种实现方式的区别在于对字节码切入方式不同.可选方式:
a.java代理/Cglib是基于动态构建接口实现类字节
b.AspectJ是借助eclipse工具在编译时织入代理字节
c.instrumentation是基于javaagent在类装载时,修改class织入代理字节
d.使用自定义classloader在装载时,织入代理字节

代理对象如何代理目标对象?

关键在于invocationHandler接口,由该接口将代理对象,目标对象和代理逻辑连接在一起.下图是执行过程. 

(转) 动态代理的底层原理_第1张图片

java proxy构建新的代理对象的过程: 
1.proxy基于代理接口获取其代理class

a.从缓存接口中获取,若没有则继续下一步 (有缓存说明下一次再去取的时候不会重新生成目标对象)
b.使用proxyGenerator构建代理class字节 
c.调用本地方法装载class字节至当前classLoader (所以才需要传我们的加载器给他,是因为他要用这个加载器来加载)
d.使用class.forName()返回新的class对象(使用反射生成代理对象 )

2.调用代理对象class.newInstance()

(转) 动态代理的底层原理_第2张图片

详细的代理过程

转自:https://www.cnblogs.com/gonjan-blog/p/6685611.html

 JDK动态代理

public interface Person {
    void giveMoney();
}

其它类我不写了,一样的

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

再在invoke方法中执行被代理对象target的相应方法

 

下面是调用代理的动态构建字节码的工具来构建一个Class,并保存为class文件,然后反编译看他生成了一个怎么样的class

  byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
        String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        } catch (Exception e) {
           System.out.println("写文件错误");
        }
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
//这里是实现了我们传进去的接口
public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
  *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
  *被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。
  *
  *super(paramInvocationHandler),是调用父类Proxy的构造方法。
  *父类持有:protected InvocationHandler h;
  *Proxy构造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //这个静态块本来是在最后的,我把它拿到前面来,方便描述
   static
  {
    try
    {
      //看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

//这里就用反射获取到了我们要代理的那个方法的对象
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
 
  /**
  * 
  *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
  *this.h.invoke(this, m3, null);这里简单,明了。
  *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
  *再联系到InvacationHandler中的invoke方法。嗯,就是这样。
  */
  public final void giveMoney()
    throws 
  {
    try
    {
//这里的this.h是上面构造方法中传给父类的InvocationHandler,所以这里invoke看到的3个参数就是我们平时看到的invoke(Object object, Method method, Object[] args) 
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  //注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。

}

jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

五、总结

生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。

 

 

 

 

 

 

 

 

你可能感兴趣的:(JAVA基础)