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. .NET Worker Service 作为 Windows 服务运行及优雅退出改进

    上一篇文章我们了解了如何为 Worker Service 添加 Serilog 日志记录,今天我接着介绍一下如何将 Worker Service 作为 Windows 服务运行. 我曾经在前面一篇文章 ...

  2. 【NX二次开发】镜像对象

    使用uf5946获取镜像矩阵注意:uf5946镜像这个函数,只能用#define UF_plane_type=46这种类型的数据作为镜像面,不能用#define UF_datum_plane_type ...

  3. JAVA微服务应用(1)--SpringBoot中的REST API调用(学习笔记)

    好长时间没有写学习小结了,最近宁正好看了小马哥的微服务系列之<Spring Boot>系列,颇有收获,并且公司也布置一个课题就是关于Spring中的REST API调用.于是乎回归本行,再 ...

  4. MySQL 面试必备:又一神器“锁”,不会的在面试都挂了

    1 什么是锁 1.1 锁的概述 在生活中锁的例子多的不能再多了,从古老的简单的门锁,到密码锁,再到现在的指纹解锁,人脸识别锁,这都是锁的鲜明的例子,所以,我们理解锁应该是非常简单的. 再到MySQL中 ...

  5. 在线博客转PDF电子书 | JS爬虫初探

    最近在看一位大佬写的源码解析博客,平时上下班用手机看不太得劲,但是平板又没有网卡,所以就想搞个离线pdf版,方便通勤时间学习阅读. 所以,问题来了: 怎么把在线网页内容转成pdf? 这位大佬的博客是用 ...

  6. Linux中date的用法

    一.命令格式:date [参数]... [+格式]二.命令功能:date 可以用来显示或设定系统的日期与时间.三.命令格式:%H 小时(以00-23来表示). %I 小时(以01-12来表示). %K ...

  7. 揭开Docker的面纱

    开新坑了,开始挖坑Docker了,兄弟们.为什么需要Docker呢?Docker是什么?这里开始揭开Docker的面纱. 一.为什么需要Docker 可能每个开发人员都有一种困扰,软件开发完之后部署项 ...

  8. 学堂在线《Java程序设计(2021春)》系列笔记——前言

    写在前面 目录 写在前面 这个系列是什么 为什么要做这篇博客 我是谁(其实不重要) 其他 这个系列是什么 这是关于学堂在线<Java程序设计(2021春)>(清华大学-郑莉教授)的个人同步 ...

  9. java:编写jar包加密工具,防止反编译

    懒人方案 网盘: 链接:https://pan.baidu.com/s/1x4OB1IF2HZGgtLhd1Kr_AQ提取码:glx7 网盘内是已生成可用工具,下载可以直接使用,使用前看一下READ. ...

  10. angularjs的一点总结

    一,错误小结 1.出现类似于下面的错误,就是说明 $sessionStorage 这个服务未找到 Error: [$injector:unpr] Unknown provider: $sessionS ...