代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

在某些情况下,客户不想或不能直接引用某个对象,这个时候就需要一个中介(中间人)帮忙完成间接引用,该中介被称为 ”代理对象“。
举个具象化的例子:A 想向 C 借钱,但由于信用透支 / 彼此不认识 / …,C 不会借钱给 A,这时 A 就可以让 B(代理对象)以他自己的名义去找 C 借钱,这样 A 不仅获得了钱💴,还让 C 以为是 B 借钱,并不知道 A 的存在。在这个案例中,B 充当了 A 的代理人

生活中代理对象比比皆是,比如 “相亲对象与红娘”、“明星与经纪人”、“当事人张三和律师”、“老板和秘书” 等等。

分类

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。

  • 静态代理代理类在编译期就生成
  • 动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

角色

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

  • 抽象角色(Subject): 通过接口或抽象类声明真实角色和代理对象实现的业务方法。
  • 真实角色(Real Subject): 实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理角色(Proxy) : 提供了与真实角色相同的接口,其内部含有对真实角色的引用,它可以访问、控制或扩展真实角色的功能。

静态代理

静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展,静态代理需要代理对象和目标对象实现一样的接口。

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
// 火车站接口,有卖票功能
public interface TrainStation {
void sellTickets();
}

// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
@Override
public void sellTickets() {
System.out.println("广州火车站卖票啦");
}
}

// 代售点卖票(代理类)
public class ProxyPoint implements TrainStation {
// 目标对象(代理火车站售票)
private TrainStation station = new GuangzhouTrainStation();
// 在卖票的过程中增强前置条件, 但又不需要改变整体代码 实际该类是可以不用实现TrainStation接口的 也可以用构造函数通过传递参数选择对应的目标对象
@Override
public void sellTickets() {
System.out.println("代售加收5%手续费");
station.sellTickets();
}
}

// 测试
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
// 代售加收5%手续费
// 火车站卖票啦
proxyPoint.sellTickets();
}

可以从上面代码看到,我们访问的是ProxyPoint对象,也就是说ProxyPoint是作为访问对象和目标对象的中介的,同时也对sellTickets方法进行了增强(代理点收取加收5%手续费)。

静态代理的优点是实现简单,容易理解,只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。

而缺点也比较明显,那就是代理类和目标类必须有共同接口(父类),并且需要为每一个目标类维护一个代理类,当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动,目标对象和代理对象都需要作出调整。

动态代理

代理类在代码运行时创建的代理称之为动态代理。动态代理中代理类并不是预先在Java代码中定义好的,而是运行时由JVM动态生成,并且可以代理多个目标对象。

jdk动态代理

JDK动态代理是Java JDK自带的一个动态代理实现, 位于java.lang.reflect包下。

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
// 火车站接口,有卖票功能
public interface TrainStation {
void sellTickets();
}
// 广州火车站卖票
public class GuangzhouTrainStation implements TrainStation {
@Override
public void sellTickets() {
System.out.println("广州火车站卖票啦");
}
}
// 深圳火车站卖票
public class ShenzhenTrainStation implements TrainStation {
@Override
public void sellTickets() {
System.out.println("深圳火车站卖票啦");
}
}

// 代售点卖票(代理类)
public class ProxyPoint implements InvocationHandler {
private TrainStation trainStation;
public TrainStation getProxyObject(TrainStation trainStation) {
this.trainStation = trainStation;
Class<? extends TrainStation> clazz = trainStation.getClass();
// newProxyInstance 获取代理类对象
// clazz.getClassLoader() 类加载器
// clazz.getInterfaces() 得到目标对象的实现接口
return (TrainStation) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
// 第一个参数:代理对象.一般不使用;第二个参数:需要增强的方法;第三个参数:方法中的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代售火车票收取5%手续费");
return method.invoke(this.trainStation, args);
}
}
// 测试
public static void main(String[] args) {
ProxyPoint proxy = new ProxyPoint();
TrainStation guangzhouTrainStation = proxy.getProxyObject(new GuangzhouTrainStation());
// 代售火车票收取5%手续费
// 广州火车站卖票啦
guangzhouTrainStation.sellTickets();

TrainStation shenzhenTrainStation = proxy.getProxyObject(new ShenzhenTrainStation());
// 代售火车票收取5%手续费
// 深圳火车站卖票啦
shenzhenTrainStation.sellTickets();
}

jdk代理步骤:代理角色实现InvoctionHandler方法invoke–>在invoke方法里面写需要增强的代码–>在写一个方法接收需要代理的对象,在其中传递相关参数给invoke代理方法并返回

优点:

  • 使用简单、维护成本低。
  • Java原生支持,不需要任何依赖。
  • 解决了静态代理存在的多数问题。

缺点:

  • 由于使用反射,性能会比较差。
  • 对于每一次方法调用都会生成一个新的代理实例,这可能会导致额外的性能开销。
  • 只支持接口实现,不支持继承, 不满足所有业务场景。

cglib动态代理

CGLIB是一个强大的、高性能的代码生成库。它可以在运行期扩展Java类和接口,其被广泛应用于AOP框架中(Spring、dynaop)中, 用以提供方法拦截。CGLIB比JDK动态代理更强的地方在于它不仅可以接管Java接口, 还可以接管普通类的方法。

  • 先引用cglib包
1
2
3
4
5
6
<!-- 先引入cglib包 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib-version}</version>
</dependency>
  • 示例代码
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
// 代售点卖票(代理类)
public class ProxyPoint implements MethodInterceptor {
public TrainStation getProxyObject(Class<? extends TrainStation> trainStation) {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer =new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(trainStation);
//设置回调函数
enhancer.setCallback(this);
//创建代理对象并返回
return (TrainStation) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代售火车票收取5%手续费");
return methodProxy.invokeSuper(o, objects);
}
}
// 测试
public static void main(String[] args) {
ProxyPoint proxy = new ProxyPoint();
TrainStation guangzhouTrainStation = proxy.getProxyObject(GuangzhouTrainStation.class);
// 代售火车票收取5%手续费
// 广州火车站卖票啦
guangzhouTrainStation.sellTickets();
TrainStation shenzhenTrainStation = proxy.getProxyObject(ShenzhenTrainStation.class);
// 代售火车票收取5%手续费
// 深圳火车站卖票啦
shenzhenTrainStation.sellTickets();
}

优点:

  • 可以代理类和接口,无需要目标对象实现任何接口。
  • 在代理类中可以使用任意的Java方法。
  • 由于CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此它能够实现对方法的精细化控制。

总结

应用场景:

  • 保护目标对象。
  • 增强目标对象。

优点:

  • 代理模式能将代理对象与真实被调用的目标对象分离。
  • 一定程度上降低了系统的耦合程度,易于扩展。
  • 代理可以起到保护目标对象的作用。
  • 增强目标对象的职责。

缺点:

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

两种动态代理的对比:

  • JDK动态代理的特点:
    • 需要实现InvocationHandler接口, 并重写invoke方法。
    • 被代理类需要实现接口, 它不支持继承。
    • JDK 动态代理类不需要事先定义好, 而是在运行期间动态生成。
    • JDK 动态代理不需要实现和被代理类一样的接口, 所以可以绑定多个被代理类。
    • 主要实现原理为反射, 它通过反射在运行期间动态生成代理类, 并且通过反射调用被代理类的实际业务方法。
  • cglib的特点:
    • cglib动态代理中使用的是FastClass机制。
    • cglib生成字节码的底层原理是使用ASM字节码框架。
    • cglib动态代理需创建3份字节码,所以在第一次使用时会比较耗性能,但是后续使用较JDK动态代理方式更高效,适合单例bean场景。
    • cglib由于是采用动态创建子类的方法,对于final方法,无法进行代理