设计模式--结构型--代理模式
代理模式
概述
由于某些原因需要给某对象提供一个代理以控制该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
java中的代理机制分为静态代理和动态代理
- 静态代理:代理类在在编译期就生成
- 动态代理:代理类在java运行时动态生成。
- JDK代理
- CGLib代理
结构
代理模式分为三种角色:
- 抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理案例:卖车票
市面上任何卖火车票软件都是通过12306来买的,这就是一个典型的代理模式,12306是目标对象,其他售票软件是代理对象。
java
public class ProxyPoint implements SellTicket{
// 声明12306类对象
private TrainStation trainStation = new TrainStation();
@Override
public void sell() {
System.out.println("其它app买票---可以增强");
trainStation.sell();
}
}
java
/**
* 卖火车票的接口
*/
public interface SellTicket {
void sell();
}
java
public class TrainStation implements SellTicket{
@Override
public void sell() {
System.out.println("12306卖火车票");
}
}
java
public class Test01 {
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sell();
}
}
从上面代码中可以看出测试类是直接访问ProxyPoint类对象,也就是说ProxyPoint作为目标访问对象和目标对象的中介,同时也对sell方法进行了增强。
jdk动态代理
接下来使用动态代理实现上面案例,java中提供了一个动态代理类Proxy,Proxy并不是我们上面说的代理对象类,
而是提供了一个创建代理对象的静态方法(newProxyInstance)来获取代理对象。
java
/**
* 获取代理对象的工厂类
* 代理类也实现了对应的接口
*/
public class ProxyFactory {
// 声明目标对象
private TrainStation station = new TrainStation();
public SellTicket getProxyObject(){
/**
* ClassLoader loader 类加载器,用于加载代理类。可以通过目标对象获取类加载器
* Class<?>[] interfaces 代理类实现的接口的字节码对象
* InvocationHandler h 代理对象的调用处理程序
*/
SellTicket proxyObject = (SellTicket) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
/**
* Object proxy:代理对象,和proxyObject对象是同一个对象
* Method method:对接口中的方法进行封装的method对象
* Object[] args:调用方法的实际参数
* 返回值:方法的返回值
*/
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//
System.out.println("代售app---增强");
// 执行目标对象的方法
return method.invoke(station, args);
}
}
);
return proxyObject;
}
}
java
/**
* 卖火车票的接口
*/
public interface SellTicket {
void sell();
}
cglib代理
java
/**
* 代理对象工厂,用来获取代理对象
*/
public class ProxyFactory implements MethodInterceptor {
private TrainStation station = new TrainStation();
public TrainStation getProxyObject(){
// 创建Enhancer对象,类似jdk代理中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
// 设置回调函数
enhancer.setCallback(this);
// 创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理");
// 要调用目标对象的方法
TrainStation invoke = (TrainStation) method.invoke(station, objects);
return invoke;
}
}
java
public class TrainStation {
public void sell() {
System.out.println("12306卖火车票");
}
}
java
public class Test02 {
public static void main(String[] args) {
// 创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
// 获取代理对象
TrainStation proxyObject = factory.getProxyObject();
// 调用代理对象中的sell方法
proxyObject.sell();
}
}
三种代理对比
- jdk代理和cglib代理
-
使用CGLib实现动态代理,cglib底层采用ASM字节码生产框架,使用字节码技术生成代理类,在jdk1.6之前比使用java反射效率要高。
唯一需要注意的是:cglib不能对声明为final的类或方法进行代理,因为cglib原理是动态生成被代理的子类。
在jdk1.8对jdk动态代理优化后,jdk代理效率高于cglib代理,如果有接口使用jdk代理,没有接口使用cglib代理。
-
- 动态代理和静态代理
- 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法处理(InvocationHandler.invoke)。这样在接口
方法数量比较多的时候,我们可以灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式处理实现所有类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
- 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法处理(InvocationHandler.invoke)。这样在接口
优缺点
- 优点:
- 代理模式在客户端与目标对象之间起到了一个中介作用和保护作用
- 代理对象可以扩展目标对象的功能
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
- 缺点:
- 增加了系统的复杂度
使用场景
- 远程代理
- 本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,
我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
- 本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,
- 防火墙代理
- 将浏览器配置成使用代理功能时,防火墙就可以将浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把他转给你的浏览器。
- 保护代理
- 控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。