代理
第一节 静态代理
1.1 简介
静态代理则是指设计模式中的代理模式,此模式为其它对象提供一种代理来控制某个对象的访问。

1.2 结构
Subject
是定义 RealSubject
和 Proxy
的公共接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| interface Subject { public abstract void request(); }
class RealSubject implements Subject { @Override public void request(){ ... } }
class Proxy implements Subject { private RealSubject rs;
@Override public void request(){ if(null == real) real = new RealSubject(); real.request(); } }
|
代理对象就像是实体对象的替身一样,替身的作用就是帮本体完成其不能或没时间完成的事务。
静态代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于 Proxy 和 RealSubject 的功能本质上是相同的,Proxy 只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。
静态代理的优势在于增强接口业务功能,可以不改变目标实体的同时对功能进行扩展。
缺点就是代码太冗余,接口的实现类太多,代码量成倍增加,导致维护成本增加。
第二节 动态代理
2.1 简介
动态代理解决了静态代理的缺点,就是即用即处理,在需要代理的阶段动态的创建代理对象,在使用之后即销毁。

Java的代理模式是通过实现接口来使代理类完成目标类的操作,另一种设计就是直接继承目标类。
2.2 场景
动态代理是反射机制的一个应用场景,常用来实现一些框架,如Spring的AOP、Struts2中的拦截器、Dubbo的SPI接口。
2.3 步骤
动态代理步骤:
- 获取
RealSubject
上的所有接口列表;
- 确定要生成的代理类的类名,默认为:
com.sun.proxy.$ProxyXXXX
;
- 根据需要实现的接口信息,在代码中动态创建该
Proxy
类的字节码;
- 将对应的字节码转换为对应的
Class
对象;
- 创建
InvocationHandler
实例 handler,用来处理 Proxy
所有方法调用;
Proxy
的 Class
对象以创建的 handler 对象为参数,实例化一个 proxy 对象。
2.4 Proxy和InvocationHandler
功能:利用代理可以在运行时创建一个实现了一组给定接口的新类。
场景:编译时无法确定需要实现哪个接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
public class Proxy implements java.io.Serializable { private static final Class<?>[] constructorParams = { InvocationHandler.class };
private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();
protected InvocationHandler h;
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { Objects.requireNonNull(h); final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass(); Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); return newProxyInstance(caller, cons, h); }
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager Constructor<?> cons, InvocationHandler h) { try { if (caller != null) { checkNewProxyPermission(caller, cons.getDeclaringClass()); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException | InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } } }
|
第三节 自定义代理
1. 定义TraceHandler存储包装的对象,invoke方法中打印方法和参数,当elements[i]调用某个方法时,会调用invoke打印方法和参数,再用value调用方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class TraceHandler implements InvocationHandler { private Object target;
public TraceHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ System.out.print(target); System.out.print("." + method.getName() + "("); if(args != null){ for(int i = 0;i < args.length;i++) { System.out.print(args[i]); if(i < args.length - 1) System.out.print(", "); } } System.out.println(")"); return method.invoke(target,args); } }
public static void main(String[] args){ Object[] elements = new Object[1000]; for(int i = 0;i < elements.length;i++){ Integer value = i+1; elements[i] = Proxy.newProxyInstance( null, new Class[]{Comparable.class},new TraceHandler(value)); }
Integer key = new Random().nextInt(elements.length) + 1; int result = Arrays.binarySearch(elements,key); if(result > 0) System.out.println(elements[result]); }
|
结果如下,可以明显的看到二分查找的过程。
1 2 3 4 5 6 7 8 9 10 11 12
| 500.compareTo(223) 250.compareTo(223) 125.compareTo(223) 187.compareTo(223) 218.compareTo(223) 234.compareTo(223) 226.compareTo(223) 222.compareTo(223) 224.compareTo(223) 223.compareTo(223) 223.toString() 223
|
2. 通过简单的代码来测试实现动态代理的转发过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public interface Subject { void print(); }
public class RealSubject implements Subject { @Override public void print() { System.out.println("-真实实体-"); } }
public class ProxyHandler implements InvocationHandler { private Object subject; public ProxyHandler(Object subject) { this.subject = subject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-由代理接收-"); System.out.println("proxy: " + proxy.getClass().getName()); System.out.println("method: " + method); Object result = method.invoke(subject,args); System.out.println("-由代理转发-"); return result; } }
public class Client { public static void main(String[] args) { Subject realSubject = new RealSubject(); InvocationHandler handler = new ProxyHandler(realSubject); Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler); System.out.println("代理类名: " + subject.getClass().getName()); subject.print(); } }
|
输出结果如下:
1 2 3 4 5 6
| 代理对象名: com.sun.proxy.$Proxy0 -由代理接收- proxy: com.sun.proxy.$Proxy0 method: public abstract void proxy.Subject.print() -真实实体- -由代理转发-
|
代理对象是在运行时创建在JVM中的对象,命名格式就如上com.sun.proxy.$Proxy+X。
特性
- 代理类一旦在运行中被创建,就和常规类无异
- 所有代理类都扩展于Proxy类,一个代理类对应一个实例域——调用处理器
- 所有代理类都覆盖了Object中的toString,equals和hashCode,调用这些方法就类似于其它代理方法
- 特定的类加载器和一组接口,只能有一个代理类,多次调用newProxyInstance只会得到同代理类的多个对象,getProxyClass可以获得这个代理类
- 代理类一定public和final,代理类所实现的接口若非public就必须属于同一个包,此时代理类也属于该包
第四节 应用场景
4.1 AOP
- 当bean的是实现中存在接口或者是Proxy的子类,采用JDK动态代理;不存在接口,spring会采用CGLIB来生成代理对象;
- JDK动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。
- Proxy 利用 InvocationHandler(定义横切逻辑) 接口动态创建 目标类的代理对象。
4.2 JDK动态代理
- 通过bind方法建立代理与真实对象关系,通过Proxy.newProxyInstance(target)生成代理对象。
- 代理对象通过反射invoke方法实现调用真实对象的方法。
4.3 CGLib
4.3.1 什么是CGLib?
CGLib是Java字节码生成和转换类库。经常用于AOP、测试、数据访问框架、生成动态代理对象和拦截字段访问。基于 ASM
生成新的字节码,使用 Enhancer
增强器可以为非接口类型生成Java代理。
CGLib动态代理和JDK动态代理类似,也是采用操作字节码机制,在运行时生成代理类。CGLib 动态代理采取的是创建目标类的子类的方式(通过继承实现),因为是子类化,我们可以达到近似使用被调用者本身的效果。
字节码处理机制-指得是ASM来转换字节码并生成新的类。
注:Spring中有完整的CGLib相关的依赖,所以以下代码基于Spring官方下载的demo中直接进行编写的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
public class OrderService {
public void saveOrder(String orderInfo) throws InterruptedException { Thread.sleep(System.currentTimeMillis() & 100); System.out.println("订单:" + orderInfo + " 保存成功"); } }
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target) { this.target = target; }
public Object getProxyInstance(){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("cglib代理:保存订单用时: " + (System.currentTimeMillis() - start) + "ms"); return result; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public class Client { public static void main(String[] args) throws InterruptedException { OrderService orderService = new OrderService(); OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance(); String saveFileName = "CglibOrderServiceDynamicProxy.class"; ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class}); orderServiceProxy.saveOrder(" cruder 新买的花裤衩 "); } }
|
CGLib动态代理实现步骤和JDK及其相似,可以分为以下几个步骤:
- 创建一个实现MethodInterceptor接口的类
- 在类中定义一个被代理对象的成员属性,为了扩展方便可以直接使用Object类,也可以根据需求定义相应的接口
- 在invoke方法中实现对委托对象的调用,根据需求对方法进行增强
- 使用Enhancer创建生成代理对象,并提供要给获取代理对象的方法
CGLib动态代理生成的代理类和JDK动态代理代码格式上几乎没有什么区别,唯一的区别在于CGLib生成的代理类继承了仅仅Proxy类,而JDK动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public final class OrderService extends Proxy { private static Method m1; private static Method m2; private static Method m0;
public OrderService(InvocationHandler var1) throws { super(var1); }
public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
|
4.3.2 CGLib 应用场景
- Java工具:字节码工具允许在Java应用程序的编译阶段后操作或创建类,即运行时创建。
- Java框架:如Spring和Hibernate,Spring AOP中默认使用JDK动态代理,而Spring Boot 在2.X版本后为了解决JDK动态代理导致的类型转化异常而默认使用 CGLib。Hibernate不是直接返回存储在数据库中的对象,而是生成一份存储类的工具版,并在请求时延迟加载部分值。
- Mock框架
4.3.3 CGLib 与 JDK 动态代理
CGLIB
与 JDK动态代理
区别:
JDK动态代理是面向接口的
CGLib则是通过字节码底层继承要代理的类来实现
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 CGLib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
CGLib 优势:
- 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 CGLib 动态代理就没有这种限制。
- 只操作我们关心的类,而不必为其他相关类增加工作量。
参考博客和文章书籍等:
《Java核心技术 卷Ⅰ》
深入理解 Java 反射和动态代理
Java 动态代理作用是什么
面试填坑笔记-从代理模式到SpringAOP的动态代理
CGLIB
因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容