从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式
代理模式是一种理论上非常简单,但是各种地方的实现往往却非常复杂。本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现。读完本文你将get到以下几点:
- 为什么需要代理模式,它通常用来解决什么问题,以及代理模式的设计与实现思路
- Java领域中代理模式3种不同实现类型(静态代理,jdk动态代理,cglib)
- 代理模式的面试考点
为什么要使用代理模式
在生活中我们通常是去商场购买东西,而不是去工厂。最主要的原因可能有以下几种:
- 成本太高,去工厂路途遥远成本太高,并且可能从工厂进货要办理一些手续流程;
- 工厂不直接卖给你,毕竟可能设计到一些行业机密或者无良厂家有一些不想让你知道的东西;
- 商场能提供一些商品之外的服务,商场里有舒适的温度,整洁的洗手间,当然还有漂亮的小姐姐。
在面向对象的系统中也有同样的问题,有些对象由于某种原因,比如对象创建开销很大,或者某些操作需要安全控制等,直接访问会给使用者或者系统结构带来很多麻烦,这时我们就需要考虑使用代理模式。
在应用中我们可能会用代理模式解决以下问题:
- 权限控制与日志, 在客户端请求接口时我们可能需要在调用之前对权限进行验证,或者通过记录接口调用前后时间,统计执行时长,又或者说我们需要记录用户的一些操作日志信息等,我们可以对原接口进行代理,然后根据需求在接口执行前后增加一些特定的操作。
- 重量级操作, 比如创建开销大的对象, 可以先由代理对象扮演对象的替身,在需要的使用再创建对象,然后代理再将请求委托给真实的对象。
什么是代理模式
代理模式:为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。类图如下:
所谓控制,其实使用接口隔离其他对象与这个对象之间的交互;就是为client对象对RealSubject对象的访问一种隔离,本质上就是CLient→RealSuject的关系变成了Client→Subject, Proxy→RealSubject。 需要注意的时,代理类(Proxy)并不一定要求保持接口的完整的一致性(既也可以完全不需实现Subject接口),只要能够实现间接控制即可。
代理模式代码演进
背景:假设已有一个订单系统,可以保存订单信息。
需求:打印保存订单信息消耗时间。
/**
* 订单服务
*
* @author cruder
* @date 2019-11-23 15:42
**/
public class OrderService2 {
/**
* 保存订单接口
*/
public void saveOrder(String orderInfo) throws InterruptedException {
// 随机休眠,模拟订单保存需要的时间
Thread.sleep(System.currentTimeMillis() & 100);
System.out.println("订单:" + orderInfo + " 保存成功");
}
}
普通方式实现
直接修改源代码,这通常也是最简单和最容易想到的实现。
/**
* 保存订单接口, 直接修改代码
*/
public void saveOrder(String orderInfo) throws InterruptedException {
long start = System.currentTimeMillis();
// 随机休眠,模拟订单保存需要的时间
Thread.sleep(System.currentTimeMillis() & 100);
System.out.println("订单:" + orderInfo + " 保存成功");
System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
}
面向对象设计原则中的“开闭原则”告诉我们,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
代理模式实现
/**
* 1. 定义接口,为了使代理被代理对象看起来一样。当然这一步完全可以省略
*
* @author cruder
* @date 2019-11-23 15:58
**/
public interface IOrderService {
/**
* 保存订单接口
* @param orderInfo 订单信息
*/
void saveOrder(String orderInfo) throws InterruptedException;
}
/**
* 2. 原有订单服务,也实现这个接口。注意 此步骤也完全可以省略。
*
* @author cruder
* @date 2019-11-23 15:42
**/
public class OrderService implements IOrderService{
/**
* 保存订单接口
*/
@Override
public void saveOrder(String orderInfo) throws InterruptedException {
// 随机休眠,模拟订单保存需要的时间
Thread.sleep(System.currentTimeMillis() & 100);
System.out.println("订单:" + orderInfo + " 保存成功");
}
}
/**
* 3. 创建代理类,实现订单服务接口【这才是代理模式的实现】
*
* @author cruder
* @date 2019-11-23 16:01
**/
public class OrderServiceProxy implements IOrderService{
/**
* 内部持有真实的订单服务对象,保存订单工作实际由它来完成
*/
private IOrderService orderService;
@Override
public void saveOrder(String orderInfo) throws InterruptedException {
/**
* 延迟初始化,也可以创建代理对象时就创建,或者作为构造参数传进来
* 仅作为代码实例,不考虑线程安全问题
*/
if (orderService == null) {
orderService = new OrderService();
}
long start = System.currentTimeMillis();
orderService.saveOrder(orderInfo);
System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
}
}执行程序
执行程序
代理模式的优缺点
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
Java中代理模式的实现
在java中代理模式可以按照代理类的创建时机分两类,即静态代理和动态代理,而动态代理又可以分为jdk动态代理和cglib动态代理。每种实现方式都各有千秋,接下来笔者将回针对不同的实现方式进行演示和剖析。
静态代理
在上文代理模式代码演进中就使用了静态代理模式。所谓静态代理中的“静”字,无非就是代理类的创建时机不同罢了。静态代理需要为每个被代理的对象手动创建一个代理类;而动态代理则时在运行时通过某种机制来动态生成,不需要手动创建代理类。
动态代理 - jdk
jdk动态代理模式是利用java中的反射技术,在运行时动态创建代理类。接下来我们仍借助上文中的订单服务的案例,使用jdk动态代理实现。
基于动态jdk涉及到两个核心的类Proxy类和一个 InvocationHandler接口。
/**
* 基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口
*
* @author cruder
* @date 2019-11-23 16:40
**/
public class ProxyFactory implements InvocationHandler {
/**
* 委托对象,既被代理的对象
*/
private Object target;
public ProxyFactory (Object target) {
this.target = target;
}
/**
* 生成代理对象
* 1. Classloader loader: 制定当前被代理对象使用的累加子啊其,获取加载器的方法固定
* 2. Class<?>[] interfaces: 委托类的接口类型,使用泛型方法确认类型
* 3. InvocationHandler handler: 事件处理,执行委托对象的方法时会触发事件处理器方法,
* 会把当前执行的委托对象方法作为参数传入
*/
public Object getProxyInstance() {
Class clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
method.invoke(target, args);
System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
return null;
}
}
/**
* 通过动态代理方式来保存订单
*
* @author cruder
* @date 2019-11-23 15:49
**/
public class Client {
public static void main(String[] args) throws InterruptedException {
ProxyFactory proxyFactory= new ProxyFactory (new OrderService());
IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance();
orderService.saveOrder(" cruder 新买的花裤衩 ");
}
}
以上便是jdk动态代理的全部实现,有种只可意会不可言传的感觉,笔者始终感觉这种实现看起来很别扭。不过也要强行总结以下,jdk实现动态代理可以分为以下几个步骤:
- 先检查委托类是否实现了相应接口,保证被访问方法在接口中也要有定义
- 创建一个实现InvocationHandler接口的类
- 在类中定义一个被代理对象的成员属性,为了扩展方便可以直接使用Object类,也可以根据需求定义相应的接口
- 在invoke方法中实现对委托对象的调用,根据需求对方法进行增强
- 使用Proxy.newProxyInstance(...)方法创建代理对象,并提供要给获取代理对象的方法
代理类源码阅读
上文中基于jdk动态代理的代码实现中对于可*的产品经理来说已经完全满足了需求,但是对于具有Geek精神的程序员来说这远远不够,对于这种不知其所以然的东西往往让人感到不安。接下来我们将通过自定义的一个小工具类将动态生成的代理类保存到本地来一看究竟。
/**
* 将生成的代理类保存为.class文件的工具类
*
* @author cruder
* @date 2019-08-15 0:27
*/
public class ProxyUtils {
/**
* 将代理类保存到指定路径
*
* @param path 保存到的路径
* @param proxyClassName 代理类的Class名称
* @param interfaces 代理类接口
* @return
*/
public static boolean saveProxyClass(String path, String proxyClassName, Class[] interfaces){
if (proxyClassName == null || path == null) {
return false;
}
// 获取文件字节码,然后输出到目标文件中
byte[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces);
try (FileOutputStream out = new FileOutputStream(path)) {
out.write(classFile);
out.flush();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
}
// 此处是重点, 生成的代理类实现了IOrderService,并且继承了Proxy
public final class $Proxy0 extends Proxy implements IOrderService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void saveOrder(Order var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
// 通过反射获取Method对象
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("cn.mycookies.test08proxy.IOrderService").getMethod("saveOrder", Class.forName("cn.mycookies.test08proxy.Order"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
ps: 实习转正面试中被问到为什么jdk动态代理被代理的类为什么要实现接口?
cglib动态代理
对于cglib我想大多数人应该都很陌生,或者是在学习Spring中AOP(面向切面编程)时听说了它使用jdk和cglib两种方式实现了动态代理。接下来笔者将针对cglib进行简要介绍。
cglib动态代理和jdk动态代理类似,也是采用操作字节码机制,在运行时生成代理类。cglib 动态代理采取的是创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调用者本身的效果。
字节码处理机制-指得是ASM来转换字节码并生成新的类
注:spring中有完整的cglib相关的依赖,所以以下代码基于spring官方下载的demo中直接进行编写的
/**
* 1. 订单服务-委托类,不需要再实现接口
*
* @author cruder
* @date 2019-11-23 15:42
**/
public class OrderService {
/**
* 保存订单接口
*/
public void saveOrder(String orderInfo) throws InterruptedException {
// 随机休眠,模拟订单保存需要的时间
Thread.sleep(System.currentTimeMillis() & 100);
System.out.println("订单:" + orderInfo + " 保存成功");
}
}
/**
* cglib动态代理工厂
*
* @author cruder
* @date 2019-11-23 18:36
**/
public class ProxyFactory implements MethodInterceptor {
/**
* 委托对象, 即被代理对象
*/
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 返回一个代理对象
* @return
*/
public Object getProxyInstance(){
// 1. 创建一个工具类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4.创建子类对象,即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("cglib代理:保存订单用时: " + (System.currentTimeMillis() - start) + "ms");
return result;
}
}
/**
* 使用cglib代理类来保存订单
*
* @author cruder
* @date 2019-11-23 15:49
**/
public class Client {
public static void main(String[] args) throws InterruptedException {
// 1. 创建委托对象
OrderService orderService = new OrderService();
// 2. 获取代理对象
OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance();
String saveFileName = "CglibOrderServiceDynamicProxy.class";
ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class});
orderServiceProxy.saveOrder(" cruder 新买的花裤衩 ");
}
}
cglib动态代理实现步骤和jdk及其相似,可以分为以下几个步骤:
- 创建一个实现MethodInterceptor接口的类
- 在类中定义一个被代理对象的成员属性,为了扩展方便可以直接使用Object类,也可以根据需求定义相应的接口
- 在invoke方法中实现对委托对象的调用,根据需求对方法进行增强
- 使用Enhancer创建生成代理对象,并提供要给获取代理对象的方法
cglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别,唯一的区别在于cglib生成的代理类继承了仅仅Proxy类,而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码如下:
// 生成一个Proxy的子类
public final class OrderService extends Proxy {
private static Method m1;
private static Method m2;
private static Method m0;
public OrderService(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
jdk动态代理 VS cglib
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
cglib 优势:
- 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 cglib 动态代理就没有这种限制。
- 只操作我们关心的类,而不必为其他相关类增加工作量。
总结
- 代理模式: 为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。
- jdk动态代理生成的代理类继承了Proxy类并实现了被代理的接口;而cglib生成的代理类则仅继承了Proxy类。
- jdk动态代理最大缺点:只能代理接口,既委托类必须实现相应的接口
- cglib缺点:由于是通过“子类化”的方式, 所以不能代理final的委托类或者普通委托类的final修饰的方法。
Q&A
- 为什么jdk动态代理只能代理接口?
- Spring中AOP的实现采用那种代理方式?
- 都说jdk动态代理性能远比cglib要差,如果是,依据是什么?
从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式的更多相关文章
- Spring 静态代理+JDK动态代理和CGLIB动态代理
代理分为两种:静态代理 动态代理 静态代理:本质上会在硬盘上创建一个真正的物理类 动态代理:本质上是在内存中构建出一个类. 如果多个类需要进行方法增强,静态代理则需要创建多个物理类,占用磁盘空间.而动 ...
- 静态代理、JDK动态代理和CGLib动态代理之前的区别
昨天看了一天的代理方面的知识,刚开始看的时候没看出什么花头来,感觉不实用.一大堆的东西,还不如直接new出来,然后调用方法.后来仔细研究了一下AOP(面向切面)的思想,才发现代理的用处实在太大了.现在 ...
- java的静态代理、jdk动态代理和cglib动态代理
Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...
- 代理模式之静态代理,JDK动态代理和cglib动态代理
代理模式,顾名思义,就是通过代理去完成某些功能.比如,你需要购买火车票,不想跑那么远到火车站售票窗口买,可以去附近的火车票代售点买,或者到携程等第三方网站买.这个时候,我们就把火车站叫做目标对象或者委 ...
- Java代理:静态代理、JDK动态代理和CGLIB动态代理
代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式.所谓的代理者是指一个类别可以作为其它东西的接口.代理者可以作任何东西的接口:网络连接.存储器中的大对象.文件或其它昂贵或无法复制 ...
- jdk动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)
代理模式是一种很常见的模式,本文主要分析cglib动态代理的过程 1. 举例 使用cglib代理需要引入两个包,maven的话包引入如下 <!-- https://mvnrepository.c ...
- JDK动态代理和CGLib动态代理简单演示
JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...
- spring源码学习【准备】之jdk动态代理和cglib动态代理的区别和性能
一:区别:---->JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了.--->JDK的动态代理机制只能代理实现了接口的类,而不能 ...
- 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...
随机推荐
- 【Python秘籍】ASCII码与字符的转换
如何在python中显示ASCII码呢?其实你只需要记住两个函数即可:ord()和 chr(),这两个函数都是python内置的函数,不需要引入任何的包,直接就可以使用. 一.显示ASCII码 显示A ...
- opencv::积分图计算
利用积分图像,可以计算在某象素的上-右方的或者旋转的矩形区域中进行求和.求均值以及标准方差的计算,并且保证运算的复杂度为O(). #include <opencv2/opencv.hpp> ...
- opencv::自定义角点检测
#include <opencv2/opencv.hpp> #include <iostream> #include <math.h> using namespac ...
- 百万年薪python之路 -- 包
包 使用import 和from xx import xx 现有如下结构 bake ├── __init__.py ├── api ├── __init__.py ├── policy.py └── ...
- 轻量级CNN模型之squeezenet
SqueezeNet 论文地址:https://arxiv.org/abs/1602.07360 和别的轻量级模型一样,模型的设计目标就是在保证精度的情况下尽量减少模型参数.核心是论文提出的一种叫&q ...
- 6G仅仅是比5G多1G吗??
第六代移动通信系统(6th generation mobile networks,或6th generation wireless systems),简称6G,是指第六代移动通信技术,是5G系统后的延 ...
- 修改List<Map<String, Object>>的值
List<Map<String, Object>> aList = new ArrayList(); //加入一个Map元素Map map = new HashMap();m ...
- python语法入门之流程控制
python语法入门之流程控制 流程控制是指控制流程,具体指控制程序执行的流程. 流程控制分为三种: 1.顺序结构 程序从上而下的正常执行(正常执行的代码就是顺序结构) 2.分支结构 赋予程序人的思维 ...
- 【排列组合】给定一个M*N的格子或棋盘,从左下角走到右上角的走法总数(每次只能向右或向上移动一个方格边长的距离)
版权声明:本文为CSDN博主「梵解君」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明. 原文链接:https://blog.csdn.net/hadeso/art ...
- 货物移动BAPI:BAPI_GOODSMVT_CREATE报错提示“不能执行功能模块 MB_CREATE_GOODS_MOVEMENT”的原因
在开发过程中,我们调用BAPI:BAPI_GOODSMVT_CREATE进行货物移动生成物料凭证时,出现了报错提示:“不能执行功能模块 MB_CREATE_GOODS_MOVEMENT”,如下图所示: ...