写点什么

Spring 之 JDK 动态代理与 CGLIB 动态代理

作者:echoes
  • 2022 年 8 月 04 日
  • 本文字数:3916 字

    阅读完需:约 13 分钟

Spring之JDK动态代理与CGLIB动态代理

实现原理区别

通过反编译 JDK 的代理类和 CGLIB 的代理类,可以比较两种不同的实现机制:

目标类:

JDK 动态代理是通过实现目标类的接口,然后将目标类在构造动态代理时作为参数传入,使代理对象持有目标对象,再通过代理对象的 InvocationHandler 实现动态代理的操作。


CGLIB 动态代理是通过配置目标类信息,然后利用 ASM 字节码框架进行生成目标类的子类。当调用代理方法时,通过拦截方法的方式实现代理的操作。


总的来说: JDK 动态代理利用接口实现代理,只能代理实现了接口的类,代理对象是利用反射机制动态生成;CGLIB 动态代理利用继承的方式实现代理,可以代理未实现任何接口的类,代理对象是利用拦截机制动态生成。

一、JDK 动态代理

利用 JDK 的 API(利用反射机制),动态的在内存中构建代理对象,且代理类必须实现 InvocationHandler 接口,同时目标对象要实现接口,否则不能用动态代理。在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

1、JDK 动态代理类使用步骤

  1. 定义一个接口及其实现类(被代理对象);

  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;

  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;

1.1 定义被代理类

接口

/** * @Description: TestProxy0Impl * @Author: 青衣醉 * @Date: 2022/7/21 4:30 下午 */public interface TestProxy0 {    public String test0();}
public interface TestProxy1 { public void test1(String mm);} public interface TestProxy2 { public String test2(String cc);}
复制代码

实现类

package com.abc.test;
public class TestProxyImpl implements TestProxy0,TestProxy1,TestProxy2{ @Override public String test0() { System.out.println ("调用了test0方法"); return "方法结束"; } @Override public void test1(String mm) { System.out.println ("test1"+mm);
} @Override public String test2(String cc) { System.out.println ("test2");
return cc+"方法结束";; }
}
复制代码
1.2 定义 InvocationHandler

代理类的逻辑处理程序,可在 invoke 方法中自定义些逻辑对被代理类方法进行增强

invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。


/** * @Description: TargetInvoker * @Author: 青衣醉 * @Date: 2022/7/21 5:14 下午 */public class TargetInvoker implements InvocationHandler { private Object target; public TargetInvoker(Object target) { this.target = target; } //proxy 被代理对象 //method 目标对象中的方法对象 //args 方法对象的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("jdk 代理执行前"); Object result = method.invoke(target, args); System.out.println("jdk 代理执行后"); return result; }}
复制代码
1.3 定义 JDK 代理工厂

通过 Proxy 类的 newProxyInstance 方法创建代理对象

package com.abc.test;
import java.lang.reflect.Proxy;
/** * @Description: JDK动态代理类厂 * @Author: 青衣醉 * @Date: 2022/7/27 10:15 上午 */public class JDKProxy { public static Object getProxy(Object obj){ Class<?> aClass = obj.getClass (); /** * 第一个参数: aClass.getClassLoader (),使用handler对象的classloader对象来加载我们的代理对象 * 第二个参数:aClass.getInterfaces (),这里为代理类提供的接口是真实对象实现的接口 * ,这样代理对象就能像真实对象一样调用接口中的所有方法 * 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上 */ return Proxy.newProxyInstance (aClass.getClassLoader (), aClass.getInterfaces (), new TargetInvoker (obj)); }}
复制代码
1.4 测试
   @Test    public void jdkProxy(){        TestProxy2 proxy =  (TestProxy2) JDKProxy.getProxy (new TestProxyImpl ());        System.out.println (proxy.test2 ("使用jdk动态代理"));    }

复制代码


2、源码输出

idea 中通过配置 VM 参数可以将动态生成的代理类的字节码文件保存到本地,方便观察分析代理类的结构。

JDK8 及以前版本:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

JDK8 以后版本:-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true

3、源码分析


InvocationHandler 实现类 #invoke


从上面源码来看

1、JDK 动态代理,通过继承 Proxy 类,然后实现被代理类的的接口,来生成代理对象;

2、调用时,在实现的接口方法里面调用父类中 InvocationHandler 的 invoke 方法;

3、在接口 InvocationHandler 的实现类里面定义具体的调用逻辑,这里就可以增加额外的功能,对目标方法进行增强。

二、CGLIB 动态代理

CGLIB 动态代理的实现机制是生成目标类的子类,通过调用父类(目标类)的方法实现,在调用父类方法时再代理中进行增强。在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

1、CGLIB 动态代理类使用步骤

  1. 定义一个类;

  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;

  3. 通过 Enhancer 类的 create()创建代理类;

1.1 定义被代理类
package com.abc.test;
/** * @Description: CglibSevice * @Author: 青衣醉 * @Date: 2022/8/4 11:05 上午 */public class CglibSevice {
public String send (String msg){ System.out.println (msg); return "发送邮件成功"; }}
复制代码
1.2 定义 MethodInterceptor(方法拦截器)

创建拦截器类(增强类), 实现 MethodInterceptor 接口. 在这里面可以对方法进行增强处理

package com.abc.test;

import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/** * @Description: TargetInterceptor方法拦截接口 * @Author: 青衣醉 * @Date: 2022/7/26 5:07 下午 */public class TargetInterceptor implements MethodInterceptor {
/** * 拦截所有目标类的方法调用 * * @param obj 目标对象 * @param method 目标方法 * @param args 方法参数 * @param methodProxy 代理类实例 * @return * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("CGLIB 调用前"); //代理类对象调用父类方法 Object result = methodProxy.invokeSuper(obj, args); System.out.println("CGLIB 调用后"); return result;
}}
复制代码
1.3 定义 CGLIB 代理工厂
package com.abc.test;
import org.springframework.cglib.proxy.Enhancer;
import java.io.File;import java.io.FileOutputStream;
/** * @Description: CglibProxy动态代理类 * @Author: 青衣醉 * @Date: 2022/7/27 10:15 上午 */public class CglibProxy { public static Object getProxy(Class<?> clazz){ Enhancer enhancer = new Enhancer(); // 设置类加载 enhancer.setClassLoader(clazz.getClassLoader()); // 设置被代理类 enhancer.setSuperclass(clazz); // 设置方法拦截器 enhancer.setCallback(new TargetInterceptor()); // 创建代理类 Object o = enhancer.create (); //输出字节码文件 extracted (enhancer); return o; }}
复制代码
1.4 测试
   @Test    public void cglibProxy(){        CglibSevice proxy = (CglibSevice) CglibProxy.getProxy (CglibSevice.class);
System.out.println (proxy.send ("测试cglib动态代理!")); }
复制代码


2、源码输出

  private static void extracted(Enhancer enhancer) {        try {            byte[] generate = new byte[0];            generate = enhancer.getStrategy ().generate (enhancer);            FileOutputStream fileOutputStream = new FileOutputStream (                    new File ("/Users/tangyunhang/Studying/JAVAGJ/StudyTestLibrary/com/CglibProxy.class"));            fileOutputStream.write (generate);            fileOutputStream.flush ();            fileOutputStream.close ();        } catch (Exception e) {            e.printStackTrace ();        }    }
复制代码

3、源码分析


从上述源码中看到 CGLIB 生成的代理类是通过创建一个新的类继承被代理类,并重写了父类的方法,在重写的方法里面调用的接口的 intercept 方法,在这里面可以增加额外的逻辑实现对目标方法的增强。


三、总结


1、如果目标对象实现了接口,则默认采用 JDK 动态代理;

2、如果目标对象没有实现接口,则使用 Cglib 代理;

3、如果目标对象实现了接口,但强制使用了 Cglib,则使用 Cglib 进行代理

用户头像

echoes

关注

探索未知,分享收获 2018.04.25 加入

还未添加个人简介

评论

发布
暂无评论
Spring之JDK动态代理与CGLIB动态代理_echoes_InfoQ写作社区