👉文章示例代码👈

定义

为其他对象提供一种代理用来控制对这个对象的访问。

在代理模式中,代理对象作为访问对象和目标对象之间的中介。

场景示例

笔者这里以购买火车票为例。我们购买火车票不一定要去火车站购买,我们可以通过12306网站或者去火车票代售点购买。

基于继承的静态代理实现

创建目标对象

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
/**
* @author zhh
* @description 火车站(目标对象)
* @date 2020-02-26 21:44
*/
public class Station {

/**
* 姓名
*/
private String username;

/**
* 始发站
*/
private String start;

/**
* 终点站
*/
private String end;

public Station(String username, String start, String end) {
this.username = username;
this.start = start;
this.end = end;
}

// 此处省略getter、setter方法

public void buy() {
System.out.println(String.format("%s购买了从%s至%s的火车票", username, start, end));
}
}

创建代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author zhh
* @description 12306网站类(代理对象)
* @date 2020-02-26 21:47
*/
public class Website12306 extends Station {

public Website12306(String username, String start, String end) {
super(username, start, end);
}

@Override
public void buy() {
// 模拟日志打印
System.out.println(String.format("【打印日志】 %s在12306平台上购买从%s至%s的火车票", getUsername(), getStart(), getEnd()));
super.buy();
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author zhh
* @description 测试类
* @date 2020-02-26 21:54
*/
public class Test {

public static void main(String[] args) {
Station station = new Website12306("海豪", "诸暨", "杭州");
station.buy();
}
}

测试类的输出结果如下:

【打印日志】 海豪在12306平台上购买从诸暨至杭州的火车票
海豪购买了从诸暨至杭州的火车票

类结构图

以上示例类的结构图如下所示
image.png

从上述示例代码中,我们可以看到,代理对象中的 buy() 方法实际上最终调用的是目标对象的 buy() 方法,同时我们也可以在代理对象中对方法进行增强。

基于接口的静态代理实现

创建抽象接口

可以是接口也可以是抽象类。

1
2
3
4
5
6
7
8
9
/**
* @author zhh
* @description 抽象接口
* @date 2020-02-26 21:44
*/
public interface Station {

void buy();
}

创建目标对象

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
/**
* @author zhh
* @description 诸暨火车站(目标对象)
* @date 2020-02-26 22:10
*/
public class ZhuJiStation implements Station {

/**
* 姓名
*/
private String username;

/**
* 始发站
*/
private String start;

/**
* 终点站
*/
private String end;

public ZhuJiStation(String username, String start, String end) {
this.username = username;
this.start = start;
this.end = end;
}

// 此处省略getter、setter方法

public void buy() {
System.out.println(String.format("%s购买了从%s至%s的火车票", username, start, end));
}
}

创建代理对象

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
/**
* @author zhh
* @description 12306网站类(代理对象)
* @date 2020-02-26 21:47
*/
public class Website12306 implements Station {

/**
* 姓名
*/
private String username;

/**
* 始发站
*/
private String start;

/**
* 终点站
*/
private String end;

private Station station;

public Website12306(String username, String start, String end) {
this.username = username;
this.start = start;
this.end = end;
}

public void buy() {
// 模拟日志打印
System.out.println(String.format("【打印日志】 %s在12306平台上购买从%s至%s的火车票", username, start, end));
if (station == null) {
station = new ZhuJiStation(username, start, end);
}
station.buy();
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author zhh
* @description 测试类
* @date 2020-02-26 21:54
*/
public class Test {

public static void main(String[] args) {
Station station = new Website12306("海豪", "诸暨", "杭州");
station.buy();
}
}

测试类的输出结果如下:

【打印日志】 海豪在12306平台上购买从诸暨至杭州的火车票
海豪购买了从诸暨至杭州的火车票

类结构图

以上示例类的结构图如下所示
image.png

这种方式需要目标对象和代理对象实现同一个接口或者继承同一个抽象类。

基于JDK动态代理实现

基于JDK动态代理的实现,主要依赖的是 Proxy 类的 newProxyInstance() 方法和 InvocationHandler 类的 invoke() 方法。

而且JDK动态代理不能够代理类,只能够基于接口实现。

创建抽象接口

这个地方只能是接口。

1
2
3
4
5
6
7
8
9
/**
* @author zhh
* @description 抽象接口
* @date 2020-02-26 21:44
*/
public interface Station {

void buy();
}

创建目标对象

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
/**
* @author zhh
* @description 诸暨火车站(目标对象)
* @date 2020-02-26 22:10
*/
public class ZhuJiStation implements Station {

/**
* 姓名
*/
private String username;

/**
* 始发站
*/
private String start;

/**
* 终点站
*/
private String end;

public ZhuJiStation(String username, String start, String end) {
this.username = username;
this.start = start;
this.end = end;
}

// 此处省略getter、setter方法

public void buy() {
System.out.println(String.format("%s购买了从%s至%s的火车票", username, start, end));
}
}

创建动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author zhh
* @description 动态代理类
* @date 2020-02-26 22:32
*/
public class StationDynamicProxy implements InvocationHandler {

private Object target;

public StationDynamicProxy(Object target) {
this.target = target;
}

public Object bind() {
Class clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 模拟日志打印
System.out.println("【日志打印】真实操作之前进行日志打印");
return method.invoke(target, args);
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author zhh
* @description 测试类
* @date 2020-02-26 21:54
*/
public class Test {

public static void main(String[] args) {
Station station = (Station) new StationDynamicProxy(new ZhuJiStation("海豪", "诸暨", "杭州")).bind();
station.buy();
}
}

测试类的输出结果如下:

【日志打印】真实操作之前进行日志打印
海豪购买了从诸暨至杭州的火车票

类结构图

以上示例类的结构图如下所示
image.png

基于CGLib动态代理实现

CGLib是针对于类的代理模式,使用CGLib就必须实现 MethodInterceptor 接口。

创建目标对象

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
/**
* @author zhh
* @description 诸暨火车站(目标对象)
* @date 2020-02-26 22:10
*/
public class ZhuJiStation {

/**
* 姓名
*/
private String username;

/**
* 始发站
*/
private String start;

/**
* 终点站
*/
private String end;

// 此处忽略getter、setter方法

public void buy() {
System.out.println(String.format("%s购买了从%s至%s的火车票", username, start, end));
}
}

创建动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author zhh
* @description 动态代理类
* @date 2020-02-26 22:32
*/
public class StationDynamicProxy implements MethodInterceptor {

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 模拟日志打印
System.out.println("【日志打印】真实操作之前进行日志打印");
return methodProxy.invokeSuper(o, objects);
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author zhh
* @description 测试类
* @date 2020-02-26 21:54
*/
public class Test {

public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ZhuJiStation.class);
enhancer.setCallback(new StationDynamicProxy());
ZhuJiStation proxy = (ZhuJiStation) enhancer.create();
proxy.setUsername("海豪");
proxy.setStart("诸暨");
proxy.setEnd("杭州");
proxy.buy();
}
}

测试类的输出结果如下:

【日志打印】真实操作之前进行日志打印
【日志打印】真实操作之前进行日志打印
【日志打印】真实操作之前进行日志打印
【日志打印】真实操作之前进行日志打印
海豪购买了从诸暨至杭州的火车票

类结构图

以上示例类的结构图如下所示
image.png

CGLib摆脱了对于 Proxy 的依赖,从而避免了创建代理类必须继承 Proxy 的限制。

总结

适用场景

访问对象不适合或者不能直接引用目标对象。

优点

  • 将访问对象和目标对象分离,降低了系统的耦合度。
  • 扩展性好,可以扩展目标对象的功能。
  • 可以保护、增强目标对象。

缺点

  • 增加了系统的复杂度。
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢。

扩展

代理方式

  • 静态代理:通过在代码中显示地定义一个业务实现类的一个代理,在代理类中对同名的业务方法进行包装,用户通过调用代理类的被包装过的业务方法来调用目标对象的业务方法。同时对目标对象的业务方法进行增强。
  • JDK动态代理:是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法。
  • CGLib动态代理:是通过继承来实现的,生成的动态代理类是业务类的子类,通过重写业务方法进行代理。

Spring代理选择

  • 当Spring中的Bean有实现接口时,Spring会用JDK的动态代理。
  • 当Spring中的Bean没有实现接口时,Spring会使用CGLib动态代理。

可以强制Spring使用CGLib动态代理(在Spring的配置中添加 <aop:aspectj-autoproxy proxy-target-class="true" )。

参考