JDK的动态代理

JDK的动态代理

动态代理,Spring框架经常使用到的一个技术,Spring有使用两种动态代理技术,这次介绍的是JDK提供的动态代理。

以下基于JDK1.8进行说明。

使用

JDK动态代理是基于接口实现的,使用的核心围绕着java.lang.reflect.Proxy类来使用的。

java.lang.reflect.Proxy#newProxyInstance方法可以创建出代理对象,示例代码如下:

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) {
IHumanity iHumanity = (IHumanity)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{IHumanity.class}, new InvocationHandler() {
private final Student student = new Student();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始人类行为:" + method.getName());
return method.invoke(student, args);
}
});
iHumanity.say();
iHumanity.eat();
}
}

IHumanity

1
2
3
4
public interface IHumanity {
void say();
void eat();
}

Student

1
2
3
4
5
6
7
8
9
10
11
public class Student implements IHumanity{
@Override
public void say() {
System.out.println("我是学生");
}

@Override
public void eat() {
System.out.println("正在进食");
}
}

探索

java.lang.reflect.Proxy类不提供构造方法,但有protected修饰的构造方法可以提供继承,下面看看它提供的静态类方法。

newProxyInstance

newProxyInstance方法有三个参数,接下来分别介绍参数的意义。

ClassLoader loader

要求提供一个类加载器,该加载器要能够加载到被代理接口。下面可以给出一个反例:

1
2
3
4
5
6
7
Proxy.newProxyInstance(ZipInfo.class.getClassLoader(), new Class[]{IHumanity.class}, new InvocationHandler() {
private final Student student = new Student();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(student, args);
}
});

com.sun.nio.zipfs.ZipInfo是位于JDK扩展目录下的jar包,由扩展类加载器加载的,一般情况下它是不会加载到我们编写的代码类,所以上面所示的反例将会抛出异常Exception in thread "main" java.lang.IllegalArgumentException: interface com.gugu.service.IHumanity is not visible from class loader

Class<?>[] interfaces

要求提供被代理的接口类数组,这个参数与第三个参数关联,下面可以给出一个反例:

1
2
3
4
5
6
7
8
IMale iMale = (IMale)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{IMale.class}, new InvocationHandler() {
private final Student student = new Student();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(student, args);
}
});
iMale.readme();

Student类是没有实现IMale接口的,执行iMale.readme();将会抛出异常Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class

InvocationHandler h

要求提供实现代理逻辑的接口,这个参数将在调用代理对象方法时调用。这个接口有三个参数可以看看:

Object proxy

表示代理对象,这个参数的意义要看场景,下面举一个例子说明:

IProfession

1
2
3
public interface IProfession {
IProfession action();
}

Student

1
2
3
4
5
6
7
public class Student implements IProfession {
@Override
public IProfession action() {
System.out.println("作为学生开始学习");
return this;
}
}

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) {
IProfession iProfession = (IProfession)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{IProfession.class}, new InvocationHandler() {
private final Student student = new Student();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("行动前先摸下鱼");
method.invoke(student, args);
return proxy;
// return method.invoke(student, args);
}
});
IProfession iProfession1 = iProfession.action();
iProfession1.action();
}
}

如果注释Main的第8、9行代码打开第10行代码注释的话,执行到14行代码时将绕过代理对象,学生就不会在行动前摸鱼了。

Method method

方法实例对象,表示要执行的方法。

Object[] args

方法参数,表示要执行的方法所需参数。

getProxyClass

这个方法相比于newProxyInstance缺少了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
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);
}

isProxyClass

该方法可以判断是否是代理类,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) {
Object newProxyInstance = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{IHumanity.class, IProfession.class}, new InvocationHandler() {
private final Student student = new Student();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始代理:" + method.getName());
return method.invoke(student, args);
}
});
IHumanity newProxyInstance1 = (IHumanity) newProxyInstance;
newProxyInstance1.say();
newProxyInstance1.eat();
IProfession newProxyInstance2 = (IProfession) newProxyInstance;
newProxyInstance2.action();
System.out.println("(Proxy.isProxyClass(newProxyInstance2.getClass())) = " + (Proxy.isProxyClass(newProxyInstance2.getClass()))); // true
}
}

getInvocationHandler

看名字可以知道是获取代理对象的InvocationHandler,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Main {
private static class MyInvocationHandler implements InvocationHandler{

private final Student student;

public MyInvocationHandler(Student student) {
this.student = student;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始代理:" + method.getName());
return method.invoke(student, args);
}
}

public static void main(String[] args) {
Student student = new Student();
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(student);
Object proxyInstance = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{IHumanity.class, IProfession.class}, myInvocationHandler);
System.out.println("(Proxy.getInvocationHandler(proxyInstance) == myInvocationHandler) = " + (Proxy.getInvocationHandler(proxyInstance) == myInvocationHandler)); // true
}
}

JDK的动态代理
https://blog.gugu.dev/2024-02-21/JDK的动态代理/
作者
MinMin
发布于
2024年2月21日
许可协议