Java设计模式系列(5):代理模式

布鸽不鸽 Lv4

前言

有时,我们想要给某对象提供一个代理对象,以控制对该对象的访问。代理对象应该作为访问对象和目标对象之间的中介。
Java中的代理可以分为静态代理和动态代理。静态代理类在编译期间就生成,动态代理类在Java运行时动态产生。动态代理又分为JDK代理和cglib代理两种。

原文地址:https://xuedongyun.cn/post/23348/

代理模式的角色

代理(Proxy)模式分为三种角色:

  • 抽象主题类(Subject):声明需要实现的业务方法
  • 真实主题类(Real Subject):实现抽象主题类。是代理对象所代表的真实对象
  • 代理类(Proxy):实现抽象主题类。内部有对真实主题类的引用

静态代理

抽象主题类(Subject)

1
2
3
public interface SellTickets {
void sell();
}

真实主题类(Real Subject)

1
2
3
4
5
6
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("车站买票");
}
}

代理类(Proxy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ProxyPoint implements SellTickets {

private SellTickets target;

public ProxyPoint(SellTickets target) {
this.target = target;
}

// 代理类作为中介,调用引用对象的放,实现代理
@Override
public void sell() {
System.out.println("before");
target.sell();
System.out.println("after");
}
}

使用代理

1
2
3
TrainStation trainStation = new TrainStation();
ProxyPoint proxy = new ProxyPoint(trainStation);
proxy.sell();

JDK动态代理

Java中提供了一个动态代理类Proxy,其中有一个newProxyInstance方法用来获取代理对象

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
// 工厂类,用来生产代理对象
public class ProxyFactory {

private TrainStation station = new TrainStation();

public ProxyFactory(TrainStation station) {
this.station = station;
}

public SellTickets getProxyObject() {
SellTickets proxyInstance = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {

/*
参数说明
proxy: 代理对象本身(不用this,是因为此处this指向的是InvocationHandler匿名类)
method: 接口中的方法的Method实例
args: 调用方法时传递的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object res = method.invoke(station, args);
System.out.println("after");
return res;
}
});
return proxyInstance;
}
}

使用代理

1
2
3
4
5
6
TrainStation trainStation = new TrainStation();

ProxyFactory proxyFactory = new ProxyFactory(trainStation);
SellTickets proxy = proxyFactory.getProxyObject();

proxy.sell();

原理分析如下:

Java创建了com.sun.proxy.$Proxy0类作为代理类,是程序在运行中动态的在内存中创建的,我们摘取重点代码来查看

执行时流程如下:

  1. 测试时,调用代理对象的sell方法
  2. 代理对象($Proxy0)中的sell方法,调用invocationHandler实现类的sell方法(此时传入了this,也即前面提到的proxy参数)
  3. invocationHandler实现类中,使用反射执行了真实对象的sell方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 程序运行过程中动态生成的代理类(部分)
public final class $Proxy0 extends Proxy implements SellTickets {

private static Method m3;

public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}

static {
m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
}

public final void sell() {
this.h.invoke(this, m3, null);
}
}

CGLIB动态代理

JDK动态代理要求被代理的对象必须实现接口。CGLIB是一个功能强大的第三方包,能为没有实现接口的类提供代理

需要先引入依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

截至目前为止,官方的CGLIB任然没有更新,无法在JDK17下使用。可以使用Spring中官方修改过的CGLIB来代替

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
public class ProxyFactory implements MethodInterceptor {

private TrainStation station;

public ProxyFactory(TrainStation station) {
this.station = station;
}

public TrainStation getProxyObject() {
// 创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
enhancer.setSuperclass(station.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 返回代理对象
return (TrainStation) enhancer.create();
}

/*
参数说明
Object o: 代理对象
method: 真实对象中的方法
objects: 实际参数
methodProxy: 代理对象中的方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
TrainStation res = (TrainStation) methodProxy.invokeSuper(o, objects);
System.out.println("after");
return res;
}
}

两种代理的对比

  • JDK代理 VS CGLIB 代理
    • CGLIB底层采用ASM字节码生成框架,使用字节码生成代理类。在JDK1.6之前比反射效率高。CGLIB的原理为生成被代理对象的子类。(因此不能代理final修饰的类)
    • 目前,JDK代理效率高于CGLIB。所以有接口类的使用JDK代理,没接口的类使用CGLIB
  • 标题: Java设计模式系列(5):代理模式
  • 作者: 布鸽不鸽
  • 创建于 : 2023-06-23 22:04:47
  • 更新于 : 2023-06-23 23:17:57
  • 链接: https://xuedongyun.cn//post/23348/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论