一、代理模式
一句话概括
教你将类和对象结合再一起形成一个更强大的结构.
1.1、代理模式概述
由于一些原因,我们不能直接访问目标对象,这时候需要提供一个目标对象的代理,通过代理来实现对目标对象的访问.
比如,我要去买火车票,就需要在火车触发的前几天坐车去火车站买票,相当的麻烦. 但是还有一种方式,就是我们可以直接在 12306 网站上直接进行买票,十分方便,这里的 12306 网站就相当于 代理.
代理模式分为以下三种角色:
- 抽象主题类:通过接口或者抽象类声明真实主题要干的事情(业务).
- 真实主题类:实现了抽象主题中的具体业务.
- 代理类:内部包含 真实主题类 的实例对象,可以访问和扩展真实主题的功能.
1.2、静态代理
静态代理表示在编译期间就创建好了代理对象.
这里还是使用刚刚提到的火车站买票的案例来实现.
/**
* 抽象主题:卖票接口
*/
public interface SellTickets {
void sell();
}
/**
* 真实主题类:火车站
*/
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站卖票");
}
}
/**
* 代理类:代售点(例如 12306 网站)
*/
public class ProxyPoint {
//在编译时期对象就创建好了,因此是静态代理
private TrainStation trainStation = new TrainStation();
public void sell() {
System.out.println("方便了你,我自然要收取代理费用");
trainStation.sell();
}
}
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
//直接就可以通过代理类买到票,不用再去火车站了
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sell();
}
}
这里就可以看出测试类是通过 ProxyPoint 类访问到的目标方法,同时也对 sell 方法进行了增强(例如:收取代理费用).
Ps:JDK 动态代理要求必须要定义接口,因为他就是对接口进行代理的.
1.3、JDK 动态代理
动态代理就是在程序运行期间,动态的在内存中创建出代理类(静态代理是在编译期间创建出代理类).
Java 中提供类一个动态代理类 Proxy(并不是 静态代理 中所说的代理对象类),提供了创建代理对象的静态方法(Proxy.newProxyInstance 方法)来获取代理对象.
这里继续使用买火车票案例
/**
* 抽象主题:卖票接口
*/
public interface SellTickets {
void sell();
}
/**
* 真实主题类:火车站
*/
public class TrainStation implements SellTickets {
@Override
public void sell() {
System.out.println("火车站卖票");
}
}
/**
* 代理类工厂(不是代理类):用来创建代理对象
*/
public class ProxyFactory {
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
//使用 Proxy 获取代理对象
//newProxyInstance 参数如下:
//ClassLoader loader: 类加载器,此处填写真实对象的类加载器,是用来加载代理类的.
//Class<?>[] interfaces: 填写真实对象实现的接口
//InvocationHandler h: 代理对象要处理的业务逻辑
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy: 代理对象,此方法中一般不用
//method: 对应于在代理对象上调用的接口方法的 Method 实例
//args: 代理对象调用接口方法时传递的实际参数
System.out.println("收取代理费用(JDK 动态代理)");
//执行真实对象
Object result = method.invoke(station, args);
return result;
}
}
);
return sellTickets;
}
}
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory();
//获取代理对象
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
执行流程如下:
- 在测试类中通过代理对象调用 sell 方法.
- 此处执行的是代理类中的 sell 方法,因为此方法的对象来自于 JDK 的 Proxy.newProxyInstance 方法生成的.
- 代理类中的 sell 方法有调用了 InvocationHandler 接口的子实现类对象的 invoke 方法.
- invoke 方法底层(这里的源码可以通过阿里巴巴开源的诊断工具 Arthas 看到代理类的结构)通过反射执行了真实主题类中的 sell 方法.
1.4、CGLIB 动态代理
同样是买火车票案例,如果没有定义 SellTickets 这个 抽象主题类,只定义了 TrainStation 火车列类,很显然 JDK 代理就无法使用了,因为 JDK 动态代理要求必须定义接口,对接口进行代理.
CGLIB 是一个功能强大,高性能的代码生成包. 他为没有实现接口的类提供了代理,给 JDK 动态代理提供了很好的补充.
CGLIB 是第三方提供的包,因此需要引入 jar 包.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
具体实现如下:
/**
* 火车站
*/
public class TrainStation {
public void sell() {
System.out.println("火车站卖票");
}
}
public class ProxyFactory implements MethodInterceptor {
private TrainStation station = new TrainStation();
public TrainStation getProxyObject() {
//1.创建 Enhancer 对象,类似于 JDK 动态代理 Proxy 类
Enhancer enhancer = new Enhancer();
//2.设置父类(代理类的父类就是被代理类)的字节码对象
enhancer.setSuperclass(station.getClass());
//3.设置回调函数
enhancer.setCallback(this);
//4.创建代理对象
TrainStation obj = (TrainStation) enhancer.create();
return obj;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//这里的参数就和 JDK 动态代理的 invoke 很像了
//o: 代理对象
//method: 真实主题中的方法的 Method 实例
//objects: 实际参数
//methodProxy: 代理对象中的 method 实例
System.out.println("收取代理费用(CGLIB 动态代理)");
Object result = method.invoke(station, objects);
return result;
}
}
public class Client {
public static void main(String[] args) {
//创建代理类工厂
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = factory.getProxyObject();
//调用代理对象中的 sell 方法
proxyObject.sell();
}
}
1.5、对比三种代理
1.5.1、jdk 代理 VS CGLIB 代理
- CGLIB 底层使用 ASM 字节码生成框架(这个不用去了解),使用字节码技术生成代理类,在 JDK 1.7 之前要比 Java 反射效率要高. 但是 CGLIB 不能对声明为 final 的类和方法进行处理,因为 CGLIB 的原理就是动态生成被代理类(真实主题类)的子类.
- 在 JDK 1.8 之后,JDK 的代理效率要高于 CGLIB. 因此只要是有接口的,就是用 JDK 动态代理,没有接口的使用 CGLIB 代理.
1.5.2、动态代理 VS 静态代理
- 动态代理最大的好处就是 抽象主题类(接口) 中声明的所有方法都被转移到了一个地方集中处理(InvocationHandler.invoke). 这样,当接口的方法数目比较多的时候,就不需要像静态代理那样每一个方法都需要在代理类中再中转一次.
- 将来如果接口增加一个方法,静态代理除了所有实现类(真实主题类)需要实现这个方法外,还需要在代理类中实现此方法. 提高了代码的维护成本. 静态代理就不会出现此问题.
1.6、优缺点
优点
中介,保护:代理模式在客户端和目标对象之间起到一个中介的作用和保护目标对象的作用.
扩展:代理对象可以扩展目标对象的功能.
解耦合:代理模式对客户端和目标对象进行解耦.
缺点:
实现起来相对复杂.
1.7、使用场景
- 远程代理:在远程调用时,为了良好的代码设计和可维护性,可以将网络通信部分隐藏起来,只暴露非本地服务器一个接口(代理),通过这接口即可访问到远程服务提供的功能,不必关心过多的通信实现细节.
- 防火墙代理:当浏览器配置成代理功能时,防火墙就会将浏览器的请求转给网络,当网络返回相应时吗,代理服务器再把他转给你的浏览器.
- 权限代理:控制一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限.