定义
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
角色和类图
角色
代理(Proxy)模式分为三种角色:
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
类图
实现
静态代理
/**
* @author hao
* 抽象主题类
*/
public interface NBA {
void playBasketBall();
}
/**
* @author hao
* 具体主题类
*/
public class KouBe implements NBA {
@Override
public void playBasketBall() {
System.out.println("科比凌晨4点在训练...,科比进入湖人打球...");
}
}
/**
* @author hao
* 代理类 与具体主题类实现了同一的接口
*/
public class KouBeAgent implements NBA{
private KouBe kouBe = new KouBe();
@Override
public void playBasketBall() {
System.out.println("科比经纪人为科比安排与湖人签订合同...");
kouBe.playBasketBall();
}
}
测试代码:
public class MainTest {
public static void main(String[] args){
KouBeAgent kouBeAgent = new KouBeAgent();
kouBeAgent.playBasketBall();
}
}
结果:
科比经纪人为科比安排与湖人签订合同…
科比凌晨4点在训练…,科比进入湖人打球…
静态代理模式比较简单,与装饰器模式有些类似。具体区分的话,也可以这么理解:
- 目的不同:静态代理的目的是为了隐藏对象,而装饰器模式是为了增强对象。(通过目标对象的传入方式)
不钻牛角尖的话,其实2个模式可以看作是相同的。都是为了增强目标对象。这个大家要了解下。学设计模式不要钻牛角尖…
JDK动态代理
JDK动态代理提供了创建代理类的方法,无需我们创建代理类。
Proxy proxy = Proxy.newProxyInstance(
ClassLoader loader, //目标类的类加载器
Class<?> insterfaces, //目标类所实现的接口
InvocationHandler h // 代理对象的调用处理程序);
通过上述代码,我们可以看出,JDK动态代理要求目标类必须实现接口。我们把上述静态代理的代码修改下,使用动态代理实现。
具体代码如下:
public interface NBA {
void playBasketBall();
}
/**
* @author hao
* 具体主体类
*/
public class KouBe implements NBA {
@Override
public void playBasketBall() {
System.out.println("科比凌晨4点在训练...,科比进入湖人打球...");
}
}
/**
* @author hao
*/
public class ProxyFactory {
private KouBe kouBe;
public ProxyFactory(KouBe kouBe){
this.kouBe = kouBe;
}
public NBA getProxyInstance() {
NBA nba = (NBA) Proxy.newProxyInstance(
kouBe.getClass().getClassLoader(),
kouBe.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("科比经纪人为科比安排与湖人签订合同...");
Object invoke = method.invoke(kouBe, args);
return invoke;
}
}
);
return nba;
}
}
测试代码:
/**
* @author hao
*/
public class MainTest {
public static void main(String[] args){
ProxyFactory proxyFactory = new ProxyFactory(new KouBe());
NBA proxyInstance = proxyFactory.getProxyInstance();
proxyInstance.playBasketBall();
}
}
结果:
科比经纪人为科比安排与湖人签订合同…
科比凌晨4点在训练…,科比进入湖人打球…
可以得到同样的结果。我们并没有创建代理类。这就是动态代理。上述说到JDK动态代理需要目标对象实现接口。这是为什么呢???
我们来分析下:
我们通过Arthas jad反编译下代理类 对象com.sun.proxy.$Proxy0:
简洁的代码如下:
public final class $Proxy0 extends Proxy implements NBA {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
m3 = Class.forName("com.atguigu.mydesign.structural.proxy.jdk.NBA").getMethod("playBasketBall", new Class[0]);
}
//代理对象执行的目标方法
public final void playBasketBall() {
this.h.invoke(this, m3, null);
return;
}
}
通过代理对象的目标方法可以看出,调用代理对象的playBasketBall方法时,实际上是调用的InvocationHandler接口的实现类中的invoke方法,invoke在通过反射执行了真实对象类中的playBasketBall方法。
CGLIB动态代理
CGLIB动态代理与JDK动态代理的好处在于不需要实现接口。用CGLIB来实现上述的例子。
代码如下:
/**
* @author hao
*/
public class ProxyFactory implements MethodInterceptor {
private KouBe kouBe;
public ProxyFactory(KouBe kouBi){
this.kouBe = kouBi;
}
public KouBe getProxyInstance(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(kouBe.getClass());
enhancer.setCallback(this);
return (KouBe) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("科比经纪人为科比安排与湖人签订合同...");
method.invoke(kouBe, objects);
return null;
}
}
/**
* @author hao
* 具体主体类
*/
public class KouBe {
public void playBasketBall() {
System.out.println("科比凌晨4点在训练...,科比进入湖人打球...");
}
}
测试结果:
科比经纪人为科比安排与湖人签订合同…
科比凌晨4点在训练…,科比进入湖人打球…
CGLIB动态代理是通过创建一个具体类的子类。然后通过字节码的方式操作调用父类方法时拦截处理。