JDK 和 CGLib 实现动态代理和区别

在日常的开发中,Spring AOP 是一个非常常用的功能。谈到 AOP,自然离不开动态代理。

那么,基于 JDK 和 CGLib 如何实现动态代理,他们之间的区别和适用场景是什么呢?接下来,我们一起来探讨一下这个问题。

JDK 如何实现动态代理?

话不多说,我们直接对照着代码来查看。

代码示例

Hello 接口

public interface HelloInterface {

    /**
* 代理的目标方法
*/
void sayHello(); /**
* 未被代理处理的方法
*/
void noProxyMethod();
}

Hello 实现类

public class HelloImpl implements HelloInterface {

    @Override
public void sayHello() {
System.out.println("proxyMethod:sayHello");
} @Override
public void noProxyMethod() {
System.out.println("noProxyMethod");
}
}

MyInvocationHandler 实现 InvocationHandler 接口类

public class MyInvocationHandler implements InvocationHandler {

    /**
* 目标对象
*/
private Object target; /**
* 构造方法
*
* @param target
*/
public MyInvocationHandler(Object target) {
this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("sayHello".equals(methodName)) {
// 比方说,mybaitis 中的 PooledConnection 利用 jdk 动态代理重新实现了 close 方法
System.out.println("change method");
return null;
}
System.out.println("invoke method");
Object result = method.invoke(target, args);
return result;
} }

动态代理神奇的地方就是:

  1. 代理对象是在程序运行时产生的,而不是编译期;
  2. 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等。

️注意:从 Object 中继承的方法,JDK Proxy 会把hashCode()、equals()、toString()这三个非接口方法转发给 InvocationHandler,其余的 Object 方法则不会转发。详见 JDK Proxy官方文档

代码测试

public class MyDynamicProxyTest {

    public static void main(String[] args) {
HelloInterface hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 构造代码实例
HelloInterface proxyInstance = (HelloInterface) Proxy.newProxyInstance(
HelloImpl.class.getClassLoader(),
HelloImpl.class.getInterfaces(),
handler);
// 代理调用方法
proxyInstance.sayHello();
proxyInstance.noProxyMethod();
}
}

打印的日志信息如下:

关键要点

结合上面的演示,我们小结一下 JDK 动态代理的实现,包括三个步骤:

  • 1.定义一个接口

    比如上面的 HelloInterface,Jdk 的动态代理是基于接口,这就是代理接口。

  • 2.编写接口实现类

    比如上面的 HelloImpl,这个就是目标对象,也就是被代理的对象类。

  • 3.编写一个实现 InvocationHandler 接口的类,代理类的方法调用会被转发到该类的 invoke() 方法。

    比如上面的 MyInvocationHandler。

CGLib 如何实现动态代理?

代码示例

Hello 类

无需定义和实现接口。

public class Hello {

    public String sayHello(String name) {
System.out.println("Hello," + name);
return "Hello," + name;
} }

CglibMethodInterceptor 实现 MethodInterceptor

/**
* 实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
*/
public class CglibMethodInterceptor implements MethodInterceptor { @Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("intercept param is " + Arrays.toString(args));
System.out.println("before===============" + method);
// 这里可以实现增强的逻辑处理s
Object result = methodProxy.invokeSuper(obj, args);
// 这里可以实现增强的逻辑处理
System.out.println("after===============" + method);
return result;
} }

️注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()equals()toString()等,但是getClass()wait()等方法不会(因为其他方法是 final,无法被代理),CGLIB 无法代理她们。

pom 依赖

	<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>

代码测试

public class CglibTest {

    /**
* 在需要使用 Hello 的时候,通过CGLIB动态代理获取代理对象
*/
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Hello.class);
enhancer.setCallback(new CglibMethodInterceptor());
// 给目标对象创建一个代理对象
Hello hello = (Hello) enhancer.create();
hello.sayHello("Alan");
}

打印的日志如下:

关键要点

结合上面的演示,我们小结一下 CGLIB 动态代理的实现:

  • 1.实现一个MethodInterceptor,方法调用被转发到该类的 intercept() 方法。
  • 2.使用 Enhancer 获取代理对象,并调用对应的方法。

JDK Vs CgLib

Java 1.3 后,提供了动态代理技术,允许我们开发者在运行期创建接口的代理实例,后来这项技术被用到了很多地方(比如 Spring AOP)。

JDK 动态代理主要对应到 java.lang.reflect 包下边的两个类:ProxyInvocationHandler

其中 InvocationHandler 是一个接口,可以通过实现该接口定义横切逻辑。

举个例子,在方法执行前后打印的日志(这里只是为了说明,实际应用一般不会只是简单的打印日志,一般用于日志、安全、事务等场景),并通过「反射机制」调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。

  • JDK 动态代理有一个限制:它只能为接口创建代理实例

    对于没有通过接口定义业务方法的类,如何创建动态代理实例呢?答案就是 CGLib。

  • CGLIB(Code Generation Library))是一个底层基于 ASM 的字节码生成库,它允许我们在「运行时」修改和动态生成字节码。

    CGLIB 通过继承方式实现代理,在子类中采用方法拦截的方式拦截所有父类方法的调用并顺势织入横切逻辑

JDK 和 CGLib 动态代理区别

1. JDK 动态代理实现原理

  • 通过实现 InvocationHandler 接口创建自己的调用处理器
  • 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 创建动态代理
  • 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数传入

JDK 动态代理是面向接口的代理模式,如果被代理目标没有接口则无能为力。

例如,Spring 通过 Java 的反射机制生产被代理接口的新的匿名实现类,重写了 AOP 的增强方法。

2. CGLib 动态代理原理

利用 ASM 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理。

3. 两者对比

  • JDK 动态代理是面向接口的;
  • CGLib 动态代理是通过字节码底层继承代理类来实现,如果被代理类被 final 关键字所修饰,则无法被代理。

4.适用场景

  • 如果被代理的对象是个实现了接口的实现类,那么可以使用 JDK 动态代理。

    例如,Spring 会使用 JDK 动态代理来完成操作(Spirng 默认采用)

  • 如果被代理的对象没有实现接口,只有实现类,那么只能使用 CGLib 实现动态代理(JDK 不支持)。

    例如,被代理对象是没有接口的实现类,Spring 强制使用 CGLib 实现的动态代理。

性能对比

网上有人对于不通版本的 jdk 进行了测试,经过多次试验,测试结果大致如下:

  • 在 JDK 1.6 和 1.7 时,JDK 动态代理的速度要比 CGLib 要慢,但是并没有某些书上写的10倍差距那么夸张。
  • 在 JDK 1.8 时,JDK 动态代理的速度比 CGLib 快很多。

[idea] 很多时候,性能差异不一定是我们选择某种方式的绝对因素,我们更应该去考虑该技术适用的场景

例如,我们应用中绝大多数的性能差异可能主要在集中在磁盘 I/O,网络带宽等因素,动态代理这点性能差异可能只是占了非常小的比例。

Reference


END

JDK 和 CGLib 实现动态代理和区别的更多相关文章

  1. JDK和Cglib实现动态代理实例及优缺点分析

    Spring AOP使用的核心技术是动态代理,说到动态代理就不得不和设计模式中的代理模式联系起来,通过代理模式我们可以对目标类进行功能增强,在某个方法的执行前后增加一些操作,例如计算方法执行效率.打印 ...

  2. JDK和CGLIB生成动态代理类的区别

     关于动态代理和静态代理 当一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁--代理对象. 按照代理对象的创建时期不同,可以分为两种: 静态代 ...

  3. 【4】JDK和CGLIB生成动态代理类的区别

    当一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁--代理对象. 按照代理对象的创建时期不同,可以分为两种: 静态代理:事先写好代理对象类,在 ...

  4. JDK和CGLIB生成动态代理类的区别(转)

     关于动态代理和静态代理 当一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁--代理对象. 按照代理对象的创建时期不同,可以分为两种: 静态代 ...

  5. jdk与cglib的动态代理

    JDK动态代理中包含一个类和一个接口: InvocationHandler接口: public interface InvocationHandler { public Object invoke(O ...

  6. java面试题之spring aop中jdk和cglib哪个动态代理的性能更好?

    在jdk6和jdk7的时候,jdk比cglib要慢: 在jdk8的时候,jdk性能得到提升比cglib要快很多: 结论出自:https://www.cnblogs.com/xuliugen/p/104 ...

  7. 学习CGLIB与JDK动态代理的区别

    动态代理 代理模式是Java中常见的一种模式.代理又分为静态代理和动态代理.静态代理就是显式指定的代理,静态代理的优点是由程序员自行指定代理类并进行编译和运行,缺点是一个代理类只能对一个接口的实现类进 ...

  8. JDK和CGLIB动态代理原理区别

    JDK和CGLIB动态代理原理区别 https://blog.csdn.net/yhl_jxy/article/details/80635012 2018年06月09日 18:34:17 阅读数:65 ...

  9. cglib实现动态代理简单使用

    Boss: package proxy.cglib; public class Boss{ public void findPerson() { System.out.println("我要 ...

随机推荐

  1. 「题解」NWRRC2017 Grand Test

    本文将同步发布于: 洛谷博客: csdn: 博客园: 简书. 题目 题目链接:洛谷 P7025.gym101612G. 题意概述 给你一张有 \(n\) 个点 \(m\) 条边的无向图,无重边无自环, ...

  2. 【NX二次开发】Block UI 列表框

    属性说明 常规         类型 描述     BlockID     String 控件ID     Enable     Logical 是否可操作     Group     Logical ...

  3. Java IO学习笔记七:多路复用从单线程到多线程

    作者:Grey 原文地址:Java IO学习笔记七:多路复用从单线程到多线程 在前面提到的多路复用的服务端代码中, 我们在处理读数据的同时,也处理了写事件: public void readHandl ...

  4. 生成树协议(STP)

    一.交换网络环路的产生 1.广播风暴的形成 2.多帧复制 3.MAC地址表紊乱 二.STP简介 STP-Spanning Tree Protocol(生成树协议) 逻辑上断开环路,防止广播风暴的产生 ...

  5. Oracle冷备

    概念:一致性的备份,也就是在数据库一致性关闭后做的备份,一般用:shutdown immediate方式关闭. 步骤:1.查看三大核心文件所在位置:数据文件,控制文件,日志文件 数据文件:select ...

  6. [Linux]经典面试题 - 系统管理 - 备份策略

    [Linux]经典面试题 - 系统管理 - 备份策略 目录 [Linux]经典面试题 - 系统管理 - 备份策略 一.备份目录 1.1 系统目录 1.2 服务目录 二.备份策略 2.1 完整备份 2. ...

  7. 《吃透微服务》 - 服务容错之Sentinel

    大家好,我是小菜. 一个希望能够成为 吹着牛X谈架构 的男人!如果你也想成为我想成为的人,不然点个关注做个伴,让小菜不再孤单! 本文主要介绍 SpringCloud中Sentinel 如有需要,可以参 ...

  8. 在C++中,你真的会用new吗?

    摘要:"new"是C++的一个关键字,同时也是操作符.关于new的话题非常多,因为它确实比较复杂,也非常神秘. 本文分享自华为云社区<如何编写高效.优雅.可信代码系列(2)- ...

  9. IP地址与子网的划分

    一.IP地址 1.IP地址的定义 (1).IP地址有32位二进制数组成,一般用点分十进制来表示 (2).IP地址由两部分组成 网络部分(NETWORK) 主机部分(HOST) 2.IP地址的分类 IP ...

  10. unity调用安卓方法实现安装apk文件(androidx)

    原文链接:点击打开 unity想要实现安装apk文件需要与安卓通讯,所以需要自己来实现安卓代码. 第一步先要新建一个安卓项目提供给unity来使用,我这里使用的工具是android studio4.1 ...