代理模式
一、引言
在 Java 开发中,代理模式是一种非常重要的设计模式,它为其他对象提供一种代理,以控制对这个对象的访问,在访问对象和目标对象之间起到中介作用。Java 中的代理按照代理类生成时机不同分为静态代理和动态代理,而动态代理又有 JDK 代理和 CGLib 代理两种。本文将详细探讨代理模式的概念、角色、静态代理与动态代理的实现以及它们的优缺点和对比。
二、代理模式的角色
- 抽象角色(Subject):通过接口或抽象类声明真实角色和代理对象实现的业务方法。
- 真实角色(Real Subject):实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理角色(Proxy):提供了与真实角色相同的接口,其内部含有对真实角色的引用,它可以访问、控制或扩展真实角色的功能。
三、静态代理
静态代理的定义:静态代理就是指在给一个类扩展功能的时候,书写一个静态的类,相当于在之前的类上套了一层,在不改变之前的类的前提下对原有功能进行扩展。静态代理需要代理对象和目标对象实现一样的接口。
// 高铁站接口,有卖票功能
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();
@Override
public void sellTickets() {
// 代售加收 5%手续费
System.out.println("代售加收 5%手续费");
// 调用目标对象的卖票方法
station.sellTickets();
}
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
// 代售加收 5%手续费
// 西安高铁站卖票啦
proxyPoint.sellTickets();
}
}
静态代理的优缺点:
- 优点:实现简单,容易理解,只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。
- 缺点:代理类和目标类必须有共同接口 (父类),并且需要为每一个目标类维护一个代理类,当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动,目标对象和代理对象都需要作出调整。
四、动态代理
动态代理的定义:代理类在代码运行时创建的代理称之为动态代理。动态代理中代理类并不是预先在 Java 代码中定义好的,而是运行时由 JVM 动态生成,并且可以代理多个目标对象。
- JDK 动态代理
JDK 动态代理是 Java JDK 自带的一个动态代理实现,位于 java.lang.reflect
包下。
// 高铁站接口,有卖票功能
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;
/**
* 获取代理对象的方法
* @param trainStation 目标对象
* @return 代理对象
*/
public TrainStation getProxyObject(TrainStation trainStation) {
this.trainStation = trainStation;
Class<? extends TrainStation> clazz = trainStation.getClass();
return (TrainStation) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代售高铁票收取 5%手续费
System.out.println("代售高铁票收取 5%手续费");
// 调用目标对象的方法
return method.invoke(this.trainStation, args);
}
}
JDK 动态代理的优缺点:
- 优点:使用简单、维护成本低;Java 原生支持,不需要任何依赖;解决了静态代理存在的多数问题。
- 缺点:由于使用反射,性能会比较差;只支持接口实现,不支持继承,不满足所有业务场景。
- CGLib 动态代理
CGLib 是一个强大的、高性能的代码生成库。它可以在运行期扩展 Java 类和接口,其被广泛应用于 AOP 框架中(Spring、dynaop)中,用以提供方法拦截。CGLib 比 JDK 动态代理更强的地方在于它不仅可以接管 Java 接口,还可以接管普通类的方法。
首先在项目中引入 CGLib 依赖:
<!-- 先引入 cglib 包 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib-version}</version>
</dependency>
代理类代码如下:
// 代售点卖票(代理类)
public class ProxyPoint implements MethodInterceptor {
/**
* 获取代理对象的方法
* @param trainStation 目标对象的类
* @return 代理对象
*/
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 {
// 代售高铁票收取 5%手续费
System.out.println("代售高铁票收取 5%手续费");
// 调用父类(目标对象)的方法
return methodProxy.invokeSuper(o, objects);
}
}
五、总结
- 应用场景:
- 保护目标对象。
- 增强目标对象。
- 优点:
- 代理模式能将代理对象与真实被调用的目标对象分离。
- 一定程度上降低了系统的耦合程度,易于扩展。
- 代理可以起到保护目标对象的作用。
- 增强目标对象的职责。
- 缺点:
- 代理模式会造成系统设计中类的数目增加。
- 在客户端和目标对象之间增加了一个代理对象,请求处理速度变慢。
- 增加了系统的复杂度。
- 两种动态代理的对比:
- JDK 动态代理的特点:
- 需要实现
InvocationHandler
接口,并重写invoke
方法。 - 被代理类需要实现接口,它不支持继承。
- JDK 动态代理类不需要事先定义好,而是在运行期间动态生成。
- JDK 动态代理不需要实现和被代理类一样的接口,所以可以绑定多个被代理类。
- 主要实现原理为反射,它通过反射在运行期间动态生成代理类,并且通过反射调用被代理类的实际业务方法。
- 需要实现
- CGLib 的特点:
- CGLib 动态代理中使用的是
FastClass
机制。 - CGLib 生成字节码的底层原理是使用 ASM 字节码框架。
- CGLib 动态代理需创建 3 份字节码,所以在第一次使用时会比较耗性能,但是后续使用较 JDK 动态代理方式更高效,适合单例 bean 场景。
- CGLib 由于是采用动态创建子类的方法,对于
final
方法,无法进行代理。
- CGLib 动态代理中使用的是
- JDK 动态代理的特点: