Java 动态代理原理图解 (附:2种实现方式详细对比)
动态代理在 Java 中有着广泛的应用,例如:Spring AOP 面向切面编程,Hibernate 数据查询、以及 RPC Dubbo 远程调用等,都有非常多的实际应用@mikechen
Java 动态代理原理
按照代理的创建时期,代理类可以分为两种:
- 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译,在程序运行前,代理类的 .class 文件就已经存在了。
- 动态代理:在程序运行时,可以运用反射机制动态创建代理类的 .class 文件。
动态代理类与静态代理类最主要的不同点是:代理类的字节码不是在程序运行前生成的,而是在程序运行时再虚拟机中程序自动创建的。
动态代理的实现方式很多。例如:JDK 自身提供的动态代理,就利用了上面提到的反射机制。除了反射,动态代理还可以通过 CGLib 来实现,而 CGLib 是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。
简单来说,动态代理是一种行为方式,而 反射或 ASM 只是它的一种实现手段而已。
本文我主要详解 Java 动态代理的 2 种主流现方式:JDK 原生动态代理与 CGLib 。
JDK 原生动态代理
JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可,整个实现代码如下所示:、
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK Proxy 相关示例
*/
public class ProxyExample {
static interface Car {
void running();
}
static class Bus implements Car {
@Override
public void running() {
System.out.println("The bus is running.");
}
}
static class Taxi implements Car {
@Override
public void running() {
System.out.println("The taxi is running.");
}
}
/**
* JDK Proxy
*/
static class JDKProxy implements InvocationHandler {
private Object target; // 代理对象
// 获取到代理对象
public Object getInstance(Object target) {
this.target = target;
// 取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 执行代理方法
* @param proxy 代理对象
* @param method 代理方法
* @param args 方法的参数
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException {
System.out.println("动态代理之前的业务处理.");
Object result = method.invoke(target, args); // 执行调用方法(此方法执行前后,可以进行相关业务处理)
return result;
}
}
public static void main(String[] args) {
// 执行 JDK Proxy
JDKProxy jdkProxy = new JDKProxy();
Car carInstance = (Car) jdkProxy.getInstance(new Taxi());
carInstance.running();
以上程序的执行结果是:
动态代理之前的业务处理。
The taxi is running.
可以看出, JDK Proxy 实现动态代理的核心是实现 Invocation 接口,我们查看 Invocation 的源码,会发现里面其实只有一个 invoke() 方法,源码如下:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
这是因为在动态代理中有一个重要的角色,也就是代理器,它用于统一管理被代理的对象,显然 InvocationHandler 就是这个代理器。而 invoke() 方法,则是触发代理的执行方法,我们通过实现 Invocation 接口来拥有动态代理的能力。
CGLib 动态代理实现
CGLIB (Code Generation Library) 是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改、和动态生成 CGLIB 通过继承方式实现代理。
在使用 CGLib 之前,我们要先在项目中引入 CGLib 框架,在 pom.xml 中添加如下配置:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLib 的实现代码:
package com.mikechen.proxydemo;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLibExample {
static class Car {
public void running() {
System.out.println("The car is running.");
}
}
/**
* CGLib 代理类
*/
static class CGLibProxy implements MethodInterceptor {
private Object target; // 代理对象
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
// 设置父类为实例类
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法调用前业务处理.");
Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
return result;
}
}
// 执行 CGLib 的方法调用
public static void main(String[] args) {
// 创建 CGLib 代理类
CGLibProxy proxy = new CGLibProxy();
// 初始化代理对象
Car car = (Car) proxy.getInstance(new Car());
// 执行方法
car.running();
以上程序的执行结果是:
方法调用前业务处理。
The car is running.
可以看出:
CGLib 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的。
唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类,来实现动态代理的。
因此,被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。
JDK 动态代理与 CGLib 的区别
1. JDK 动态代理具体实现原理
- 通过实现 InvocationHandler 接口,创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface ,来创建动态代理;
- 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入。
2. CGLib 动态代理
CGLib 是一个强大、高性能的 Code 生产类库,可以实现运行期动态扩展 java 类,Spring 在运行期间通过 CGlib 继承要被动态代理的类,重写父类的方法,实现 AOP 面向切面编程。
3. 两者对比
- JDK 动态代理是面向接口的。
- CGLib 动态代理是通过字节码底层继承要代理类来实现(如果被代理类被 final 关键字所修饰,会失败)。
4. 性能对比
- CGLib 所创建的动态代理对象,在实际运行时候的性能要比 JDK 动态代理高不少,有研究表明,大概要高出10倍;
- CGLib 在创建对象的时候所花费的时间,比 JDK 动态代理要多很多,有研究表明,大概要高出8倍。
因此,对于 singleton 的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,更适合采用 CGLib 动态代理,反之,则比较适用 JDK 动态代理。
以上,是关于 Java 动态代理原理、以及动态代理2 种实现方式的解析。
希望有所帮助,谢谢【关注+点赞+转发】支持。
作者简介
陈睿 | mikechen , 10年+大厂架构经验,「mikechen 的互联网架构」系列文章作者,专注于互联网架构技术。
阅读「mikechen 的互联网架构」40W 字技术文章合集
Java并发 | JVM | MySQL | Spring | Redis | 分布式 | 高并发
--- end ---
Java 动态代理原理图解 (附:2种实现方式详细对比)的更多相关文章
- java动态代理原理
我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...
- JAVA 动态代理原理和实现
在 Java 中动态代理和代理都很常见,几乎是所有主流框架都用到过的知识.在面试中也是经常被提到的话题,于是便总结了本文. Java动态代理的基本原理为:被代理对象需要实现某个接口(这是前提),代理对 ...
- Java动态代理原理及其简单应用
概念 代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互.代理的存在对于调用者来说是透明的,调用者看到的只是接口.代理对象则可以封装一些内部的处理逻辑,如访问控制.远程通信.日志.缓存等 ...
- 设计模式学习——JAVA动态代理原理分析
一.JDK动态代理执行过程 上一篇我们讲了JDK动态代理的简单使用,今天我们就来研究一下它的原理. 首先我们回忆下上一篇的代码: public class Main { public static v ...
- 动态代理 原理简析(java. 动态编译,动态代理)
动态代理: 1.动态编译 JavaCompiler.CompilationTask 动态编译想理解自己查API文档 2.反射被代理类 主要使用Method.invoke(Object o,Object ...
- Java 动态代理 两种实现方法
AOP的拦截功能是由java中的动态代理来实现的.说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执 ...
- java动态代理浅析
最近在公司看到了mybatis与spring整合中MapperScannerConfigurer的使用,该类通过反向代理自动生成基于接口的动态代理类. 于是想起了java的动态代理,然后就有了这篇文章 ...
- Java Proxy和CGLIB动态代理原理
动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...
- java动态代理实现与原理详细分析
关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 一.代理模式 代理模式是常用的java设计模式, ...
随机推荐
- 【Go实战基础】GO语言是什么,有哪些优势
一.简介 2007年,为了提高在多核.网络机器(networked machines).大型代码库(codebases)的业务场景下的开发效率,Google 首席软件工程师决定创造一种语言那就是 Go ...
- HCIA-datacom 4.3 实验三:网络地址转换配置实验
实验介绍: 网络地址转换NAT(Network Address Translation)是将IP数据报文头中的IP地址转换为另一个IP地址的过程.作为减缓IP地址枯竭的一种过渡方案,NAT通过地址重用 ...
- Lua环境搭建编译[Lua5.2+VS2010]
这里是我编译的和一个测试Demo:http://download.csdn.net/detail/allh45601/6783653 大家如果看我下边说的不是很清楚,可以下载这个具体工程: 群:103 ...
- idea中无法在@Test 之下使用Scanner
//如何解决idea中无法在 @Test 之下使用Scanner@Testpublic void testInsert(){ Scanner scanner = new Scanner(System. ...
- OpenDrop 这样的应用程序以及与当今流行的替代品的比较
由安全移动网络实验室 OpenDrop 创建的用 Python 编写的开放 Apple AirDrop 实现是一个命令行工具,允许直接通过 Wi-Fi 在设备之间共享文件.它的独特之处在于它与 App ...
- 诺塔斯读写卡QT SDK笔记
卡片操作函数调用 寻卡: Request --> LotusCardRequest 防撞处理: Anticollission --> LotusCardAnticoll 选卡: Selec ...
- 通过Quartz 进行定时任务
小记一下通过Quartz 进行轮询数据库从而进行自动打印的需求. 一:首先通过NuGet引用Quartz,Quartz依赖Common.Logging和Common.Logging.Log4Net12 ...
- KingbaseES XA 分布式事务
在分布式系统中,各个节点(或者事务参与方)之间在物理上相互独立,各节点之间无法确切地知道其它节点中的事务执行情况,所以多节点之间很难保证ACID,尤其是原子性.如果是单节点的事务,由于存在事务机制,可 ...
- torch.sort 和 torch.argsort
定义 torch.sort(input,dim,descending) torch.argsort(input,dim,descending) 用法 torch.sort:对输入数据排序,返回两个值, ...
- STL再回顾(非常见知识点)
目录 为人熟知的pair类型 再谈STL 迭代器的使用 常用的STL容器 顺序容器 vector(向量) 构造方式 拥有的常用的成员函数(java人称方法) string 构造方式 成员函数 dequ ...