JDK动态代理的深入理解
引入代理模式
代理模式是框架中经常使用的一种模式,动态代理是AOP(面向切面编程)思想的一种重要的实现方式,在我们常用的框架中也经常遇见代理模式的身影,例如在Spring中事务管理就运用了动态代理,它将Service层原先应该进行的事务管理交给了Spring框架,大大简化了开发流程。在Hibernate中对象的懒加载模式,也运用了JDK的动态代理以及cglib代理。
静态代理
在说动态代理之前,我们需要先了解一下静态代理
静态代理通常用于对原有业务逻辑的扩充。比如持有第三方jar包中的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入第三方jar包的方法里。所以可以创建一个代理类实现和这个类方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。
这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。
满足代理模式应用场景的三要素:
1. 两个角色:被代理者、执行者
2. 注重过程:被代理对象不想做,但是必须要做时使用代理模式
3. 代理对象:执行者必须持有被代理对象的引用
这里我们提出一个需求:我们在编写的Service类的每个方法中没有进行事务处理,但根据需要我们必须进行实物处理,那此时我们进行静态代理。
ServiceInterface接口:
public interface ServiceInterface {
void a();
void b();
}
接口的实现类:
public class ServiceImpl implements ServiceInterface{
@Override
public void a() {
System.out.println("执行a方法");
}
@Override
public void b() {
System.out.println("执行b方法");
}
}
代理类:
public class ServiceImplProxy implements ServiceInterface{
private ServiceInterface service;
public ServiceImplProxy(ServiceInterface service) {
super();
this.service = service;
}
@Override
public void a() {
System.out.println("开启事务");
service.a();
System.out.println("提交事务");
}
@Override
public void b() {
System.out.println("开启事务");
service.b();
System.out.println("提交事务");
}
}
测试:
public class TestProxy {
public static void main(String[] args) {
//创建目标对象
ServiceInterface service=new ServiceImpl();
//创建代理对象,将目标对象传入代理对象
ServiceInterface serviceProxy =new ServiceImplProxy(service);
serviceProxy.a();
serviceProxy.b();
}
}

静态代理的优点及缺点
上面演示了静态代理的完整过程,现在我们来看看静态代理的优缺点
优点:扩展原功能,不侵入原代码。
缺点:
1. 如果我们只需要调用代理对象的某一个方法,换句话说我们只需要代理对象代理委托类的某一个方法时,我们仍然需要实现接口中所有的方法,这样显得很浪费。当然在这里我们可以通过继承来实现静态代理,使用继承时,我们只需要覆写需要代理的方法即可。
2. 如果在接口中定义了很多方法,而这些方法都需要被代理,并且代理的逻辑都是相同的,比如说在WEB开发中Service层所有方法执行之前都需要打开事务,结束后关闭事务。那么此时我们使用静态代理,会导致大量代码重复(想想如果有50个方法,你去一个一个手敲吧)。
既然静态代理这么多缺点,那JDK一定会帮我们解决这个问题,现在我们就来看看JDK自带的动态代理。
动态代理
动态代理的原理:
动态代理由程序在内存中动态生成一个对象,不需要我们手写代理对象,我们只需要指定代理方法的模板即可。

JDK自带动态代理的核心类:
java.lang.reflect.Proxy类:
该类中的newInstance的静态方法,用于创建动态代理对象,该类也是所有生成的动态代理对象的父类。
方法:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数:
ClassLoader:传入被代理类的类加载器,实际上只需要传入类加载器即可,不一定必须传入“被代理类”的类加载器,也 可以传入别的实例的类加载器,因为java中类加载器是一个对象。这个类加载器用于将生成的字节码文件加载进方法区, 并生成字节码对象。
Class<?>[] interfaces:这个参数是指定代理对象实现的接口,可以实现多个接口,这些接口中的所有方法都会按照invoke模板 中的代码进行加强。
InvocationHandler h:传入InvocationHandler接口的实现类,该类中的invoke方法是代理对象中所有方法的逻辑处理模板。
java.lang.reflect.InvocationHandler接口:
该接口有一个invoke方法,我们需要实现invoke方法,这个方法就是代理方法的模板。
接口中需要实现的方法:
invoke(Object proxy, Method method, Object[] args)
参数:
Object proxy:代表代理对象本身,可以它调用代理对象的其他方法
Method method:代表“被代理对象”对应方法的字节码对象
Object[] args:传入“代理对象”对应方法的参数数组
动态代理的示例代码:
编写InvocationHandler实现类
public class ServiceInvocationHandler implements InvocationHandler {
private Object instance;//这是目标对象(被代理对象)
public Object getInstance() {
return instance;
}
public void setInstance(Object instance) {
this.instance = instance;
}
/***
* proxy:代表代理对象本身,可以通过它调用代理对象的其他方法
* method:代表目标对象对应方法的字节码对象
* args:代表目标对象相应的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
method.invoke(instance,args);
System.out.println("提交事务");
return null;
}
}
通过Proxy类创建动态代理
public class TestMyProxy {
public static void main(String[] args) throws Exception{
//创建目标对象
ServiceInterface sale = new ServiceImpl();
//创建模板对象
ServiceInvocationHandler invocationHandler = new ServiceInvocationHandler();
//将目标对象注入模板对象中
invocationHandler.setInstance(sale);
Class serviceClazz = sale.getClass();
ServiceInterface proxy = (ServiceInterface) Proxy.newProxyInstance(serviceClazz.getClassLoader(), serviceClazz.getInterfaces(), invocationHandler);
proxy.a();
proxy.b();
}
}
通过测试,动态代理对象与前面的静态代理结果相同

生成代理对象的字节码文件
看到这里肯定很多人都会另一头雾水,动态代理底层到底是怎么实现的,我们想办法通过程序将代理对象字节码文件输出到磁盘上,然后通过jdgui反编译工具,查看动态代理对象的源码结构。
public class TestMyProxy {
public static void main(String[] args) throws Exception{
byte[] buffer = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{ServiceInterface.class});
try {
FileOutputStream output = new FileOutputStream("C:/Users/Lenovo/Desktop/$Proxy0.class");
output.write(buffer);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注:使用ProxyGenerator类需要将JRE中lib目录下的一个jar包导入到项目中

通过反编译工具可以看到代理对象源码结构如下(下列代码对源码进行了部分删减,如果有需要可以自行生成源代码查看):
public final class $Proxy0
extends Proxy
implements ServiceInterface
{
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final void b()
{
try
{
this.h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void a()
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("com.proxy.ServiceInterface").getMethod("b", new Class[0]);
m3 = Class.forName("com.proxy.ServiceInterface").getMethod("a", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
我们分析源码可知,代理对象中的代理方法,都执行了this.h.invoke()方法,this.h实际上是代理类的父类中(Proxy类)中的属性,它保存了在调用Proxy.newInstance()方法时传入的InvocationHandler实例,而InvocationHandler中的invoke方法就是,代理类的代理逻辑。

也就是说在生成的一个动态代理对象中,对目标对象中所有方法都进行了相同的前处理和后处理过程,因为都执行相同的invoke“模板”方法,也就是说如果我们想对目标对象中每个方法进行不同的前处理和后处理,我们需要编写不同的InvocationHandler实现类,然后创建不同的动态代理对象才能实现这一需求。
动态代理思想的总结:
动态代理实际上就是在内存中直接生成字节码文件(通常程序员写的java文件编译后生成的字节码文件都在硬盘中),然后将字节码加载进方法区生成字节码对象,生成字节码对象后通过反射就可以创建代理对象的实例了。生成字节码文件就必定需要生成这个代理类的方法,那程序是怎么知道这个代理对象需要实现哪些方法呢,这就是为什么在Proxy.newProxyInstance()需要传入接口的数组,传入几个接口,这个代理就会实现这些接口,程序自然就知道它需要实现哪些方法了。此时就会开始循环生成代理类中的方法,那这个方法的具体实现代码又是什么呢?由于生成的代理类继承了Proxy类,在Proxy类中有一个h字段,保存的是一个InvocationHandler实现类的对象,而我们通过Proxy.newInstance()方法时,传入了一个InvocationHandler实例,所以在代理对象中存放着一个InvocationHandler对象,代理对象的每个方法都会调用父类中存放的InvocationHandler实例中的invoke方法。
走过路过不要错过,原创不易,帮忙点个赞撒!!!!
JDK动态代理的深入理解的更多相关文章
- JDK 动态代理的简单理解
动态代理 代理模式是 Java 中的常用设计模式,代理类通过调用被代理类的相关方法,提供预处理.过滤.事后处理等服务,动态代理及通过反射机制动态实现代理机制.JDK 中的 java.lang.refl ...
- JDK动态代理的简单理解
转载:http://www.cnblogs.com/luotaoyeah/p/3778183.html 动态代理 代理模式是 Java 中的常用设计模式,代理类通过调用被代理类的相关方法,提供预处理. ...
- jdk动态代理:由浅入深理解mybatis底层
什么是代理 代理模式,目的就是为其他对象提供一个代理以控制对某个对象的访问,代理类为被代理者处理过滤消息,说白了就是对被代理者的方法进行增强. 看到这里,有没有感觉很熟悉?AOP,我们熟知的面向切面编 ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(下)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(上)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...
- 从Mybatis源码理解jdk动态代理默认调用invoke方法
一.背景最近在工作之余,把开mybatis的源码看了下,决定自己手写个简单版的.实现核心的功能即可.写完之后,执行了一下,正巧在mybatis对Mapper接口的动态代理这个核心代码这边发现一个问题. ...
- 举例理解JDK动态代理
JDK动态代理 说到java自带的动态代理api,肯定离不开反射.JDK的Proxy类实现动态代理最核心的方法: public static Object newProxyInstance(Class ...
- JDK动态代理
一.基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念.代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念.这里引用维基百科上的一句话对代理进行定义: A ...
- 静态代理和利用反射形成的动态代理(JDK动态代理)
代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 静态代理 1.新建 ...
- JDK动态代理的实现原理
学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504
随机推荐
- std::thread 三:条件变量(condition_variable())
condition_variable . wait . notify_one . notify_all *:notify_one:通知(唤醒)一个线程 *:notify_all:通知( ...
- 《深入理解Java虚拟机》读书笔记: 虚拟机类加载的时机和过程
虚拟机类加载的时机和过程 一.类加载的时机 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation ...
- 重新整理linux 系列 ——硬件的介绍(一)
前言 打算重新整理linux,计划每天一更,希望能够按照计划执行吧. 正文 首先有一个疑惑,那就是一台手机是否是一台计算机? 来看下什么可以定义为一台计算机: 计算机为接收用户的输入,经由中央处理器的 ...
- 重新整理数据结构与算法(c#)——算法套路普利姆算法[二十九]
前言 看一个题目: 这个问题就是求最小生成树,是图转换为树的一种方式. 最小生成树概念: 最小生成树简称MST. 1.n个顶点,一定有n-1条边 2.包含全部顶点. 3.图转换为最小生成树,权重之和最 ...
- sumo简单安装及运行实例
下载解压并添加环境变量 记录一下今天SUMO的安装及使用经验,写的可能比较潦草,没看懂的小伙伴在下方评论,我看到一定会解答. 第一步先打开网址下载sumo: https://sourceforge.n ...
- 调用App Store Connect Api
对iOS的证书.描述文件.账号.设备等管理,之前都去苹果开发者中心操作,官网上操作也比较繁杂,想搞一些自动化之类的,更是麻烦,有时候官网都打不开-- 其实苹果还提供里一套API接口,创建证书.创建账号 ...
- 力扣1620(java&python)-网络信号最好的坐标(中等)
题目: 给你一个数组 towers 和一个整数 radius . 数组 towers 中包含一些网络信号塔,其中 towers[i] = [xi, yi, qi] 表示第 i 个网络信号塔的坐标是 ...
- 力扣400(java)-第N位数字(中等)
题目: 给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第 n 位上的数字. 示例 1: 输入:n = 3输出: ...
- 让容器跑得更快:CPU Burst 技术实践
简介:让人讨厌的 CPU 限流影响容器运行,有时人们不得不牺牲容器部署密度来避免 CPU 限流出现.我们设计的 CPU Burst 技术既能保证容器运行服务质量,又不降低容器部署密度.CPU Bur ...
- IPv6时代,中小企业该如何布局?
简介:IPv6要为全世界的每一粒沙子都分配一个IP,你的企业跟上了吗? 11月中旬,中央网信办等部门联合印发了<关于开展IPv6技术创新和融合应用试点工作的通知>,联合组织开展IPv6技 ...