代理模式简介

代理模式(Proxy)是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。


代理模式是常用的 Java 设计模式,它的特征是代理类与委托类有同样的接口代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

我们根据加载被代理类的时机不同,将代理分为静态代理和动态代理。如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类这就是动态代理,比如 RPC 框架和 Spring AOP 机制。动态代理又可以分为 JDK的动态代理 和 Cglib 的动态代理。

一、静态代理

如果我们在代码编译时就确定了被代理的类是哪一个,这就是静态代理。静态代理由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类和代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成

不足:静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。

静态代理示例

(1)接口类 Shopping.java 接口

interface Shopping {void buy();
}

(2)实现类 Client.java

class Client implements Shopping {private String name;public Client(String name) {this.name = name;}public void buy() {System.out.println("我想买这件商品");}
}

(3)代理类 StaticProxy.java

class StaticProxy implements Shopping {// 用接口统一接收被代理的顾客private Shopping shopping;public StaticProxy(Shopping shopping) {this.shopping = shopping;}public void buy() {System.out.println("降价促销,疯狂大甩卖了!");shopping.buy();}
}

(4)测试类

public class StaticProxyTest {public static void main(String[] args) {// 被代理的顾客张三,他的购买活动由代理对象 service 完成Client client = new Client("张三");// 生成代理对象,并将顾客张三传给代理对象StaticProxy service = new StaticProxy(client);// 代理对象代理顾客张三完成购买service.buy();}
}

输出结果:

降价促销,疯狂大甩卖了!
我想买这件商品

静态代理最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。

二、动态代理

代理类在程序运行时创建的代理方式被成为动态代理。动态代理中的代理类并不是在 Java 代码中定义的,而是在运行时根据我们在 Java 代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

1、JDK 动态代理

在 Java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口来生成 JDK 动态代理类和动态代理对象,所有对动态代理对象的方法调用都会转发到 InvocationHandler 中的 invoke() 方法中实现
JDK 代理方式必须要有接口

JDK 为我们的生成了一个叫 $Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
我们可以将 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,在 invoke 方法中调用被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对 invoke 的调用最终都转为对被代理对象的调用。
代理类调用被代理对象的方法时,通过自身持有的中介类对象来调用中介类对象的 invoke 方法,然后 invoke 方法再调用被代理对象的相应方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

优点

● JDK动态代理是JDK原生的,不需要任何依赖即可使用;
● 通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;

缺点

● 如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
● JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。
● JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;

(1)InvocationHandler 接口

每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler这个接口的 invoke 方法来进行调用,并不是自己来真实调用,而是通过代理的方式来调用的。 invoke 方法是 InvocationHandler 接口的唯一一个方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

(2)Proxy 类

Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance
这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

这个方法的作用就是得到一个动态的代理对象,接收三个参数,三个参数所代表的含义如下:

通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式:$proxy和一个表示对象标号的数字,例如 $Proxy0, $Proxy1。

(3)JDK 动态代理简单实现:

① 通过实现 InvocationHandler 接口来自定义自己的InvocationHandler;
② 通过 Proxy.getProxyClass 获得动态代理类的 class 对象,必须使用接口的class为参数;
③ 通过反射机制获得代理类的构造方法,方法签名为 getConstructor(InvocationHandler.class);
④ 通过构造函数获得代理对象,并将自定义的 InvocationHandler 实例对象传为参数传入,代理对象强转为目标对象的接口类型;
⑤ 通过代理对象调用目标方法;

Shopping.java 接口

interface Shopping {void buy();
}

实现类 Client.java

class Client implements Shopping {private String name;public Client(String name) {this.name = name;}public void buy() {System.out.println("我想买这件商品");}
}

ClientInvocationHandler.java 类

该类实现 InvocationHandler 接口,这个类中持有一个被代理接口实现类的实例对象 target。InvocationHandler 中有一个 invoke() 方法,所有执行代理对象的方法都会被替换成执行 invoke() 方法,然后 invoke() 方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。

public class ClientInvocationHandler implements InvocationHandler {// InvocationHandler 持有的被代理对象private Shopping target;public ClientInvocationHandler(Shopping target) {this.target = target;}/*** proxy:代表动态代理对象* method:代表正在执行的方法* args:代表调用目标方法时传入的实参*/   @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK 动态代理执行" + method.getName() + "方法");Object result = method.invoke(target, args);return result;}
}

测试类

public class ProxyTest {public static void main(String[] args)throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {// =========================第一种方式=========================// 获取动态代理类的classClass proxyClass = Proxy.getProxyClass(Shopping.class.getClassLoader(),Shopping.class);  // 必须使用接口的class为参数// 获得代理类的构造函数,并传入参数类型 InvocationHandler.classConstructor constructor = proxyClass.getConstructor(InvocationHandler.class);// 通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入Shopping proxy = (Shopping) constructor.newInstance(new ClientInvocationHandler(new Client("张三")));  // 强转为目标对象的接口类型// 通过代理对象调用目标方法proxy.buy();// =========================第二种简便方式==========================// 创建一个实例对象,这个对象是被代理的对象Client target = new Client("张三");// 创建一个与代理对象相关联的 InvocationHandlerClientInvocationHandler handler = new ClientInvocationHandler(target);// 创建一个接口类型的代理对象 proxy 来代理 target,代理对象的每个执行方法都会替换执行 Invocation 中的 invoke() 方法// 第一个参数 handler.getClass().getClassLoader() ,我们这里使用 handler 这个类的 ClassLoader 对象来加载我们的代理对象// 第二个参数 target.getClass().getInterfaces(),我们这里为代理对象提供的接口数组存放的是真实对象所实现的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了// 第三个参数 handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上Shopping proxy = (Shopping) Proxy.newProxyInstance(handler.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);// 代理执行购买的方法proxy.buy();
}

输出结果:

JDK 代理执行buy方法
我想买这件商品

动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在 InvocationHandler 中的 invoke() 方法调用的,所以我们只要在 invoke() 方法中统一处理,就可以对所有被代理的方法进行相同的操作了。

2、Cglib 动态代理

JDK 动态代理要求target对象是一个接口的实现对象,假如 target 对象并没有实现任何接口,只是一个单独的对象,这时候就会用到 Cglib 代理(Code Generation Library),即通过构建一个子类对象,从而实现对 target 对象的代理。
CGLib 实现动态代理的原理是,底层采用了 ASM 字节码生成框架,直接对需要代理的类的字节码进行操作,生成这个类的一个子类,并重写了类的所有可以重写的方法,在重写的过程中,将我们定义的额外的逻辑(简单理解为Spring中的切面)织入到方法中,对方法进行了增强。因此目标对象不能是 final 类(报错),且目标对象的方法不能是 final 或 static(不执行代理功能)
而通过字节码操作生成的代理类,和我们自己编写并编译后的类没有太大区别。

优点

● 使用CGLib代理的类,不需要实现接口,因为CGLib生成的代理类是直接继承自需要被代理的类;
● CGLib生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中所有能够被子类重写的方法进行代理;
● CGLib生成的代理类,和我们自己编写并编译的类没有太大区别,对方法的调用和直接调用普通类的方式一致,所以CGLib执行代理方法的效率要高于JDK的动态代理;

缺点

● 由于CGLib的代理类使用的是继承,这也就意味着如果需要被代理的类是一个final类则无法使用CGLib代理;
● 由于CGLib实现代理方法的方式是重写父类的方法,所以无法对final方法或者private方法进行代理,因为子类无法重写这些方法;
● CGLib生成代理类的方式是通过操作字节码,这种方式生成代理类的速度要比JDK通过反射生成代理类的速度更慢;

Cglib 动态代理简单实现:

① 引入相关依赖;
② 代理类实现 MethodInterceptor 接口,实现 intercept 方法;
③ 创建代理对象

(1)添加 Cglib 依赖的 jar 包

 <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.10</version></dependency>

(2)被代理对象类 Client1.java

public class Client1 {private String name;// 无参构造函数public Client1() {}public Client1(String name) {this.name = name;}public void buy() {System.out.println("我想买这件商品");}
}

(3)代理类 CglibProxy.java

public class CglibProxy implements MethodInterceptor {private Object target;public CglibProxy(Object target) {this.target = target;}// 给目标对象创建一个代理对象public Object getProxyInstance() {// 工具类Enhancer en = new Enhancer();// 设置父类,被代理类必须要有无参构造函数en.setSuperclass(target.getClass());//设置回调函数en.setCallback(this);//创建子类代理对象return en.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("Cglib 动态代理执行" + method.getName() + "方法");Object obj = method.invoke(target);return obj;}
}

(4)测试类

public class test {public static void main(String[] args) {Client1 target = new Client1("zj");// 创建一个与代理对象相关联的 proxyFactory CglibProxy proxyFactory = new CglibProxy(target);// 给目标对象创建一个代理对象Client1 proxy = (Client1) proxyFactory.getProxyInstance();proxy.buy();}
}

输出结果:

Cglib 动态代理执行buy方法
我想买这件商品

3、JDK 代理和 Cglib 代理的区别

(1)实现方式

① JDK 动态代理利用拦截器(拦截器必须实现 InvocationHanlder )和反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。
② CGLIB 动态代理利用 ASM 开源包,将被代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处理

(2)何时使用 JDK 或者 CGLIB

● 如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP,也可以强制使用 CGLIB 实现AOP。
● 如果目标对象没有实现了接口,必须采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。
● JDK 代理是不需要第三方库支持,只需要 JDK 环境就可以进行代理;Cglib 必须依赖于 CGLib 的类库。

(3)JDK 动态代理和 CGLIB 字节码生成的区别

1)JDK 动态代理只能对实现了接口的类生成代理,而不能针对类。

2)CGLIB 是针对类实现代理,为指定的被代理的类生成一个子类,覆盖其中的方法,并对覆盖的方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的

(4)CGlib 与 JDK 的快慢

1)使用 CGLib 实现动态代理,CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在 jdk6 之前比使用 Java 反射效率要高。唯一需要注意的是,CGLib 不能对声明为final的方法进行代理,因为 CGLib 原理是动态生成被代理类的子类。
2)在 jdk6、jdk7、jdk8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下, JDK 代理效率高于 CGLIB 代理效率,只有当进行大量调用的时候,jdk6 和 jdk7 比 CGLIB 代理效率低一点,但是到 jdk8 的时候,jdk 代理效率高于 CGLIB 代理。

总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

(5)Spring 如何选择用 JDK 还是 CGLIB?

1)当 Bean 实现接口时,Spring 就会用 JDK 的动态代理。

2)当 Bean 没有实现接口时,Spring 使用 CGlib 的动态代理。

3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。

Java 代理使用详解相关推荐

  1. Java代理设计模式详解

    代理即通过代理类,找到适合你的实现类.相当于现实生活中的中介的角色,你想租房子,这个时候你又不想自己找房子,那你可以找中介,通过中介找到合适自己的房子,同时你也可以让中介帮你签合同等其他事宜.代理存在 ...

  2. 49天精通Java,第18天,Java代理类详解

    目录 一.何时使用代理? 二.创建代理对象 三.代理类的特性 四.代理模式 五.组成 六.优点 1.职责清晰 2.保护对象 3.高扩展性 七.模式结构 八.静态代理 九.动态代理 1.动态代理流程图 ...

  3. java的动态代理机制详解

    2019独角兽企业重金招聘Python工程师标准>>> 参考资料 1.java的动态代理机制详解 转载于:https://my.oschina.net/Howard2016/blog ...

  4. 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

    2019独角兽企业重金招聘Python工程师标准>>> 在运行时期可以按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码.当前有很多开源框架可以完成这些功能,如A ...

  5. 4.6 W 字总结!Java 11—Java 17特性详解

    作者 | 民工哥技术之路 来源 | https://mp.weixin.qq.com/s/SVleHYFQeePNT7q67UoL4Q Java 11 特性详解 基于嵌套的访问控制 与 Java 语言 ...

  6. cglib动态代理jar包_代理模式详解:静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...

  7. 代理模式详解(静态代理和动态代理的区别以及联系)

    原文链接:https://www.cnblogs.com/takumicx/p/9285230.html 1. 前言 代理模式可以说是生活中处处可见.比如说在携程上定火车票,携程在这里就起到了一个代理 ...

  8. 【Spring AOP】静态代理设计模式、Spring 动态代理开发详解、切入点详解(切入点表达式、切入点函数)

    AOP 编程 静态代理设计模式 1. 为什么需要代理设计模式 2. 代理设计模式 名词解释 代理开发的核心要素 静态代理编码 静态代理存在的问题 Spring 动态代理开发 搭建开发环境 Spring ...

  9. java源码详解——String类

    java源码详解--String类目录: Java String 类 下面开始介绍主要方法: Java charAt() 方法 Java compareTo() 方法 int compareTo(St ...

  10. 【狂神说Java】多线程详解

    [狂神说Java]多线程详解 1.任务 生活中的例子.边吃饭.边玩手机 开车.打电话.挂点滴 上厕所.玩手机 现实中太多这样同时做多件事情的例子了,看起来是多个任务都在做,其实本质上我们的大脑在同一时 ...

最新文章

  1. 百度某员工哀叹:身边的混子太多,坚持了一年,简直浪费生命!
  2. 预测过去?DeepMind用AI复原古希腊铭文,登Nature封面
  3. java转账_JAVA实现账户转账问题
  4. 饥荒海难机器人怎么用_饥荒:海难是一款野外生存游戏
  5. linux git文件图标,分享|三款 Linux 下的 Git 图形客户端
  6. kubernetes-Service
  7. HDU - 1054 Strategic Game (二分图匹配模板题)
  8. c语言字符马图案,C语言实现马踏棋盘
  9. Linux入门-第四周
  10. 2020爱分析·智能通讯云厂商全景报告
  11. 查看MXNet模型结构
  12. 基音提取之短时自相关法
  13. [词根词缀]reg/rept/rid/rod/rot等衍生单词
  14. 猴子钦定大王(循环单链表)
  15. 【渝粤教育】电大中专电子商务网站建设与维护 (18)作业 题库
  16. RT-Thread_rt_kprintf()打印浮点数(解决方法2:添加rt_vsnprintf_full)
  17. 计算机专业需要安装哪些软件,大学生电脑必装软件,快看你有哪些?
  18. 事件(阻止事件传播、阻止默认事件、事件源对象、事件委托)
  19. CC2541 power saving
  20. 具有快表的地址变换机构

热门文章

  1. windows系统背景淡绿护眼色设置
  2. 腾达n318虚拟服务器,腾达N318无线路由器的设置教程
  3. AssertionError: Invalid device id
  4. Android 4.1新特性
  5. 分布式操作系统 - 4.分布式通信管理
  6. Java 截取字符串
  7. Urgent VS Relex
  8. ViewPager圆形指示器
  9. Andriod Studio 安装过程
  10. Google guava之Multimap简介说明