概述
动态代理和静态代理都是代理模式的实现方式,其主要区别在于代理类生成的时机和方式。
静态代理是在编译时就确定了代理类的代码,在程序运行前就已经存在了代理类的class文件。代理类与委托类的关系在编译时就已经确定,因此被称为静态代理。在静态代理中,代理类需要实现与委托类相同的接口或者继承委托类的父类,以便能够对委托类进行代理操作。
动态代理是在程序运行时生成代理类的代码,代理类通过反射机制动态生成。动态代理不需要像静态代理一样实现与委托类相同的接口或继承委托类的父类,它可以实现任意接口。动态代理的优势在于,在代理类和委托类之间增加一个代理对象,可以更加灵活地控制委托类的行为。同时,也可以使得代码更加通用化,减少代码的重复性。
总体来说,静态代理只能代理一个委托类,而动态代理可以代理多个委托类。但是,由于动态代理的实现机制比较复杂,所以相比静态代理,动态代理的性能开销更大。而静态代理虽然只能代理一个委托类,但是因为它是在编译时生成代码,所以相对来说执行效率更高。
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。
DK动态代理技术:只能代理接口。
CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
JDK动态代理
一个接口和一个实现类
接口类==>jdk动态代理必须要有一个接口类
Proxy类全名:java.lang.reflect.Proxy。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
其中newProxyInstance()方法有三个参数:
- 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
- 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
- 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
invoke 方法,用户调用代理对象的什么方法,实质上都是在调用处理器的
invoke 方法,通过该方法调用目标方法,它也有三个参数:
public Object invoke(Object proxy, Method method, Object[] args)
- 第一个参数为 Proxy 类类型实例,如匿名的 $proxy 实例
- 第二个参数为委托类的方法对象
- 第三个参数为委托类的方法参数
- 返回类型为委托类某个方法的执行结果
orderService
1. public interface orderService { 2. public void order(); 3. }
orderServiceImp
1. public class orderServiceImp implements orderService{ 2. @Override 3. public void order() { 4. System.out.println("order...."); 5. } 6. }
proxyOrderService
1. public class proxyOrderService { 2. public static void main(String[] args) { 3. orderServiceImp orderServiceImp = new orderServiceImp(); 4. orderService o = (orderService)Proxy.newProxyInstance(orderServiceImp.class.getClassLoader(), 5. orderServiceImp.class.getInterfaces(), new InvocationHandler() { 6. @Override 7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 8. orderServiceImp.order(); 9. return null; 10. } 11. } 12. ); 13. o.order(); 14. } 15. }
代码可以封装为以下:
1. public class proxy implements InvocationHandler { 2. 3. private orderService orderService; 4. 5. public proxy(orderService orderService) { 6. this.orderService = orderService; 7. } 8. 9. @Override 10. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 11. System.out.println("proxy"); 12. method.invoke(orderService,args); 13. return null; 14. } 15. }
1. public class proxyOrderService { 2. public static void main(String[] args) { 3. orderServiceImp orderServiceImp = new orderServiceImp(); 4. orderService o = (orderService)Proxy.newProxyInstance(orderServiceImp.class.getClassLoader(), 5. orderServiceImp.class.getInterfaces(), new proxy(orderServiceImp) 6. ); 7. o.order(); 8. } 9. }
cglb动态代理
CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor
使用CGLIB,需要引入它的依赖:
1. <dependency> 2. <groupId>cglib</groupId> 3. <artifactId>cglib</artifactId> 4. <version>3.3.0</version> 5. </dependency>
准备一个接口(cglb可以不用接口哈),一个类
1. public interface orderService { 2. public void order(); 3. }
1. public class orderServiceImp implements orderService { 2. @Override 3. public void order() { 4. System.out.println("order...."); 5. } 6. }
1. public class test { 2. public static void main(String[] args) { 3. Enhancer enhancer = new Enhancer(); 4. orderServiceImp orderServiceImp = new orderServiceImp(); 5. enhancer.setSuperclass(orderServiceImp.getClass()); 6. enhancer.setCallback(new MethodInterceptor() { 7. @Override 8. public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 9. Object invoke = method.invoke(target, objects); 10. return invoke; 11. } 12. }); 13. orderServiceImp o =(orderServiceImp)enhancer.create(); 14. orderServiceImp.order(); 15. } 16. }
MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
第一个参数:代理对象
第二个参数:目标方法
第三个参数:目标方法调用时的实参
第四个参数:代理方法
对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
- --add-opens java.base/java.lang=ALL-UNNAMED
- --add-opens java.base/sun.net.util=ALL-UNNAMED
CGLIB使用步骤:
1、创建字节码增强器
2、设置目标对象
3、设置回调函数
4、创建代理对象
5、使用代理对象