一、描述

1、描述一

代理背后一般至少有一个实际对象,代理的外部功能和实际对象一般是一样的,用户与代理打交道,不直接接触实际对象。虽然外部功能和实际对象一样,但代理有它存在的价值,比如:

  • 节省成本比较高的实际对象的创建开销,按需延迟加载,创建代理时并不真正创建实际对象,而只是保存实际对象的地址,在需要时再加载或创建。
  • 执行权限检查,代理检查权限后,再调用实际对象。
  • 屏蔽网络差异和复杂性,代理在本地,而实际对象在其他服务器上,调用本地代理时,本地代理请求其他服务器。

代理模式的简单例子如下:

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
interface MyService {  
void request();

void response();
}

class MyServiceImpl implements MyService {
@Override
public void request() {
System.out.println("MyServiceImpl.request()");
}

@Override
public void response() {
System.out.println("MyServiceImpl.response()");
}
}

class MyProxy implements MyService {

private final MyService myService;

public MyProxy() {
this.myService = new MyServiceImpl();
}

@Override
public void request() {
System.out.println("request before");
myService.request();
System.out.println("request after");
}

@Override
public void response() {
System.out.println("response before");
myService.response();
System.out.println("response after");
}

public static void main(String[] args) {
MyProxy proxyService = new MyProxy();
proxyService.request();
}
}

代理和实际对象一般有相同的接口,上面的示例中,共同接口为 MyService,实际对象是 MyServiceImpl,代理是 MyProxy。在 MyProxy 内部有一个 MyService 变量,指向实际对象,在构造方法中被初始化。

上面的示例中,在实际对象的方法调用前后加一些调试语句。为了在不修改原类的情况下达到这个目的,在代码中创建了一个代理类 MyProxy,它的代码是在写程序时固定的,所以称为静态代理

输出跟踪调试信息是一个通用需求,如果每个类都需要,而又不希望修改类定义,我们需要为每个类创建代理,实现所有接口,这个工作就太烦琐了,如果再有其他的切面需求,整个工作可能又要重来一遍。可以使用动态代理来解决这个问题。

2、描述二

某些情况下,当客户端不能或不适合直接访问目标业务对象时,业务对象可以通过代理把自己的业务托管起来,使客户端间接地通过代理进行业务访问。如此不但能方便用户使用,还能对客户端的访问进行一定的控制。

动态代理的实例化过程是动态完成的,不需要专门针对某个接口去编写代码实现一个代理类,而是在接口运行时动态生成。

JDK 反射包中提供的 InvocationHandler (动态调用处理器)接口,这个接口定义了动态反射调用的标准。

很多软件框架中都大量应用了动态代理的理念,如 Spring 的面向切面编程技术 AOP,只需要定义好一个切面类 @Aspect 并声明其切入点 @Pointcut(标记出被代理的哪些对象的哪些接口方法)​,以及被切入的代码块(要增加的逻辑,比如这里的过滤功能代码,可分为前置执行 @Before、后置执行 @After 以及异常处理 @AfterThrowing 等)​,框架就会自动帮我们生成代理并切入目标。最常用到的就是给多个类方法前后动态加入写日志,此外还有为业务类加上数据库事务控制(业务代码开始前先切入“事务开始”​,执行过后再切入“事务提交”​,如果抛异常被捕获则执行“事务回滚”​)​,如此就不必为每个业务类都写这些重复代码了,整个系统对数据库的访问都得到了事务管控,开发效率得到了提升。

不管是在编程时预定义静态代理,还是在运行时即时生成的动态代理,它们的基本理念都是通过拦截被代理对象的原始业务并在其之前或之后加入一些额外的业务或者控制逻辑,来最终实现在不改变原始类(被代理类)的情况下对其进行加工、管控。

代理模式中各个角色如下:

  • 业务接口:对业务接口标准的定义与表示
  • 被代理业务:需要被代理的实际业务类,实现了业务接口
  • 代理:同样实现了业务接口标准,包含被代理对象的实例并对其进行管控,对外提供代理后的业务方
  • 客户端:业务的使用者,直接使用代理业务,而非实际业务

代理模式不仅能增强原业务功能,更重要的是还能对其进行业务管控。对用户来讲,隐藏于代理中的实际业务被透明化了,而暴露出来的是代理业务,以此避免客户端直接进行业务访问所带来的安全隐患,从而保证系统业务的可控性、安全性。

二、动态代理

动态代理是一种强大的功能,它可以在运行时动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为通过该类获取的对象添加方法、修改行为。

动态代理是实现面向切面的编程 AOP(Aspect Oriented Programming)的基础。切面的例子有日志、性能监控、权限检查、数据库事务等,它们在程序的很多地方都会用到,但与具体的业务逻辑关系也不太密切,如果在每个用到的地方都写,代码会很冗余,也难以维护,AOP 将这些切面与主体逻辑相分离,代码简单优雅得多。

在 Java 中,动态代理有多种实现方式,Java SDK 库中提供了 JDK 动态代理,还可以通过第三方库(如 cglib)提供的实现。在静态代理中,代理类是直接定义在代码中的,在动态代理中,代理类是通过动态字节码技术动态生成的,下面给出一个 JDK 动态代理的示例:

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
interface MyService {  
void request();

void response();
}

class MyServiceImpl implements MyService {
@Override
public void request() {
System.out.println("MyServiceImpl.request()");
}

@Override
public void response() {
System.out.println("MyServiceImpl.response()");
}
}

class MyInvocationHandler implements InvocationHandler {

private final MyService myService;

public MyInvocationHandler() {
this.myService = new MyServiceImpl();
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + " before");
Object object = method.invoke(myService, args);
System.out.println(method.getName() + " after");
return object;
}

public static void main(String[] args) {
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
MyService proxy = (MyService) Proxy.newProxyInstance(MyService.class.getClassLoader(), new Class[]{MyService.class}, myInvocationHandler);
proxy.request();
System.out.println();
proxy.response();
}
}

相较于静态代理,创建代理对象的方式发生了变化,使用 java.lang.reflect 包中的 Proxy 类的静态方法 newProxyInstance 来创建代理对象。public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法有三个参数:

  • ClassLoader loader,类加载器。
  • Class<?>[] interfaces,表示代理类要实现的接口列表,数组元素的类型只能是接口不能是普通的类。
  • InvocationHandler h,用于分派调用方法的调用处理器,只有一个需实现的 invoke 方法,对代理接口所有方法的调用都会转给该方法。

newProxyInstance 方法的返回值类型为 Object,可以强制转换为入参 interfaces 数组中的某个接口类型。

MyInvocationHandler 实现了 InvocationHandler 方法并重写了 invoke 方法,invoke 方法有三个参数:

  • proxy 表示代理对象本身
  • method 表示正在被调用的方法
  • args 表示方法的参数

invoke 方法中输出代理对象 proxy 的信息,可以看到代理对象的类名为:$Proxy0。该对象是动态生成的,可以将生成的代理对象输出到类文件中。在 Java 8 中可以通过在启动参数中添加 sun.misc.ProxyGenerator.saveGeneratedFiles 参数来输出代理对象,运行上面的 MyInvocationHandler 中的 main 方法,IDEA 中添加 VM 参数如下:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,输出代理对象代码如下:

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
package cn.z2huo.knowledge.jdk8.designpattern.proxy.dynamic.example;

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

final class $Proxy0 extends Proxy implements MyService {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
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 response() 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 request() throws {
try {
super.h.invoke(this, m3, (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("cn.z2huo.knowledge.jdk8.designpattern.proxy.dynamic.example.MyService").getMethod("response");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("cn.z2huo.knowledge.jdk8.designpattern.proxy.dynamic.example.MyService").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(((Throwable)var2).getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(((Throwable)var3).getMessage());
}
}
}

$Proxy0 的父类是 Proxy,它有一个构造方法,接受一个 InvocationHandler 类型的参数,保存为了实例变量 h, h 定义在父类 Proxy 中,它实现了接口 MyService,对于每个方法,代理对象会调用 InvocationHandlerinvoke 方法,对于 Object 中的方法,如 hashCodeequalstoString$Proxy0 同样转发给了 InvocationHandler

$Proxy0 类定义本身与被代理的对象没有关系,与 InvocationHandler 的具体实现也没有关系,而主要与接口数组有关,给定这个接口数组,它动态创建每个接口的实现代码,实现就是转发给 InvocationHandler,与被代理对象的关系以及对它的调用由 InvocationHandler 的实现管理。

1、通用的 InvocationHandler

使用动态代理,可以编写通用的代理逻辑,用于各种类型的代理对象,而不需要为每个被代理的类型都创建静态代理类。对 MyInvocationHandler 做如下修改:

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

private final Object object;

public MyInvocationHandler(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理对象:" + proxy.getClass().getName());
System.out.println(method.getName() + " before");
Object object = method.invoke(this.object, args);
System.out.println(method.getName() + " after");
return object;
}

public static <T> T getProxy(Class<T>[] interfaceArray, T object) {
return (T) Proxy.newProxyInstance(object.getClass().getClassLoader(), interfaceArray, new MyInvocationHandler(object));
}

}

MyInvocationHandler 中修改被代理对象类型为 Object,这样一个 MyInvocationHandler 可以用于多个被代理类型。调用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws Exception {

MyServiceImpl myService = new MyServiceImpl();
MyService proxy = getProxy(new Class[]{MyService.class}, myService);
proxy.request();
System.out.println();
proxy.response();

System.out.println();

MyService2Impl myService2 = new MyService2Impl();
MyService2 proxy2 = getProxy(new Class[]{MyService2.class}, myService2);
proxy2.run();
System.out.println();
proxy2.say();
}

2、实现多个接口的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ServiceImpl implements MyService, MyService2 {  

@Override
public void request() {
System.out.println("request");
}

@Override
public void response() {
System.out.println("response");
}

@Override
public void say() {
System.out.println("say");
}

@Override
public void run() {
System.out.println("run");
}
}

ServiceImpl 实现了两个接口 MyServiceMyService2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {  
ServiceImpl myService = new ServiceImpl();
Object proxy = MyInvocationHandler.getProxy(new Class[]{MyService.class, MyService2.class}, myService);

MyService myServiceProxy = (MyService) proxy;
MyService2 myService2Proxy = (MyService2) proxy;

myServiceProxy.request();
System.out.println();
myServiceProxy.response();

System.out.println();

myService2Proxy.run();
System.out.println();
myService2Proxy.say();

}

生成的代理对象代码如下:

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
96
97
98
99
final class $Proxy0 extends Proxy implements MyService, MyService2 {  
private static Method m1;
private static Method m4;
private static Method m5;
private static Method m2;
private static Method m3;
private static Method m6;
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 response() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void run() throws {
try {
super.h.invoke(this, m5, (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 request() 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 say() throws {
try {
super.h.invoke(this, m6, (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("cn.z2huo.knowledge.jdk8.designpattern.proxy.dynamic.example3.MyService").getMethod("response");
m5 = Class.forName("cn.z2huo.knowledge.jdk8.designpattern.proxy.dynamic.example3.MyService2").getMethod("run");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("cn.z2huo.knowledge.jdk8.designpattern.proxy.dynamic.example3.MyService").getMethod("request");
m6 = Class.forName("cn.z2huo.knowledge.jdk8.designpattern.proxy.dynamic.example3.MyService2").getMethod("say");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(((Throwable)var2).getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(((Throwable)var3).getMessage());
}
}
}

可以看到代理对象和被代理对象方法相匹配,代理对象也实现了 MyServiceMyService2 接口,并且重写了两个接口的方法,方法中是对 InvocationHandlerinvoke 方法的调用。

三、其他

1、代理与适配器、装饰器的区别

适配器和装饰器,它们与代理模式有点类似,背后都有一个别的实际对象,都是通过组合的方式指向该对象,不同之处在于,适配器是提供了一个不一样的新接口,装饰器是对原接口起到了“装饰”作用,可能是增加了新接口、修改了原有的行为等,代理一般不改变接口。不过,也可以将它们看作代理的变体,统一看待。

从前面的静态代理的示例中可以看到,代理类的构造方法中主动实例化了被代理类,而非由外部注入。某种意义上讲,这是代理模式区别于装饰器模式的一种体现。二者的理念与实现有点类似,但装饰器模式往往更加关注为其他对象增加功能,让客户端更加灵活地进行组件搭配;而代理模式更强调的则是一种对访问的管控,甚至是将被代理对象完全封装而隐藏起来,使其对客户端完全透明。

2、JDK 动态代理的优缺点

JDK 动态代理是通过动态字节码技术,在程序运行的时候直接将代理对象生成,而不是和正常的程序执行一样,正常的执行需要先将 class 字节码通过 ClassLoader 加载到 JVM 中创建 class 对象,然后通过代理类的 class 对象创建代理对象,动态字节码省去了 ClassLoader 加载字节码的过程。

JDK 动态代理的缺点是代理对象必须要实现目标对象的接口,着有很大的局限性,因为目标对象可能没有实现接口,此时 JDK 动态代理无法实现代理操作,CGLIB 是通过继承的方式来实现的,代理对象继承目标对象,从而实现子类调用父类的方法来实现代理。

相关链接

OB tags

#设计模式 #Java