JDK静态代理、JDK动态代理、Cglib动态代理区别

雨点打透心脏的1/2处 2021-12-18 22:45 549阅读 0赞

一、代理模式介绍

什么是代理模式呢?我很忙,忙的没空理你,那你要找我呢就先找我的代理人吧,那代理人总要知道被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,代理人虽然不能干活,但是被代理的人能干活呀。说白了经纪人懂吧,整不好就绿你的那种。

二、JDK静态代理

场景:比如西门庆找潘金莲,那潘金莲不好意思答复呀,咋办,找那个王婆做代理呀,然后就绿了大郎。。。

第一步:定义一种类型的女人,王婆和潘金莲就属于这个类型的女人

  1. /**
  2. * 定义一种类型的女人,王婆和潘金莲就属于这个类型的女人
  3. */
  4. public interface KindWomen {
  5. //这种类型的女人能做什么事情呢?
  6. public void makeEyesWithMan(); //抛媚眼
  7. public void happyWithMan(); //你懂吧...
  8. }

第二步:定义一个潘金莲是什么样的女人

  1. //定义一个潘金莲是什么样的女人
  2. public class PanJinLian implements KindWomen {
  3. @Override
  4. public void makeEyesWithMan() {
  5. System.out.println("潘金莲抛媚眼");
  6. }
  7. @Override
  8. public void happyWithMan() {
  9. System.out.println("潘金莲在和男人做那个.....");
  10. }
  11. }

第三步:定义一个王婆,作为经纪人。

  1. public class WangPo implements KindWomen {
  2. private KindWomen kindWomen;
  3. public WangPo() { //潘金莲的代理
  4. this.kindWomen = new PanJinLian();
  5. }
  6. //她可以是KindWomen的任何一个女人的代理,只要你是这一类型
  7. public WangPo(KindWomen kindWomen) {
  8. this.kindWomen = kindWomen;
  9. }
  10. public void happyWithMan() {
  11. this.kindWomen.happyWithMan(); //自己老了,干不了,可以让年轻的代替
  12. }
  13. public void makeEyesWithMan() {
  14. this.kindWomen.makeEyesWithMan(); //王婆这么大年龄了,谁看她抛媚眼?他只能帮忙传递一下了。。
  15. }
  16. }

第四步:整一个西门庆。

  1. /**
  2. * 定义一个西门庆,他是个什么都懂吧。
  3. * 用快递小哥的话:您是什么东西?
  4. * 用垃圾分拣阿姨的话:您是个什么垃圾?
  5. */
  6. public class XiMenQing {
  7. /**
  8. * 看过水浒传的都知道,潘金莲抛了个媚眼,然后西门庆就把持不住了,
  9. * 然后西门庆去找王婆撮合,王婆因为贪图西门庆的钱财就答应做潘金莲的经纪人了,
  10. * 然后绿了的大郎。。
  11. * 那我们假设一下:
  12. * 如果没有王婆在中间牵线,这两个不要脸的能成吗?难说的很 !
  13. */
  14. public static void main(String[] args) {
  15. //把王婆叫出来
  16. WangPo wangPo = new WangPo();
  17. //然后西门庆就说,我要和潘金莲happy,然后王婆就安排了西门庆那出戏。
  18. wangPo.makeEyesWithMan(); //看到没,虽然表面上时王婆在做,实际上happy的是潘金莲
  19. wangPo.happyWithMan();
  20. }
  21. }

由此可见:代理模式是对开闭原则的典型实践,对扩展开放,对修改关闭

缺点:一个代理类只能代理一个业务接口,如果要代理多个业务接口需要定义多个实现类和代理类,如果在调用代理业务类前后的代码是一样的,则多个代理类会有很多冗余代码。

三、JDK动态代理

动态代理是指,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理 对象只是由代理生成工具(不是真实定义的类)在程序运行时由 JVM 根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。

对比静态代理,静态代理是指在程序运行前就已经定义好了目标类的代理类。代理类与目标类的代理关系在程序运行之前就确立了。

动态代理的实现方式常用的有两种:使用 JDK 的 Proxy,与通过 CGLIB 生成代理。

Jdk 的动态要求目标对象必须实现接口,这是 java 设计上的要求。 从 jdk1.3 以来,java 语言通过 java.lang.reflect 包提供三个类支持代理模式 Proxy, Method 和 InovcationHandler。

A**、通过 JDK java.lang.reflect.Proxy 类实现动态代理,会使用其静态方法 newProxyInstance()**,依据目标对象、业务接口及调用处理器三者,自动生成一 个动态代理对象。

public static newProxyInstance ( ClassLoaderloader, Class<?>[]interfaces, InvocationHandlerhandler)

loader:目标类的类加载器,通过目标对象的反射可获取

interfaces:目标类实现的接口数组,通过目标对象的反射可获取

handler:调用处理器。

B** InvocationHandler 是个接口,其具体介绍如下:**

实现了 InvocationHandler 接口的类用于加强目标类的主业务逻辑。这个接口中有一个 方法 invoke(),具体加强的代码逻辑就是定义在该方法中的。程序调用主业务逻辑时,会自动调用 invoke()方法。

//Object proxy:代理对象

//Method m :调用的方法

//Object [] args: 调用方法的参数

public Object invoke(Object proxy, Method m, Object[] args)

C** Method 类对象,该类有一个方法也叫 invoke()**,可以调用目标类的目标方法。 这两个 invoke()**方法,虽然同名,但无关。** public Object invoke ( Object obj, Object… args) obj:表示目标对象 args:表示目标方法参数,就是其上一层 invoke 方法的第三个参数

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5NjY5MDU4_size_16_color_FFFFFF_t_70

JDK动态代理实现如下:

场景:计算每个方法的执行时间

第一步:创建业务接口类

  1. public interface SomeService {
  2. void doSome();
  3. void doOther();
  4. }

第二步:创建业务接口的实现类

  1. public class SomeServiceImpl implements SomeService {
  2. public void doSome(){
  3. int sum = 0;
  4. for(int i=1;i<9000000;i++){
  5. sum += i;
  6. }
  7. System.out.println("do some...");
  8. }
  9. public void doOther(){
  10. int sum = 0;
  11. for(int i=1;i<7000000;i++){
  12. sum += i;
  13. }
  14. System.out.println("do other...");
  15. }
  16. }

第三步:创建代理类

  1. public class TimeInvocationHandler implements InvocationHandler {
  2. //目标对象
  3. private Object target;
  4. public TimeInvocationHandler(Object obj){
  5. this.target = obj;
  6. }
  7. /**
  8. * proxy:
  9. * 代理对象的引用
  10. * method:
  11. * 目标对象的方法
  12. * args:
  13. * 目标方法的实际参数列表(实参)
  14. */
  15. @Override
  16. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  17. long begin = System.currentTimeMillis();
  18. //无论是反射机制还是传统方式调用一个方法,必须具备四个要素:对象、方法、参数、返回值。
  19. //比较传统的方法调用方式:
  20. // boolean loginSuccess = userService.login("zhangsan" , "123");
  21. // 返回值 = 对象.方法(实参);
  22. //使用反射机制调用方法:
  23. // 返回值 = 方法.调用(对象, 参数);
  24. Object retValue = method.invoke(target, args);
  25. long end = System.currentTimeMillis();
  26. System.out.println(method.getName() + "耗时" + (end - begin) + "毫秒");
  27. return retValue;
  28. }
  29. //实例方法,绑定业务对象并返回一个代理类
  30. public Object getProxy(){
  31. // 通过反射机制,创建一个代理类对象并返回实例,用户进行方法调用时使用
  32. // 创建代理对象时,需要传递该业务类的类加载器(用来获取业务实现类的元数据,调用真的的业务方法)、接口、handler实现类(this是当前对象)
  33. return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
  34. }
  35. }

第四步:创建测试类

  1. public class Test {
  2. public static void main(String[] args) {
  3. //目标对象(真正执行/处理业务的对象)
  4. SomeService someService = new SomeServiceImpl();
  5. //代理对象(JDK的动态代理只能代理接口)
  6. /*
  7. * loader:类加载器
  8. * 动态代理当中,代理类是反射机制构造的,在硬盘上看不见,反射机制中拼接的class字节码,
  9. * 虽然看不到,但是在内存中会生成临时的“代理类.class”
  10. * 只要是类,一定要通过类加载器加载才行,所以第一个参数必须传类加载器。
  11. * 规定:代理类和目标类必须通过同一个类加载加载。
  12. *
  13. * interfaces:指定代理的哪些接口
  14. * 代理类和目标类所实现的接口应该是一样的。
  15. *
  16. * h: 调用处理器(InvocationHandler)
  17. * 调用处理器中有一个invoke方法。
  18. * 这个invoke方法什么时候执行?
  19. * SUN规定:
  20. * 当代理对象调用代理方法的时候,“注册”在调用处理器当中的invoke方法会被自动调用。
  21. */
  22. /*
  23. SomeService someServiceProxy = (SomeService)Proxy.newProxyInstance(
  24. someService.getClass().getClassLoader(),
  25. someService.getClass().getInterfaces(),
  26. new TimeInvocationHandler(someService));
  27. */
  28. SomeService someServiceProxy = (SomeService)new TimeInvocationHandler(someService).getProxy();
  29. //代理对象执行代理方法
  30. someServiceProxy.doSome(); //这个doSome()是代理对象的代理方法。
  31. someServiceProxy.doOther();
  32. }
  33. }

JDK动态代理:通过传进来的业务实现类和方法进行调用业务实现类的同名方法

缺点:JDK动态代理的代理对象在创建时,需要有业务实现类所实现的接口作为参数(因为后面代理方法需要根据接口内的方法名进行调用)。如果业务实现类没有实现接口而是直接定义接口的话,或者该业务实现类中增加了接口没有的方法(因为无法调用),就无法使用JDK动态代理。

四、CGLIB 动态代理

CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展Java 类与实现 Java 接口。它广泛的被许多 AOP 的框架使用,例如 Spring AOP。

使用 JDK 的 Proxy 实现代理,要求目标类与代理类实现相同的接口。若目标类不存在接口,则无法使用该方式实现。但对于无接口的类,要为其创建动态代理,就要使用 CGLIB 来实现。

CGLIB 代理的生成原理是生成目标类的子类,而子类是增强过的,这个子类对象就是代理对象。所以,使用 CGLIB 生成动态代理,要求目标类必须能够被继承,即不能是 final 的 类。

CGLIB 经常被应用在框架中,例如 Spring ,Hibernate 等。cglib 的代理效率高于 jdk。 项目中直接使用动态代理的地方不多。一般都使用框架提供的功能。

场景:目标类返回值改变为大写

第一步:创建目标类

  1. //业务目标类
  2. public class SomeService {
  3. public String doSome(){
  4. System.out.println("业务方法doSome");
  5. return "abcd";
  6. }
  7. }

第二步:创建方法拦截器的类

  1. //定义方法拦截器的类, 需要实现cglib的接口 MethodInterceptor(等同于jdk中的InvocationHandler)
  2. public class MyMethodInterceptor implements MethodInterceptor {
  3. private Object target;
  4. public MyMethodInterceptor(Object target) {
  5. this.target = target;
  6. }
  7. /**
  8. * intercept特点:截取对目标方法的调用
  9. * 参数:
  10. * Object obj:代理对象
  11. * Method method:目标方法
  12. * Object[] args:方法参数
  13. * MethodProxy proxy:方法的代理对象
  14. * 返回值:
  15. * Object:目标方法的执行结果
  16. */
  17. @Override
  18. public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  19. System.out.println("=========intercept=========");
  20. //调用目标方法
  21. Object result = method.invoke(target, args);
  22. if( result != null){
  23. String str = (String)result;
  24. result = str.toUpperCase();
  25. }
  26. return result;
  27. }
  28. }

第三步:创建获取代理对象的工具类

  1. /**
  2. * 创建代理对象
  3. */
  4. public class ProxyFactory {
  5. public Object createProxy(Object target){
  6. //使用Enhancer对象创建目标类的代理对象
  7. //创建Enhancer对象
  8. Enhancer en = new Enhancer();
  9. //指定目标类
  10. en.setSuperclass(SomeService.class);
  11. //指定方法拦截器对象,
  12. en.setCallback(new MyMethodInterceptor(target));
  13. //创建代理对象
  14. return en.create();
  15. }
  16. }

第四步:创建测试类

  1. public class MyTest {
  2. public static void main(String[] args) {
  3. //创建目标对象
  4. SomeService target = new SomeService();
  5. //创建工具类
  6. ProxyFactory factory = new ProxyFactory();
  7. //创建代理对象
  8. SomeService proxy = (SomeService) factory.createProxy(target);
  9. System.out.println("代理对象父类名称:"+proxy.getClass().getSuperclass().getName());
  10. System.out.println("代理类名称:"+proxy.getClass().getName());
  11. //通过代理对象执行方法。实现功能增强
  12. String str = proxy.doSome();
  13. System.out.println("str:"+str);
  14. }
  15. }

运行:查看代理对象父类名可以看出是继承自目标类的

20190702112748733.png

在Spring框架中AOP动态代理机制,如果被代理对象实现了需要被代理的接口,则使用JDK的动态代理,如果没有实现接口则使用CGLIB动态代理,spring会自动在JDK动态代理和CGLIB动态代理之间来回切换, 如果需要强制使用CGLIB动态代理可以在Spring配置文件中加入

发表评论

表情:
评论列表 (有 0 条评论,549人围观)

还没有评论,来说两句吧...

相关阅读