代理模式
概念
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
实例
我们通过案例来感受一下静态代理。
【例】火车站卖票
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。
静态代理
火车站
public class TrainStation implements SellTickets{
@Override
public void sell() {
System.out.println("火车站卖票");
}
}
售票接口
public interface SellTickets {
void sell();
}
代售点
public class ProxyPoint implements SellTickets {
private TrainStation station = new TrainStation();
@Override
public void sell() {
System.out.println("代售点收取一些服务费用");
station.sell();
}
}
主要测试方法
public class Test {
public static void main(String[] args) {
ProxyPoint proxyPoint = new ProxyPoint();
proxyPoint.sell();
}
}
JDK动态代理
售票接口和火车站类不变
代理工厂代码
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() {
/*
InvocationHandler中invoke方法参数说明:
proxy : 代理对象
method : 对应于在代理对象上调用的接口方法的 Method 实例
args : 代理对象调用接口方法时传递的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代售点收取额外服务费用(JDK动态代理)");
//执行真实对象
return method.invoke(station, args);
}
}
);
return sellTickets;
}
}
测试代码
public class Test {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
SellTickets proxyObject = proxyFactory.getProxyObject();
proxyObject.sell();
}
}
- 代理工厂不是代理类,代理类是运行过程中动态在内存中创建的。
- 代理类实现了方法接口。
- 代理类将InvocationHandler该匿名内部类对象传递给了父类。
执行过程如下:
程序调用代理类的sell方法,调用的是在代理类中重写了的sell方法,sell方法执行了InvocationHandler中的方法处理器的逻辑,执行完之后,又调用回目标类的sell方法。
CGLib动态代理
如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。所以CGLib适用于没有接口的类,但是他是第三方提供的,所以我们要引入第三方的依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
火车站代码不变,不需要方法接口
代理工厂
public class ProxyFactory implements MethodInterceptor {
private TrainStation station = new TrainStation();
public TrainStation getProxyObject() {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer = new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(station.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TrainStation trainStation = (TrainStation) enhancer.create();
return trainStation;
}
/**
intercept方法参数说明:
o : 代理对象
method : 真实对象中的方法的Method实例
args : 实际参数
methodProxy :代理对象中的方法的method实例
*/
@Override
public TrainStation intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
TrainStation result = (TrainStation) method.invoke(station, objects);
return result;
}
}
测试方法
public class Test {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory proxyFactory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = proxyFactory.getProxyObject();
proxyObject.sell();
}
}
三种代理的对比
- jdk代理和CGLIB代理
到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
-
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 增加了系统的复杂度;
使用场景
- 远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
-
防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
-
保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
-
智能指引代理
引用对象时,代理类去计算引用次数,如果一直没有引用,可以去释放他;或者第一次引用时,加载进内存;或者在访问前,判断是否被锁定,防止修改;其实都是在加强功能。