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

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