JDK动态代理

ccb8837a683f140a6777c36879fbda68

当疯狂慢慢从爱情离开

前言

什么是代理 (模式)?

代理模式 (Proxy Pattern) 也称委托模式 (Deletage Pattern),属于结构型设计模式,也是一项基本的设计技巧。通常,代理模式用于处理两种问题:

  • 控制对基础对象的访问
  • 在访问基础对象时增加额外功能

代理的基本分类: 静态代理 + 动态代理,分类的标准是 “代理关系是否在编译期确定;

动态代理的实现方式: JDK、CGLIB、Javassist、ASM

静态代理

定义

静态代理是指代理关系在编译期确定的代理模式。使用静态代理时,通常的做法是为每个业务类抽象一个接口,对应地创建一个代理类。

20180727232308293

Cilent调用Source的method()方法,实际上是Proxy来调用method()方法,静态代理中Source跟Proxy都要实现接口Sourceable。

看一个例子

父接口 Animal.java

1
2
3
4
5
6
package Proxy;

public interface Animal {
public void action();
public void breath();
}

Cat类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package Proxy;


public class Cat implements Animal{

@Override
public void action() {
System.out.println("喵喵喵~~~~");
}

@Override
public void breath() {
System.out.println("猫式呼吸法~~~~");
}
}

CatProxy类

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
package Proxy;

//代理类
public class CatProxy implements Animal{

//真正要代理的类
Cat cat;

public CatProxy(Cat cat){
this.cat = cat;
}

@Override
public void action() {
System.out.println("==========DogProxy 代理类执行开始!=============");
//实质上在代理类中是调用了被代理实现接口的方法
cat.action();
System.out.println("==========DogProxy 代理类执行结束!===========");
}

@Override
public void breath() {
System.out.println("==========DogProxy 代理类执行开始!=============");
cat.breath();
System.out.println("==========DogProxy 代理类执行结束!===========");
}

}

如果我想再创建一个Dog对象,又需要重新为Dog创建一个代理对象,如下:

Dog.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Proxy;

//被代理类 Dog
public class Dog implements Animal {

@Override
public void action() {
System.out.println("汪汪汪~~~~~");
}

@Override
public void breath() {
System.out.println("狗式呼吸法~~~~");
}

}

DogProxy类

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
package Proxy;

//Dog的代理类
public class DogProxy implements Animal {

Dog dog;

public DogProxy(Dog dog) {
this.dog = dog;
}

@Override
public void action() {
System.out.println("==========DogProxy 代理类执行开始!=============");
dog.action();
System.out.println("==========DogProxy 代理类执行结束!===========");
}

@Override
public void breath() {
System.out.println("==========DogProxy 代理类执行开始!=============");
dog.breath();
System.out.println("==========DogProxy 代理类执行结束!===========");
}

}

TestStaticProxy.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package Proxy;

public class TestStaticProxy {

public static void main(String[] args) {
Cat cat = new Cat();
CatProxy catProxy = new CatProxy(cat);
catProxy.action();
catProxy.breath();

Dog dog = new Dog();
DogProxy dogProxy = new DogProxy(dog);
dogProxy.action();
dogProxy.breath();
}
}

运行如下:

image-20240217215426303

每次我要新加入一个实现Animal接口的对象的话,都要重新创建一个代理对象,这样会非常的麻烦,这其实是静态代理的缺点。

动态代理

动态代理有什么作用及应用场景?

  • 日志集中打印
  • 事务
  • 权限管理
  • AOP

定义

动态代理是指代理关系在运行时确定的代理模式。需要注意,JDK 动态代理并不等价于动态代理,前者只是动态代理的实现之一,其它实现方案还有:CGLIB 动态代理、Javassist 动态代理和 ASM 动态代理等。因为代理类在编译前不存在,代理关系到运行时才能确定,因此称为动态代理。

JDK动态代理基于拦截器和反射来实现。

JDK代理是不需要第三方库支持的,只需要JDK环境就可以进行代理,使用条件:

1)必须实现InvocationHandler接口;

2)使用Proxy.newProxyInstance产生代理对象;

3)被代理的对象必须要实现接口;

JDK的动态代理需要了解两个类

核心 : InvocationHandler 调用处理程序类和 Proxy 代理类

InvocationHandler:调用处理程序

1
public interface InvocationHandler

InvocationHandler是由代理实例的调用处理程序实现的接口

每个代理实例都有一个关联的调用处理程序。

1
Object invoke(Object proxy, 方法 method, Object[] args);

当在代理实例上调用方法的时候,方法调用将被编码并分派到其调用处理程序的invoke()方法。

参数

  • proxy– 调用该方法的代理实例
  • method-所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
  • args-包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integerjava.lang.Boolean

Proxy : 代理

1
public class Proxy extends Object implements Serializable

Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。

1
public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

参数

  • loader– 类加载器来定义代理类
  • interfaces– 代理类实现的接口列表
  • h– 调度方法调用的调用处理函数

也看一个例子

还是上一个例子,作者的下一篇文章要vip了,其他动态代理又看不懂,让GPT写一个尝试理解吧。

使用JDK动态代理的五大步骤:

1)通过实现InvocationHandler接口来自定义自己的InvocationHandler;

2)通过Proxy.getProxyClass获得动态代理类;

3)通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class);

4)通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入;

5)通过代理对象调用目标方法;

要使用静态代理中的Animal.java,Cat.java,Dog.java

新建两个类

AnimalProxy类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package Proxy;


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

public class AnimalProxy implements InvocationHandler {
private Object target;

public AnimalProxy(Object target) {
this.target = target;
}//接受一个目标对象作为参数,用于初始化target成员变量。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("==========AnimalProxy 代理类执行开始!=============");
Object result = method.invoke(target, args);
System.out.println("==========AnimalProxy 代理类执行结束!===========");
return result;
}
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable:实现 InvocationHandler 接口的方法,负责处理动态代理的调用逻辑。在这个方法中,我们可以在目标方法执行前后插入自定义逻辑。具体逻辑如下:

  • 输出代理类执行开始的日志。
  • 调用目标对象的相应方法,即 method.invoke(target, args)
  • 输出代理类执行结束的日志。
  • 返回方法的执行结果。

这个代理类的主要作用是在代理的目标对象执行前后输出日志,类似于横切关注点(cross-cutting concerns),这是 AOP(面向切面编程)的一种常见应用。代理类通过实现 InvocationHandler 接口,使得我们可以在运行时动态地为目标对象创建代理,而不需要为每个类都手动创建一个专门的代理类。

TestDynamicProxy.java 中,我们通过使用 Proxy.newProxyInstance 方法来动态创建代理对象,并传入 AnimalProxy 作为 InvocationHandler。这样,无论是 Cat 还是 Dog 都可以使用同一个代理类进行代理,而不需要为每个类都创建一个专门的代理类。

TestDynamicProxy类

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
package Proxy;

import java.lang.reflect.Proxy;
public class TestDynamicProxy {

public static void main(String[] args) {
Cat cat = new Cat();
Animal catProxy = (Animal) Proxy.newProxyInstance(
Animal.class.getClassLoader(),//用于加载代理类的类加载器
new Class[]{Animal.class},//代理的接口数组,这里是 Animal 接口
new AnimalProxy(cat)//实现了 InvocationHandler 接口的代理类,其中的目标对象是 cat
);
catProxy.action();
catProxy.breath();

Dog dog = new Dog();
Animal dogProxy = (Animal) Proxy.newProxyInstance(
Animal.class.getClassLoader(),
new Class[]{Animal.class},
new AnimalProxy(dog)
);
dogProxy.action();
dogProxy.breath();
}
}

运行结果:

image-20240217223134127

JDK 动态代理源码分析

我看的是JDK1.8

API概述

Proxy 类主要包括以下 API:

Proxy 描述
getProxyClass(ClassLoader, Class…) : Class 获取实现目标接口的代理类 Class 对象
newProxyInstance(ClassLoader,Class<?>[],InvocationHandler) : Object 获取实现目标接口的代理对象
isProxyClass(Class<?>) : boolean 判断一个 Class 对象是否属于代理类
getInvocationHandler(Object) : InvocationHandler 获取代理对象内部的 InvocationHandler

核心源码

Proxy.java

获取代理类 Class 对象

1
2
3
4
5
6
7
8
9
10
11
12
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

return getProxyClass0(loader, intfs);//获得代理类Class对象
}

实例化代理类对象

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 static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);//获得代理类Class对象

/*
* Invoke its constructor with the designated invocation handler.
*获得代理类构造器 (接收一个 InvocationHandler 参数)
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
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);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}

实例化代理对象也需要先通过 getProxyClass0(…) 获取代理类 Class 对象,而 newProxyInstance(…) 随后会获取参数为 InvocationHandler 的构造函数实例化一个代理类对象。

先看下代理类 Class 对象是如何获取的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
* 获得代理类Class对象
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//如果指定加载器定义的代理类实现
//给定的接口存在,这将简单地返回缓存副本
//否则,它将通过ProxyClassFactory创建代理类
return proxyClassCache.get(loader, interfaces);
}

看看proxyClassCache实例

1
2
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

看到WeakCache.java的120行,这个apply的实现类的ProxyClassFactory(代理类的工厂)再次回到Proxy类

image-20240218152447847

注意这个时候在这里加一个断点调试可以看到proxy的值为$Proxy@537为什么呢?image-20240218155304181

接着看

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
84
85
86
87
88
89
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}

String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}

if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}

/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;//获取名字

/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}

视线放到这里,其实就行进行了一个拼接来获取名字。这个名字就是代理对象的名字!

image-20240218155729991

接下来的这行代码用来生成字节码的文件,然后加载到class文件

image-20240218160054514

到这里代理对象以及生成了。

那我们在TestDynamicProxy.java的main方法中添加一些代码来看到生成的字节码文件

1
2
3
4
5
6
7
8
9
10
11
12
byte[] classFile = sun.misc.ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Animal.class});

// 将字节码文件写入指定路径
String path = "D:\\语言学习\\java学习\\学习源代码\\learn\\src\\Proxy\\AnimalProxy.class";
try (FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("Success: Proxy class file generated at " + path);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Fail");
}

可以看到提示成功生成了.class文件

image-20240218161548679

接下来看看IDEA反编译的内容

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
84
85
86
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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

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

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 void breath() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

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);
}
}

public final void action() throws {
try {
super.h.invoke(this, m4, (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"));
m3 = Class.forName("Proxy.Animal").getMethod("breath");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("Proxy.Animal").getMethod("action");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

注意看这个breath方法,可以看出来调用TestDynamicProxy类的xxxProxy.breath()方法实则在调用invoke方法。

image-20240218161925464

invoke执行的这个h是继承于Proxy类里面的

image-20240218163119537

InvocationHandler接口只有一个invoke方法

image-20240218163213269

调用代理方法是就是AnimalProxy类的invoke方法。这样就是根据源码以及运行结果都可以看出来。

在动态代理中InvocationHandler是核心,每个代理实例都具有一个关联的调用处理程序(InvocationHandler)。

对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序(InvocationHandler)的invoke()方法。

所以对代理方法的调用都是通InvocationHadler的invoke来实现中,而invoke方法根据传入的代理对象,

方法和参数来决定调用代理的哪个方法。

AOP与动态代理

Spring框架中有用到,相信后面在学习Spring框架安全的时候还会再遇到,此处作为扩展了解即可。

AOP(Aspect Orient Programming,面向切面编程) 假如有三个代码段中都有相同的代码,如下图所示:

image-20240218183648901

这时为了简化代码,我们会将相同的代码封装到一个方法中,比如这样:

image-20240218183737920

有这样一种情况,某几段代码段,前后的代码是固定一样的,中间的一段代码会调用不同方法,比如这样:

image-20240218183811737

这时简化代码就需要用到动态代理。在动态代理中增加通用方法,中间变化的代码就进行动态调用。

JDK动态代理和CGLIB动态代理区别

在于JDK自带的动态代理,必须要有接口,而CGLIB动态代理有没有接口都可以。

  • JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib动态代理:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。(CGLIB 通过动态生成一个需要被代理类的子类(即被代理类作为父类),该子类重写被代理类的所有不是 final 修饰的方法,并在子类中采用方法拦截的技术拦截父类所有的方法调用,进而织入横切逻辑。)

没有实现接口或者不需要实现接口的类,我们可以通过cglib动态代理对它进行代理。

基于cglib实现的动态代理需要引入第三方cglib库,之后我们可以实现基于子类的动态代理。

使用cglib实现的动态代理也有一个约束条件,就是被代理类不能被final修饰,

使用cglib实现的动态代理核心是Enhancer类,仔细观察我们会发现cglib动态代理的实现过程和JDK实现动态代理的过程极其类似。

在反序列化中动态代理的作用

虽然Java安全还没怎么开始,但是还是先了解一下吧,毕竟学这些开发都是为了安全。

据说在LazyMap CC1链和JDK7U21这两条链子会涉及到动态代理。

说要利用反序列化的漏洞,我们是需要一个入口类的。

我们先假设存在一个能够漏洞利用的类为B.f,比如Runtime.exec这种。
我们将入口类定义为A,我们最理想的情况是 A[O] -> O.f,那么我们将传进去的参数O替换为B即可。但是在实战的情况下这种情况是极少的。

回到实战情况,比如我们的入口类A存在O.abc这个方法,也就是 A[O] -> O.abc;而 O 呢,如果是一个动态代理类,Oinvoke方法里存在.f的方法,便可以漏洞利用了,我们还是展示一下。

1
2
3
4
A[O] -> O.abc
O[O2] invoke -> O2.f // 此时将 B 去替换 O2
最后 ---->
O[B] invoke -> B.f // 达到漏洞利用效果

动态代理在反序列化当中的利用和readObject是异曲同工的。

readObject方法在反序列化当中会被自动执行。
invoke方法在动态代理当中会自动执行。

参考:

JAVA实现静态代理_java 静态代理-CSDN博客

【Spring基础】JDK动态代理实现原理(jdk8)_jdk8 动态方法调用-CSDN博客

彻底搞懂jdk动态代理并自己动手写一个动态代理-腾讯云开发者社区-腾讯云 (tencent.com)

Java | JDK 动态代理的原理其实很简单 - 掘金 (juejin.cn)

Java反序列化基础篇-JDK动态代理 - FreeBuf网络安全行业门户

知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 (zsxq.com)