代理

代理

第一节 静态代理

1.1 简介

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

1.2 结构

  Subject 是定义 RealSubjectProxy 的公共接口。

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(){
...
}
}
// Proxy保存实体的引用,使代理可以访问实体,并实现Subject接口,所以代理可以代替实体工作
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 步骤

  动态代理步骤:

  1. 获取 RealSubject上的所有接口列表;
  2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX
  3. 根据需要实现的接口信息,在代码中动态创建该 Proxy 类的字节码;
  4. 将对应的字节码转换为对应的 Class 对象;
  5. 创建 InvocationHandler 实例 handler,用来处理 Proxy 所有方法调用;
  6. ProxyClass 对象以创建的 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 };

// 已设置flag的代理构造函数的缓存
private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();

// 此代理实例的调用处理程序
protected InvocationHandler h;

// 创建代理对象,需要loader类加载器,interfaces对象数组表示要代理的对象需要实现的接口,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
/**
* 1. 订单服务-委托类,不需要再实现接口
*
* @author cruder
* @date 2019-11-23 15:42
**/
public class OrderService {
/**
* 保存订单接口
*/
public void saveOrder(String orderInfo) throws InterruptedException {
// 随机休眠,模拟订单保存需要的时间
Thread.sleep(System.currentTimeMillis() & 100);
System.out.println("订单:" + orderInfo + " 保存成功");
}
}

/**
* cglib动态代理工厂
*
* @author cruder
* @date 2019-11-23 18:36
**/
public class ProxyFactory implements MethodInterceptor {

/**
* 委托对象, 即被代理对象
*/
private Object target;

public ProxyFactory(Object target) {
this.target = target;
}

/**
* 返回一个代理对象
* @return
*/
public Object getProxyInstance(){
// 1. 创建一个工具类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(target.getClass());
// 3. 设置回调函数
enhancer.setCallback(this);
// 4.创建子类对象,即代理对象
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
/**
* 使用cglib代理类来保存订单
*
* @author cruder
* @date 2019-11-23 15:49
**/
public class Client {
public static void main(String[] args) throws InterruptedException {
// 1. 创建委托对象
OrderService orderService = new OrderService();
// 2. 获取代理对象
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及其相似,可以分为以下几个步骤:

  1. 创建一个实现MethodInterceptor接口的类
  2. 在类中定义一个被代理对象的成员属性,为了扩展方便可以直接使用Object类,也可以根据需求定义相应的接口
  3. 在invoke方法中实现对委托对象的调用,根据需求对方法进行增强
  4. 使用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
// 生成一个Proxy的子类
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 动态代理

CGLIBJDK动态代理 区别:

  • JDK动态代理是面向接口的

  • CGLib则是通过字节码底层继承要代理的类来实现

  • JDK Proxy 的优势:

    • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 CGLib 更加可靠。
    • 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
    • 代码实现简单。
  • CGLib 优势:

    • 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的实践,类似 CGLib 动态代理就没有这种限制。
    • 只操作我们关心的类,而不必为其他相关类增加工作量。

参考博客和文章书籍等:

《Java核心技术 卷Ⅰ》

深入理解 Java 反射和动态代理

Java 动态代理作用是什么

面试填坑笔记-从代理模式到SpringAOP的动态代理

CGLIB

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容