首先,代理模式的定义:为另一个对象提供一个替身或占位符以控制对这个对象的访问。
通过上述对“代理模式”的定义,可以知道: 代理类 是 真正该被访问的类 的替身。
举个生活中的例子,我现在想买口红, 没必要为了一只口红跑到国外去,而 “代购” ——代理购买,就可以实现购买的需要。对于我而言,只是 “买口红” 这一简单的操作,而对于代购而言,买口红之前,要办签证、出国、进入商城、找柜台;在买口红之后,还要开发票、给客户们发货、告知客户发货信息… …
从现实中代理的例子,可以看出代理模式的好处:增强方法。可以在不修改源码的情况下,增强一些方法(无侵入的代码扩展),有了代理模式,在方法的前后可以做任何想做的事情,甚至不调用方法也行。 这就是代理模式的好处,没理解之前我一直感到奇怪… … 既然静态代理里是通过反射产生了一个对象实例,进而调用实例方法;动态代理里是通过 handler 调用 invoke 进而调用实例方法,而且还需要 new 一个实际业务对象传进去,何必来个代理模式呢?
1.静态代理
首先提供一个核心操作接口,核心业务是买口红。
package Proxy.Bacis;
//核心操作接口
public interface ISubject {
//买口红是核心业务
public void buy();}
接下来提供真正要被访问的类和真实业务, RealSubject 就相当于 我,真实业务 就是 买口红。
package Proxy.Bacis;
//真实业务,真正做事的对象
class RealSubject implements ISubject
{
public void buy()
{System.out.println("买只 Dare You");}
}
接下来是代理类,也就是 “代购” ,在代理类的方法中实现扩充,这样用户测试类中通过反射机制调用这个代理类的方法即可:
package Proxy.Bacis;
//代理类,也要实现核心操作接口,因为它提供的方法是对核心业务的扩充。
class ProxySubject implements ISubject {
//拥有ISubject字段是为了调用它的 buy 方法,这是未扩充版本的。
private ISubject subject;
//传入真实业务类
public ProxySubject(ISubject iSubject) {
this.subject = iSubject;
}
public void prepare() {
System.out.println("进商场,找柜台。");
}
public void afterBuy() {
System.out.println("取发票,发货。");
}
//可以看到,在代理类中增强了方法。
public void buy() {
//之前
this.prepare();
//未扩充版本在此调用
this.subject.buy();
//之后
this.afterBuy();
}
}
工厂类,封装反射机制:
package Proxy.Bacis;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class Factory
{
//提供获取实例的方法,传入类名
public static<T> T getInstance(String className)
{ T t=null;
try {
t=(T)Class.forName(className).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return t;
}
//提供获取实例的方法,传入 代理类类名 和 真实业务类类名,
//将真实业务类实例 作为 代理类构造器 的参数
public static<T> T getInstance
(String proxyClassName,String realClassName)
{T t=null;
try
{
T realObj=getInstance(realClassName);
Constructor<?> constructor= Class.forName(proxyClassName).getConstructor(realObj.getClass().
getInterfaces());
t= (T) constructor.newInstance(realObj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return t;
}
}
用户测试类:
package Proxy.Bacis;
import java.lang.reflect.Constructor;
public class TestDemoP {
public static void main(String[] args) {
//通过代理生成的 代理类对象
ISubject iSubject1=Factory.getInstance("Proxy.Bacis.ProxySubject",
"Proxy.Bacis.RealSubject");
//代理对象调用 业务方法
iSubject.buy();
}
}
运行结果如下:
而静态代理存在这样的缺点:
因为 真实业务类 和 代理类 都实现了接口,那么,如果接口增加一个方法,所有实现类 即 真实业务类 和 代理类 都需要覆写这个方法,如果增加很多方法,就要挨个覆写,增加了代码维护的复杂度。
再有,每个代理类只能为一个接口服务,(因为要扩充接口中的功能,自然就要实现这个接口 )这样程序开发中必然会产生许多的代理类。
因此引入动态代理:
2.动态代理
动态代理不是针对接口产生代理类,而是只有一个 动态代理类。
先来了解两个方法:
(1)
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler 是动态代理实现的标识接口,只有实现此接口才具备动态代理功能。每个动态代理类都必须实现这个接口,每个代理实例都关联到了一个 handler 。分析该方法的参数:
在动态生成的代理类的任意方法中,都会间接调用 invoke 方法。
所以,动态代理类在实现接口时覆写 invoke 方法,需要调用 method 的 invoke 方法, public Object invoke(Object obj, Object... args)
需要传入真实对象 和 方法的参数,然后返回调用的结果。(为了完成对 被代理对象的方法 的拦截,需要传入真实对象实例,而方法参数也就是上图中的 arg 。) 在此之前、之后都可以实现方法的扩充,而 如果想压根儿不调用实际业务方法,在覆写的这个 invoke 中返回 null 即可。
(2)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
该方法返回的 是个在JVM运行时动态生成的一个对象,命名为 $Proxy 0。“0” 表示对象的标号。
了解这两个方法后,就就可以开始写 动态代理的代码了:
先提供核心操作接口:
package Proxy.Dynamic;
//核心操作接口
public interface ISubject {
//买口红是核心业务
public void buy(String msg,int num);
}
接下来提供真实业务类:
package Proxy.Dynamic;
public class RealSubject implements ISubject {
public void buy(String msg, int num) {
System.out.println("我要买"+num+"只"+msg);
}
}
然后,重头戏来啦,一个动态代理类,无论以后有多少个接口,只有这一个动态代理类:
package Proxy.Dynamic;
import java.beans.MethodDescriptor;
import java.lang.reflect.Proxy;
import java.lang.reflect.*;
//动态代理实现标识接口
public class ProxySubject implements InvocationHandler{
//这个就是真实业务类实例,需要传入到Method 类的 invoke 方法中
private Object target;
//实现真实对象的绑定处理,在用户测试中会传入真实对象实例的。
//同时返回代理对象
public Object bind(Object target)
{
this.target=target;
return
//产生动态代理类 Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
public void preHandle()
{System.out.println("ProxySubject 方法处理前");}
public void afterHandle()
{System.out.println("ProxySubject 方法处理后");}
//实现方法的扩充
public Object invoke(Object proxy, Method method,Object[] args ) throws InvocationTargetException, IllegalAccessException {
//前
this.preHandle();
//调用接口中的某个方法,需要传入真实对象 和 方法参数
Object ret=method.invoke(this.target,args);
//后
this.afterHandle();
//返回方法调用结果
return ret;
}
}
接下来提供用户测试类:
package Proxy.Dynamic;
public class TestDemo {
public static void main(String[] args) {
//获取动态代理对象
ISubject iSubject=(ISubject) new ProxySubject().bind(new RealSubject());
//提供动态代理对象调用接口的方法
iSubject.buy("Dare You",20);
}
}
我注意到 2 个事情:
(1)覆写InvocationHandler 接口里的 invoke 方法时,调用Method 类的invoke 方法时,传入的参数是真实对象 和 方法参数,并没有指定方法名,那如果核心业务接口里有很多方法,到底执行哪个呢?(感觉会一起都执行了… …)
于是我在接口中新增 3 个方法:
package Proxy.Dynamic;
//核心操作接口
public interface ISubject {
//买口红是核心业务
public void buy(String msg,int num);
//新值方法1
public void Method1();
//新值方法2
public void Method2();
//新值方法3
public void Method3();
}
并在真实类中实现它们:
(2)
newProxyInstance方法传入的是接口组,如果真实业务类实现了很多个接口,那么动态代理类调用方法时会受影响吗?
相比静态代理,一个非常显著的优点是动态代理可以在自定义调用处理器统一处理委托类的方法,而不必一个个编写。
把整一块儿都当作一个整体,在这个整体之前、之后扩充,(这样就弥补了 静态代理如果给接口加很多方法的话,实现接口的真实业务类 和 代理类要挨个覆写那些方法 的缺陷)如图所示: