实现原理区别
通过反编译 JDK 的代理类和 CGLIB 的代理类,可以比较两种不同的实现机制:
目标类:
JDK 动态代理是通过实现目标类的接口,然后将目标类在构造动态代理时作为参数传入,使代理对象持有目标对象,再通过代理对象的 InvocationHandler 实现动态代理的操作。
CGLIB 动态代理是通过配置目标类信息,然后利用 ASM 字节码框架进行生成目标类的子类。当调用代理方法时,通过拦截方法的方式实现代理的操作。
总的来说: JDK 动态代理利用接口实现代理,只能代理实现了接口的类,代理对象是利用反射机制动态生成;CGLIB 动态代理利用继承的方式实现代理,可以代理未实现任何接口的类,代理对象是利用拦截机制动态生成。
一、JDK 动态代理
利用 JDK 的 API(利用反射机制),动态的在内存中构建代理对象,且代理类必须实现 InvocationHandler 接口,同时目标对象要实现接口,否则不能用动态代理。在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。
1、JDK 动态代理类使用步骤
定义一个接口及其实现类(被代理对象);
自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
通过 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 动态代理类使用步骤
定义一个类;
自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
通过 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 进行代理
评论