静动态代理

11/16/2021 面试题JavaSpring

摘要

示例代码:proxy-demo (opens new window)

# 一:代理

什么是代理?

为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间

# 二:静态代理

# 2.1 什么是静态代理?

由程序创建或特定工具自动生成源代码,在程序运行前,代理类的 .class 文件就已经存在。即编译时期确定类。

通过将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的。

# 2.2 优缺点

优点:

  • 代理使客户端不需要知道实现类是什么,怎么做,而客户端只需知道代理即可;
  • 方便增加功能,扩展业务逻辑。

缺点:

  • 代理类中常出现大量冗余的代码,非常不利于扩展和维护;
  • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度

# 2.3 案例演示

接口:

/**
 * 接口
 */
public interface PayService {

    /**
     * 支付回调
     *
     * @param outTradeNo 订单号
     * @return outTradeNo
     */
    String callback(String outTradeNo);

    /**
     * 下单
     *
     * @param userId    用户id
     * @param productId 产品id
     * @return productId
     */
    int save(int userId, int productId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

接口实现类

/**
 * 接口实现类
 */
public class PayServiceImpl implements PayService {

    @Override
    public String callback(String outTradeNo) {
        System.out.println("目标类 PayServiceImpl 回调 方法 callback");
        return outTradeNo;
    }

    @Override
    public int save(int userId, int productId) {
        System.out.println("目标类 PayServiceImpl 回调 方法 save");
        return productId;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

静态代理:

/**
 * 接口实现类,静态代理
 */
public class StaticProxyPayServiceImpl implements PayService {

    /**
     * 目标类
     */
    private PayService payService;

    public StaticProxyPayServiceImpl(PayService payService) {
        this.payService = payService;
    }

    @Override
    public String callback(String outTradeNo) {
        System.out.println("StaticProxyPayServiceImpl callback begin");
        String result = payService.callback(outTradeNo);
        System.out.println("StaticProxyPayServiceImpl callback end");
        return result;
    }

    @Override
    public int save(int userId, int productId) {
        System.out.println("StaticProxyPayServiceImpl save begin");
        int id = payService.save(userId, productId);
        System.out.println("StaticProxyPayServiceImpl save end");
        return id;
    }
}
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

测试:

public class ProxyTest {

    public static void main(String[] args) {
        PayService payService = new StaticProxyPayServiceImpl(new PayServiceImpl());
        payService.save(234, 567);
    }

}
1
2
3
4
5
6
7
8

运行结果:

# 三:动态代理

在程序运行时,运用反射机制动态创建而成,无需手动编写代码。

动态代理的应用场景有很多,最常见的就是 AOP 的实现、RPC 远程调用、Java 注解对象获取、日志框架、全局性异常处理、事务处理等。

动态代理分两种:

  1. JDK动态代理
  2. CGLIB动态代理(原理:是对指定的业务类生成一个子类,并覆盖其中的业务方法来实现代理)

# 3.1 JDK 动态代理

JDK的动态代理的写法比较固定:

  1. 需要先定义一个接口和接口的实现类
  2. 然后再定义一个类,去实现 InvocationHandler 这个接口,并重写 invoke方法
  3. 然后调用 Proxy 类的 newInstance() 方法即可
/**
 * InvocationHandler是由代理实例的调用处理程序实现的接口。
 * 每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,方法调用将被编码并发送到其调用处理程序的invoke方法。
 */
public interface InvocationHandler {
    /**
     * 在代理实例上处理方法调用并返回结果。当在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
     * @param proxy       被代理的对象
     * @param method   要调用的方法
     * @param args         方法调用时所需参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

jdk动态代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * jdk动态代理类
 */
public class JDKProxy implements InvocationHandler {

    /**
     * 目标类
     */
    private Object targetObject;

    /**
     * 获取代理对象
     *
     * @param targetObject 目标类
     * @return 获取代理
     */
    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        // 绑定关系,也就是和具体的那个实现类关联
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), // 指明一个类加载器,为了操作class文件
                // 为代理对象指定要是实现哪些接口,这里要为PayServiceImpl这个目标对象创建动态代理,所以需要为代理对象指定实现PayService接口
                // 因为知道具体的,所以下面可以直接替换为 new Class[]{PayService.class}
                targetObject.getClass().getInterfaces(),
                this);
    }

    /**
     * JDK动态代理
     *
     * @param proxy  静态代理对象
     * @param method 要调用的方法
     * @param args   方法调用时所需要参数
     * @return 结果
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        Object result = null;
        try {
            System.out.println("通过JDK动态代理调用" + method.getName() + ",打印日志 begin");
            result = method.invoke(targetObject, args);
            System.out.println("通过JDK动态代理调用" + method.getName() + ",打印日志 end");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
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

测试:

public class ProxyTest {

    public static void main(String[] args) {
        // JDK 动态代理
        JDKProxy jdkProxy = new JDKProxy();
        // 获取代理对象
        PayService payServiceProxy = (PayService) jdkProxy.newProxyInstance(new PayServiceImpl());
        // 调用目标方法
        payServiceProxy.callback("hello world");
        // 调用目标方法
        payServiceProxy.save(2, 2);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

运行结果:

# 3.2 CGLIB 动态代理

CGLIB动态代理类:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGLIB动态代理类
 */
public class CGLIBProxy implements MethodInterceptor {

    /*
     * 目标类
     */
    private Object targetObject;

    // 绑定关系
    public Object newProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        Enhancer enhancer = new Enhancer();
        // 设置代理类的父类(目标类)
        enhancer.setSuperclass(this.targetObject.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        try {
            System.out.println("通过CGLIB动态代理调用" + method.getName() + ",打印日志 begin");
            result = methodProxy.invokeSuper(o, args);
            System.out.println("通过CGLIB动态代理调用" + method.getName() + ",打印日志 end");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
}
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

测试:

public class ProxyTest {

    public static void main(String[] args) {
        // CGLIB 动态代理
        CGLIBProxy cglibProxy = new CGLIBProxy();
        PayService payService = (PayService) cglibProxy.newProxyInstance(new PayServiceImpl());

        // 调用目标方法
        payService.callback("hello world");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12

运行结果:

# 四:小结

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理,解耦和易维护。

# 4.1 两种动态代理的区别

  1. JDK动态代理:要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用CGLIB动态代理;
  2. JDK动态代理是自带的,CGLIB需要引入第三方包;
  3. CGLIB动态代理,它是内存中构建一个子类对象从而实现对目标对象功能的扩展;
  4. CGLIB动态代理基于继承来实现代理,所以无法对final类,private方法和static方法实现代理

# 4.2 Spring AOP中的代理使用的默认策略

  • 如果目标对象实现类接口,则默认采用JDK动态代理
  • 如果目标对象没有实现接口,则采用CGLIB进行动态代理

# 五:深入JDK动态代理

# 5.1 查看代理类的class文件

看源文件.java或者借助反编译工具反编译.class文件后去分析源码。可是对于JDK的动态代理,它产生的代理对象是在运行时创建的,通过常规操作,我们没办法得到这个代理对象对应的.class文件。如果能得到代理对象对应的class文件,那动态代理的原理,就比较好分析。

先看看下面一段 Proxy 的源码






 
 


















 

































public class Proxy implements java.io.Serializable {

	/**
	 * 缓存代理类
	 */
	private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
		proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

	/**
	 * 一个工厂函数,它在给定 ClassLoader 和接口数组的情况下生成、定义和返回代理类
	 */
	private static final class ProxyClassFactory
		implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
		
		// 所有代理类的前缀
		private static final String proxyClassNamePrefix = "$Proxy";
		
		// 用来生成唯一类名的数字
		private static final AtomicLong nextUniqueNumber = new AtomicLong();
		
		@Override
		public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
			// ... ...
			
			// 生成特定的代理类
			byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
			
			// ... ...
		}
	}
	
	@CallerSensitive
	public static Object newProxyInstance(ClassLoader loader,
			Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
		// ... ...
		
		// 查找或生成指定的代理类
		Class<?> cl = getProxyClass0(loader, intfs);
		
		// ... ...
	}
	
	/**
	 * 生成代理类
	 * 在调用它之前必须调用 checkProxyAccess 方法来执行权限检查
	 */
	private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
		if (interfaces.length > 65535) {
			throw new IllegalArgumentException("interface limit exceeded");
		}
		
		// 如果给定加载器定义的代理类实现给定接口存在,这将简单地返回缓存的副本;
		// 否则,它将通过 ProxyClassFactory 创建代理类
		// 通过上面代码可知,为WeakCache类,初始化时把 ProxyClassFactory 封装进去
		return proxyClassCache.get(loader, interfaces);
	}
	
}
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

再来看看 WeakCache<K, P, V> 的源码








 






final class WeakCache<K, P, V> {

	public V get(K key, P parameter) {
		// ... ...
		
		// 创建 subKey 并检索其存储的可能的 Supplier<V>
		// 结合 Proxy的代码,可以发现,此处调用 ProxyClassFactory的apply()方法
		Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
		
		// ... ...
	}
	
}
1
2
3
4
5
6
7
8
9
10
11
12
13

如果有兴趣的,可以详细看看 Proxy.newProxyInstsance 源码,其实就做了下面几件事,用个流程图说明:

通过上面的代码,可以看出此时的调用链为:

Proxy.newProxyInstance
	-->getProxyClass0
	-->proxyClassCache.get(loader, interfaces)
	-->subKeyFactory.apply(key, parameter)
	-->ProxyClassFactory.apply
	-->ProxyGenerator.generateProxyClass
1
2
3
4
5
6

ProxyGenerator.generateProxyClass() 方法的作用就是生成代理对象的class文件,返回值是一个byte[]数组

方法一:手动写入磁盘

把 ProxyGenerator.generateProxyClass() 返回的 byte[]数组通过输出流的方式,将内容写入到磁盘。

import sun.misc.ProxyGenerator;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Modifier;

/**
 * 手动写入磁盘
 */
public class ManualTest {
    public static void main(String[] args) throws IOException {
        String proxyName = "top.qform.dynamic.$Proxy0";
        Class[] interfaces = new Class[]{PayService.class};
        int accessFlags = Modifier.PUBLIC;
        // 将字节数组写到磁盘
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
        File file = new File("D:\\ccj\\$Proxy0.class");
        OutputStream outputStream = new FileOutputStream(file);
        outputStream.write(proxyClassFile);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

运行结果:

运行完main()方法后,从 D:\ccj\$Proxy0.class 找到生成的文件,由于是一个class文件,所以我们需要把它有反编译器编译一下,例如:在idea

方法二:自动写入磁盘

在代码中添加一行代码:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"),就能实现将程序运行过程中产生的动态代理对象的class文件写入到磁盘。

运行结果:

运行程序,最终发现在项目的根目录下出现了一个包:com.sun.proxy。包下有一个文件$Proxy0.class

# 5.2 解析

通过上面两种方法获取到代理对象的源代码如下:

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import top.qform.PayService;

public final class $Proxy0 extends Proxy implements PayService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m0;

    public $Proxy0(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 save(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final String callback(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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");
            m3 = Class.forName("top.qform.PayService").getMethod("save", Integer.TYPE, Integer.TYPE);
            m4 = Class.forName("top.qform.PayService").getMethod("callback", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

通过源码可以发现,$Proxy0 类继承了Proxy类,同时实现了PayService接口。

所以JDK的动态代理只能基于接口实现,不能基于继承来实现。因为Java中不支持多继承,而JDK的动态代理在创建代理对象时,默认让代理对象继承了Proxy类,所以JDK只能通过接口去实现动态代理。

$Proxy0 实现了Proxy接口,所以重写了接口中的两个方法($Proxy0 同时还重写了Object类中的几个方法)。所以当我们调用save()方法时,先是调用到 $Proxy0.save() 方法,在这个方法中,直接调用了 super.h.invoke() 方法,父类是Proxy,父类中的h就是我们定义的InvocationHandler,所以这儿会调用到 JDKProxy.invoke() 方法。因此当我们通过代理对象去执行目标对象的方法时,会先经过 InvocationHandler的invoke() 方法,然后在通过反射 method.invoke() 去调用目标对象的方法,

那么这时候有另外一个问题,为什么要重写equalstoStringhashCode方法?

从源码中可以看到,这三个方法实际是调用了InvocationHandler接口实现类的相应方法。而按照理解动态代理类其实相当于一个中间件,通过动态代理类我们实际想要调用的是被代理类的方法,这么一想就很好理解了——重写这三个方法的原因是为了让动态代理类与被代理类划上”≈“号。

如果没有重写这三个方法,那么它们的hashcodetoString将会返回不同值,这样实现的动态代理类也就不完善了。

为什么说是”≈“号而不是”=“号呢?因为动态代理类实际是一个com.sun.proxy.$Proxy0类,虽然它具有与被代理类相同的状态(包括大部分方法与属性),但实际上这两个类通过equals方法来比较返回的会是false,因为它们的内存地址是不一样的。

被代理类未重写equals方法,所以调用的是Object#equals,而这里比较的是内存地址。

# 5.4 为什么继承Proxy

我们可以发现代理类并没有使用Proxy中的什么属性或者方法(虽然使用了InvocationHandler对象,但是也可以在生成class之初就将InvocationHandler放入到代理类中)

不妨假设所有的代理类都不继承这个类,那么会怎么样呢?

这样的话,代理类就要变成这样子

public class $Proxy0 implements PayService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m0;
    
    // 这个时候代理类就需要自己持有一个InvocationHandler对象了
    private InvocationHandler h;
	public $Proxy0(InvocationHandler var1) throws  {
        // super(var1);
        this.h = h;
    }
    
    // ... ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这样确实还是能实现代理类的所有功能,但是会发现,在生成每一个代理类的时候都需要往生成的字节码中写入这么一段。相比于继承的方式这种形式写入的内容要多一些,这也意味着消耗的性能要高一些。

所以基于此可以总结出第一个原因:

基于继承的方式可以减少生成代理类时产生的性能消耗

如果仅仅基于上面的原因没办法说服你的话,那么不要急,继续往下看,思考这么一种场景,如果有一天需要对所有的代理类进行某一种处理的话,该如何知道这个类是一个经过代理产生的类呢?

如果是基于上面这种方式的话,能想到的办法只有一种

  1. 遍历所有的类
  2. 判断该类中是否持有一个InvocationHandler对象

但是难道只有代理类能持有一个InvocationHandler吗?很显然不是的,我们也可以自己定义一个类,也能持有一个InvocationHandler。

但是如果通过继承Proxy的方式的话,我们只需要

  1. 遍历所有类
  2. 判断它是否是一个Proxy(instance of Prxoy)

这样就能完成我们的需求

最后我觉得Jdk自所以要这么进行实现,是因为它将所有的代理类进行了一层抽象,为所有的代理类定义了一个父类。所有的代理类都有一个共同点:持有一个InvocationHandler。所以基于此,抽象出一个父类Proxy。同时又由于JDK的动态代理就是基于接口代理来设计的,继承一个父类并没有违背它设计的初衷。因此Proxy就作为所有代理类的父类诞生了!!!

# 5.5 this

新增 PayService 的一个实现类:

public class PayServiceImpl2 implements PayService {

    @Override
    public String callback(String outTradeNo) {
        System.out.println("目标类 PayServiceImpl 回调 方法 callback");
        save(2, 2);
        return outTradeNo;
    }

    @Override
    public int save(int userId, int productId) {
        System.out.println("目标类 PayServiceImpl 回调 方法 save");
        return productId;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

测试类:

public class ProxyTest2 {

    public static void main(String[] args) {
        JDKProxy jdkProxy = new JDKProxy();
        PayService payServiceProxy = (PayService) jdkProxy.newProxyInstance(new PayServiceImpl2());
        payServiceProxy.callback("hello world ==> 内部调用 save() 方法");
    }

}
1
2
3
4
5
6
7
8
9

按照我们的理解$Proxy0代理类对 PayServiceImpl2 中的方法都进行了增强,每次调用 PayServiceImpl2 中类的方法时,应该都会经过JDKProxy中的invoke()方法,每次都会打印相关的语句。所以当我们调用payServiceProxy.callback()时,打印结果应该是:

通过JDK动态代理调用callback,打印日志 begin
目标类 PayServiceImpl 回调 方法 callback
通过JDK动态代理调用callback,打印日志 end
通过JDK动态代理调用callback,打印日志 begin
目标类 PayServiceImpl 回调 方法 save
通过JDK动态代理调用callback,打印日志 end
1
2
3
4
5
6

然而实际的运行结果:

从结果中,可以发现,只打印了一次JDKProxy.invoke()里面的语句。在调用save()方法时,并没有去执行InvocationHandler中的invoke()方法。为什么呢?原因就在这个地方:



 



public String callback(String outTradeNo) {
    System.out.println("目标类 PayServiceImpl 回调 方法 callback");
    this.save(2, 2);
    return outTradeNo;
}
1
2
3
4
5

在callback() 方法中调用 save() 方法时,实际上调用的是 this.save(),而此时this对象是PayServiceImpl2,并不是$Proxy0这个代理对象,只有在调用代理对象的save()方法时,才会经过InvocationHandler.invoke()方法,所以此时只会打印一次输出语句。

# 六:参考文献

最后更新: 2/6/2023, 8:44:43 PM