1.代理模式
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
1.1 组成结构
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
1.2 场景设计
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:
1.3 实现
1.3.1 静态代理
静态代理就是让代理类和被代理类实现同一个接口,然后在代理类中聚合被代理类。在代理类对方法增强后再通过聚合的被代理类对象调用目标方法;
//卖票接口
public interface SellTickets {
void sell();
}
//火车站(被代理类) 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火车站卖票");
}
}
//代售点(代理类)
public class ProxyPoint implements SellTickets {
private TrainStation station = new TrainStation();
public void sell() {
//此处对代理方法做一些增强
System.out.println("代理点收取一些服务费用");
station.sell();
}
}
//测试类
public class Client {
public static void main(String[] args) {
ProxyPoint pp = new ProxyPoint();
pp.sell();
}
}
1.3.2 jdk动态代理
Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。
用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 : 代理对象调用接口方法时传递的实际参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk动态代理实现方法增强");
//执行真实对象
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}
//测试类
public class Client {
public static void main(String[] args) {
//获取代理对象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
以下为jdk动态生成的代理类反编译后的代码(删掉了tostring等无用方法代码)
package com.sun.proxy;
import com.guo.structural_patterns.proxy.static_proxy.SellTicket;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements SellTicket {
//要代理的方法
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//代理类中的sell方法
public final void sell() throws {
try {
//此处调用我们再InvocationHandler类中重写的invoke方法
//super代表proxy类,h表示InvocationHandler即我们在newProxyInstance方法中传入的对象
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
//通过静态代码块拿到sell方法(method)实例
m3 = Class.forName("com.guo.structural_patterns.proxy.static_proxy.SellTicket").getMethod("sell");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从上面的类中,我们可以看到以下几个信息:
- 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
- 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
jdk动态代理执行流程如下:
1. 在测试类中通过代理对象调用sell()方法
2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
1.3.3 CGLIB动态代理
JDK的动态代理机制只能代理实现了接口的类。而不能实现接口的类就不能使用JDK的动态代理,CGLIB是针对类来实现代理的,它的原理是对指定目标类生成一个子类,并覆盖其中的方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${version}</version>
</dependency>
cglib代理实现
public class CglibProxyFactory implements MethodInterceptor {
private Object target;
public CglibProxyFactory(Object target){
this.target = target;
}
public Object getProxyInstance(){
Enhancer enhancer = new Enhancer();
//设置父类,即设置被代理对象
enhancer.setSuperclass(target.getClass());
//设置回调方法对象,即增强方法
enhancer.setCallback(this);
//设置是否缓存,当多次重复调用的时候,利用缓存就不用重新创建代理类 提高效率,默认开启缓存
enhancer.setUseCache(true);//默认为true;
Object o = enhancer.create();
return o;
}
/**
* intercept 参数介绍:
* o 代理对象实例
* method 当前要执行的方法
* objects 执行方法的参数
* methodProxy 方法的代理对象,每一个普通的方法在代理类中都有两个对应的方法,如sell()和CGLIB$sell$0();
* methodProxy对象代理了这两个方法,methodProxy.invoke()就是执行sell()方法,而methodProxy.invokeSuper()就行执行
* CGLIB$sell$0()方法。我们通常使用methodProxy.invokeSuper(o,objects)来执行被代理对象的方法,注意实例o是代理对象的实例,
* 而不是被代理对象,是代理对象的直接调用。不需要反射,所以效率较反射高;
**/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//增强逻辑
System.out.println("cgLib动态代理方法增强");
//利用反射执行,需要被代理对象target实例,效率低,不推荐使用
//method.invoke(target,objects);
//用此方法直接执行目标方法,不需要反射,效率高,也不需要被代理对象(target)的实例。推荐使用
methodProxy.invokeSuper(o,objects);
return null;
}
}
1.4 Java动态代理和cglib比较
- 生成代理类技术不同
jdk动态代理:jdk自带类ProxyGenerator生成class字节码
cglib:通过ASM框架生成class字节码文件 - 生成代理类的方式不同
jdk动态代理:代理类继承java.lang.reflect.Proxy,实现被代理类的接口
cglib:代理类继承被代理类,实现net.sf.cglib.proxy.Factory - 生成类数量不同
jdk动态代理:生成一个proxy类
cglib:生成一个proxy,两个fastclass类 - 调用方式不同
jdk动态代理:代理类->InvocationHandler->反射调用被代理类方法
cglib:代理类->MethodInterceptor->调用索引类invoke->直接调用被代理类方法 - 性能比较
在 jdk6之前比使用 Java反射效率要高,在 jdk6、jdk7、jdk8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率 高于 CGLIB 代理效率。只有当进行大量调用的时候,jdk6 和 jdk7 比 CGLIB 代理效率低一点,但是到 jdk8 的时候,jdk 代理效率高于 CGLIB 代理,总之,每一次 jdk 版本升级,JDK 代理效率 都得到提升,而 CGLIB 代理效率 确有点跟不上步伐。