java的动态代理详解(javassist,cglib)

俗话说:Coder不知动态代理,走在路上没人理!!!
所以本文尝试说明白java代理模式,代理中的静态代理和动态代理,java的动态代理如何写,动态代理的底层原理,spring aop中使用的cglib如何实现,以及底层原理,cglib和jdk的动态代理的区别,javassist的使用方法及底层实现原理

目录结构
1、静态代理
2、动态代理
2.1、jdk的动态代理
2.2、cglib
2.3、javassist

1、静态代理

为了便于理解动态理解,将常见的硬编码代理模式成为静态代理
代理模式的定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。类似于实际生活中的中介,想必都知道,这里不赘述。
代理模式的结构图如下:


代理模式

如图,我们使用JavaProgramer类实现Programer接口,ProxyJavaProgramer类也实现Programer接口,同时调用JavaProgramer类,对其code进行封装和改写。
具体代码如下:

//Programer 接口
public interface Programer {
  void coding();
}
//JavaProgramer 类
public class JavaProgramer implements Programer {

  public void coding() {
    System.out.println("java");
  }
}
//ProxyJavaProgramer 代理类
public class ProxyJavaProgramer implements Programer{

  public void coding() {
    JavaProgramer javaProgramer = new JavaProgramer();
    System.out.println("proxy start...");
    javaProgramer.coding();
    System.out.println("proxy end...");
  }
}
//测试类
public class Main {
  public static void main(String[] args) {
    Programer programer = new ProxyJavaProgramer();
    programer.coding();
  }
}
输出:
proxy start...
java
proxy end...

代理模式的作用:
1、隔离具体实现类,用户直接使用的是代理类,不需要知道具体实现类的实现细节
2、通过代理类对具体实现类进行增强,增加一些自定义功能

2、动态代理

上面讲述了静态代理,静态代理的缺点也是很明显的,1是需要程序员硬编码,2是如果一个接口有10个实现类,使用代理模式则需要编写10个代理模式,类数量剧增。动态代理则更加灵活一些,通过底层的实现,程序员只需要将需要代理的对象传进来,即可实现对应的代理类。将代理类的生成交给程序。

java中动态代理的实现方式有多种,这里我们只介绍三种。
分别是JDK自带动态代理,开源项目cglib和开源项目javassist。

下面分别讲述这三种如何实现动态代理及底层实现的原理

2.1JDK自带动态代理

java的自带动态代理实现的基本思想是:1:显示Invokhandler接口,2:使用Proxy类生成代理类对象,3:利用反射执行代理类的指定方法
直接上代码,一看就明白

//IProgramer接口
public interface IProgramer {
  void coding();
}
//Programer实现类
public class Programer implements IProgramer{
  
  public void coding() {
    System.out.println("coding");
  }
}
//统一的代理类(传入需要代理的类实例,即可)
public class JdkProxy implements InvocationHandler {
  //代理对象
  private Object target;
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("start");
    method.invoke(target,args);
    System.out.println("start");
    return null;
  }
  public JdkProxy(Object target) {
    this.target = target;
  }
}
//测试类
public class JdkProxyMain {
  public static void main(String[] args) {
    Programer programer = new Programer();
    JdkProxy jdkProxy = new JdkProxy(programer);
    IProgramer proxyProgramer =
        (IProgramer) Proxy.newProxyInstance(
            programer.getClass().getClassLoader(),
            programer.getClass().getInterfaces(),
            jdkProxy);
    proxyProgramer.coding();
  }
}
输出结果:
start
coding
start

根据代码,大家肯定很清楚动态代理是怎么回事了,下面先不着急了解底层实现的逻辑。我们紧接着看一下其他两种实现动态代理的方式:cglib和javassist

2.2cglib

cglib是一个开源的项目,使用cglib也可以实现动态代理。具体怎么实现,cglib的实现思路是,1:导入cglib依赖包,2:代理类实现MethodInterceptor,3:使用Enhance类得到动态代理的实例,并执行指定方法。
具体实现直接上代码,一看就懂

//被代理类
public class Programer {
  void codeing(){
    System.out.println("i am coding");
  }
}
//代理类
public class Hacker implements MethodInterceptor {
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
      throws Throwable {
    System.out.println("begin");
    methodProxy.invokeSuper(o,objects);
    System.out.println("end");
    return null;
  }
}
//测试类
public class CglibMain {

  public static void main(String[] args) {
    Programer programer = new Programer();
    Hacker hacker = new Hacker();
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(programer.getClass());
    enhancer.setCallback(hacker);
    Programer programerProxy = (Programer) enhancer.create();
    programerProxy.codeing();
  }
}
输出结果:
begin
i am coding
end

使用cglib的实现动态代理的过程更为简单。那么cglib和JDK动态代理有什么区别呢?区别主要有:

JDK动态代理和CGLIB字节码生成的区别?
1、JDK动态代理只能对实现了接口(注意必须有接口)的类生成代理,而不能针对类
2、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final
3、cglib效率比JDK动态代理要差一点点,具体原因后面分析底层的实现的时候讲解
在Spring aop中使用了JDK动态代理和cglib,使用情况是,如果实现了接口则使用JDK动态代理,如果没有实现接口则使用cglib。

2.3javassist

javassist是一个字节码指令库,利用javassist可以修改.class文件的结构,以实现对类的修改和创建
下面直接上代码,看下javassist是符合修改方法和创建方法的

//被修改的类
public class Programer {
  public void coding(){
    System.out.println("coding");
  }
}
//修改后的类
public class JavassistProgramer {
    /**
   * 使用javassist修改coding方法
   */
  public void updateCoding() throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.get("com.chanyi.ssist.Programer");
    CtMethod updateCoding = cc.getDeclaredMethod("coding");
    updateCoding.insertBefore("System.out.println(\"start\");");
    updateCoding.insertAfter("System.out.println(\"end\");");
    Object proxyClass = cc.toClass().newInstance();
    Method execute = proxyClass.getClass().getMethod("coding");
    execute.invoke(proxyClass);
  }

  /**
   * 使用javassist新增一个方法
   */
  public void newMethod()throws Exception {
    ClassPool pool = ClassPool.getDefault();
    CtClass cc = pool.get("com.chanyi.ssist.Programer");
    CtMethod ctMethod = new CtMethod(CtClass.voidType, "newMethod", new CtClass[]{}, cc);
    ctMethod.setModifiers(Modifier.PUBLIC);
    ctMethod.setBody("{System.out.println(\"i am new method \");}");
    cc.addMethod(ctMethod);
    Object proxyClass = cc.toClass().newInstance();
    Method personFlyMethod = proxyClass.getClass().getMethod("newMethod");
    personFlyMethod.invoke(proxyClass);
  }
}
//测试类
public class JavassistMain {

  public static void main(String[] args) {
    JavassistProgramer javassistProgramer = new JavassistProgramer();
    try {
      javassistProgramer.updateCoding();
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}
输出结果:
start
coding
end

以上介绍了三种动态修改.class字节码,从而达到修改方法实现的方法。实际上修改.class字节码的方法还有很多。下面贴一张网上结构图,一看就懂


结构图

很明显这些方式的底层实现都是利用自己维护的指令类,修改和创建.class文件。
如果有想深入了解的可以学习一下ASM。这里不再赘述

参数资料:
1、https://www.cnblogs.com/rickiyang/p/11336268.html
2、https://blog.csdn.net/chenchaofuck1/article/details/51727605

你可能感兴趣的:(java的动态代理详解(javassist,cglib))