Java代理模式
yearnxyl Lv2

代理模式

代理模式是Java常见的设计模式之一。通常情况下,我们通过new来获取一个对象的类实例,然后通过调用其方法来获取相应的服务。而在代理模式中,是通过创建代理类(proxy)的方式来调用服务。

代理类会有一个委托类对象。代理类不会自己实现服务,而是通过委托类对象的方法来提供服务。所以,我们调用的仍然是委托类的方法,不过中间隔了proxy类来对委托类的方法进行更改。这么做的好处是我们可以在不改变委托类的前提下,对服务进行更新

静态代理

静态代理简单来说,就是提前在代码中写好proxy类。在编译后无法进行更改。

举例说明静态代理:

首先创建Person接口

1
2
3
4
5
package main.org.proxy;

public interface Person {
public void sayHello();
}

再创建实现Person接口的PersonSayHello类,该类即为委托类。

1
2
3
4
5
6
7
8
package main.org.proxy;

public class PersonSayHello implements Person{
@Override
public void sayHello() {
System.out.println("Hello!!!");
}
}

创建ProxyPerson类,该类即为代理类。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.org.proxy;

public class ProxyPerson implements Person{
private PersonSayHello psh;
public ProxyPerson(PersonSayHello psh){
this.psh=psh;
}
public void sayHello(){
System.out.println("Good Morning");
psh.sayHello();
System.out.println("Bye~");
}
}

可以看到,当我们调用其sayHello()方法时,本质上还是在调用PersonSayHello类的sayHello()方法,不过是在调用的前后增加了部分逻辑。

创建测试类ProxyTest进行测试:

image

静态代理有很大的缺点:不利于系统的维护。例如在PersonSayHello类中新增一个sayHi()方法,该方法也需要被代理,那么便需要在代理类中同时增加sayHi()方法。如果需要被代理的方法过多的话,便会很麻烦。

JDK动态代理

相较于静态代理,动态代理使用了反射机制。有点类似Java反射与安全章节提到的动态特性。

直接看例子:

首先同样需要一个Person接口

1
2
3
4
5
6
7
package main.org.proxy;

public interface Person {
public void sayGoodmorning();
public void sayHello();
public void sayBye();
}

创建实现Person接口的PersonSaySomething类,该类即为委托类。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.org.proxy;

public class PersonSaySomething implements Person{
public void sayHello(){
System.out.println("Hello!!!");
}
public void sayGoodmorning(){
System.out.println("Good Morning");
}
public void sayBye(){
System.out.println("Bye~~");
}
}

创建PersonInvocationHandler类,其实现了InvocationHandler接口。查看invoke方法,可以看出,该类的作用便是通过反射来对委托类的方法进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main.org.proxy;

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

public class PersonInvocationHandler<T> implements InvocationHandler {
private T target;
public PersonInvocationHandler(T target){
this.target=target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法调用前");
method.invoke(target,args);
System.out.println("方法调用后");
return null;
}
}

创建测试类DynamicProxyTest。在测试类中使用Proxy类下的newProxyInstance()方法来创建代理类。newProxyInstance()方法的三个参数分别是:委托类的类加载器、委托类实现的接口和InvocationHandler。之后便可以通过代理类执行委托类的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main.org.proxy;

import java.lang.reflect.Proxy;

public class DynamicProxyTest {
public static void main(String[] args) {
PersonSaySomething pss=new PersonSaySomething();
PersonInvocationHandler pih=new PersonInvocationHandler(pss);
Person proxyperson= (Person) Proxy.newProxyInstance(pss.getClass().getClassLoader(), pss.getClass().getInterfaces(),pih);
proxyperson.sayGoodmorning();
proxyperson.sayHello();
proxyperson.sayBye();
}
}

image

上面的代码每一个都不难理解,但是InvocationHandlerProxy组合到一起就产生了一些疑惑。

  1. InvocationHandler中的Invoke方法是什么时候被调用的,是如何被调用的
  2. 为什么Proxy.newProxyInstace()返回的是Person接口,而不是PersonSaySomething类。当使用PersonSaySomething时会报错。image
  3. 上面报错中的com.sun.proxy.$Proxy0是什么?

这些问题最终在这篇文章找到了答案:What Is the JDK com.sun.proxy.$Proxy Class?

当我们使用动态代理的时候,JDK会动态的生成一个$Proxy类,通常情况下这个$Proxy类的全称类似于 com.sun.proxy.$Proxy0,就像Java文档所说的,$Proxy是代理类的名称前缀。

让我们来区分一下java.lang.reflect.Proxy类和$Proxy类,java.lang.reflect.Proxy是由JDK编译的类,相比之下,$Proxy类是在runtime阶段时动态生成的类。从类的层次上来讲,$Proxy类继承于java.lang.reflect.Proxy类。然而要注意的一点是,$Proxy类仅仅存在于JVM,我们并不能直观的查看其类成员。

为了能够更好的审查$Proxy类,我们可以尝试将其从JVM中dump到磁盘里。恰巧Java提供了此功能。在Java8的环境下,可以在命令行中添加-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true。或者在代码中添加System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");。在Java9及其以后版本,需要使用jdk.proxy.ProxyGenerator.saveGeneratedFiles。例如上面的DynamicProxyTest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main.org.proxy;

import java.lang.reflect.Proxy;

public class DynamicProxyTest {
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
PersonSaySomething pss=new PersonSaySomething();
PersonInvocationHandler pih=new PersonInvocationHandler(pss);
Person proxyperson= (Person) Proxy.newProxyInstance(pss.getClass().getClassLoader(), pss.getClass().getInterfaces(),pih);
proxyperson.sayGoodmorning();
proxyperson.sayHello();
proxyperson.sayBye();
}
}

执行程序后,可以看到在项目目录下生成com/sun/proxy/$Proxy0.class文件。使用idea打开(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
87
88
89
90
91
92
93
94
95
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 main.org.proxy.Person;

public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
private static Method m5;
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 void sayHello() throws {
try {
super.h.invoke(this, m4, (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 void sayBye() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

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

可以看到$Proxy0继承了java.lang.reflect.Proxy类,并实现了Person接口。这里便解释了问题二:为什么动态代理是针对接口,而不是类。针对接口需要implements,针对类则需要extends。因为Java是单继承的且已经存在一个extends了,所以针对PersonSaySomething会增加extends导致报错。

至于问题一:InvocationHandler中的Invoke方法是什么时候被调用的,是如何被调用的?

sayHello()方法为例,看完代码后相信已经不算问题了。

1
2
3
4
5
6
7
8
9
public final void sayHello() throws  {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

至于$Proxy0是如何生成的。鉴于其过于复杂,且Java版本不同,生成方法也有部分不同,这里不再深入研究。