在java中代理模式有两种,分别是静态代理与动态代理。
一. 静态代理
目标对象在一开始就能够具体确定的类(被代理对象),则是静态代理。
与日常生活中常见使用的中介服务很相似,以一个租房服务作为例子切入理解代理模式的概念。
刚毕业的小明需要租房,但由于工作忙无法亲自找房源,所以直接向房屋中介人委托找房源的事宜。小明只需要等候房屋中介人的反馈通知,最后再决定租不租。而这个找房子的行为由原本小明需要处理的,转发到房屋中介去处理了。这就符合代理模式的概念。
在案例中,有与代理模式对应三个概念。
目标对象(被代理对象):小明,小明需要找房子。
代理对象:房屋中介人,可以帮人找房子。
公共对外接口:找房子的行为。
不是直接让小明执行找房子的行为,而是通过房屋中介人根据小明的意愿来找房子。
简单理解代理模式可以如下概括:
定义(what):
不直接使用目标对象方法,而是通过代理对象来控制目标对象方法的使用。
包括以下三种角色:
-
公共对外接口(包括代理类需要使用目标对象的方法)
-
目标对象(真正实现业务逻辑的类,对调用着来说是不透明的,可以不暴露)
-
代理对象(目标对象作为属性,封装目标对象)。
使用场景(where):
-
一般只用于很复杂的对象或者耗费较多时间构造的类需要使用代理模式。
-
用于需要面向切面编程时使用,在实现原业务逻辑的基础上,可以有统一附加的行为(权限验证、日志记录、监听拦截等)。
-
隐藏方法调用的细节。
-
提升系统性能,封装目标对象可以达到延迟加载的目的。
益处(why):
- 代理对象 对 目标对象原有的业务方法能附加额外的行为。从而使代码的修改更符合开闭原则,能避免修改目标对象的修改。增加一个新的代理对象,添加新的附加行为,对原来已存在的代码不需要额外修改。
- 延迟加载。直接使用目标对象在程序启动时就会初始化目标对象,而使用代理模式后,启动时时只需要初始化轻量级的的代理对象,只有在需要操作目标对象的业务方法时才会初始化目标对象,避免在程序初始化时加重系统压力。
- 对外提供统一的接口方法,隐藏具体的实现,避免访问目标对象过于复杂,简化使用逻辑。
如何实现(How):
-
声明一个公共方法接口,也就是需要被代理的方法的声明;
-
声明真实实现公共接口的类,也就是目标对象,实现所有需要被代理方法:
-
声明代理类,该类需要将目标对象作为属性,并且在代理类初始化就要传入一个目标对象作为参数。
-
在执行代理方法时,直接创建一个代理类,并将代理类转为接口,调用接口中的方法即可。
以小明找房屋中介为例,类图关系展示如下:
代码实现
1.公共对外需要代理的方法声明
public interface ProxyMethod {
void findHouse();
}
2.目标对象
public class Tenant implements ProxyMethod{
public void findHouse() {
System.out.println("正在找房子......");
}
}
3.代理对象
public class MediaryA implements ProxyMethod{
private Tenant tenant;
public MediaryA(Tenant tenant){
this.tenant = tenant;
}
public void findHouse() {
System.out.println("--- 真实方法调用前 ---");
tenant.findHouse();
System.out.println("--- 真实方法调用后---");
}
}
4.具体调用过程
public class Main {
public static void main(String[] args) {
/**
* 静态代理 实例
*/
Tenant xiaoming = new Tenant();
ProxyMethod proxyMethod = new MediaryA(xiaoming);
proxyMethod.findHouse();
}
}
二.动态代理
目标对象不能一开始就确定的具体的类,则是动态代理。(在以下例子中,代理对象中传入的目标对象是一个Object,如果换成其他的例子,这个代理对象依旧可以保持不变。可以说无论是什么业务变化,都与代理对象无关,只与目标对象与公共对外接口的定义有关,整理后发现以下例子并没有充分体现这个特性。)
依旧以小明找房子为例,房屋中介人一般都只负责一个片区,如果要找多个区域的房子,那么就要找不同区域的房租中介代理找房子事宜。于是实现模型可能变更成这样:
同一个代理方法,却产生了多个代理类,如果需要对代理方法作出更改,就需要对所有的类都作出调整,这样不符合软件的设计原则--开闭原则(对修改关闭,对拓展开放)。而使用动态代理模型,能将代理类固定为一个,更容易对新增的目标对象做拓展。通过一个动态代理类,如果有新的代理行为则在公共接口中定义,并在目标对象中实现即可。可以动态加入新的代理行为,而不需要修改代理对象的原代码。
如果小明不再找某个房屋中介人,而是直接找一个房屋中介机构,找各个区域的房子都直接交由中介机构来处理,那么代理模型可以简化成这样:
动态代理与静态代理的实现流程类似,但代理对象的生成需要依赖于Proxy类(JDK 1.3中就提供一个专用于动态代理的类java.lang.reflect.Proxy)。
在该类中,有两个最为核心的实现:
1.决定使用哪个目标对象被委托代理的创建实现
Proxy类的静态获取代理对象实例的方法:
/* @param 目标对象的类加载器
* @param 代理类需要实现的接口列表
* @param 关联的InvocationHandler的实现类
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
2.用来定义具体调用逻辑的接口,InvocationHandler接口。
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都必须要有一个关联的InvocationHandler实现;在代理实例调用方法时,方法调用将调用关联的InvocationHandler中的invoke方法。
/**
* @param proxy 代理类代理的真实代理对象com.sun.proxy.$Proxy0
* @param method 调用某个对象真实的方法的Method对象
* @param args 代理对象方法传递的参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args)
以小明找房屋中介机构为例,作出以下代码实现:
1.公共对外需要代理的方法声明
public interface ProxyMethod {
void findHouse();
void findAreaAHouse();
void findAreaBHouse();
void findAreaCHouse();
}
2.目标对象
public class Tenant implements ProxyMethod{
public void findHouse() {
System.out.println("找房子......");
}
public void findAreaAHouse() {
System.out.println("需要找A区的房子......");
}
public void findAreaBHouse() {
System.out.println("需要找B区的房子......");
}
public void findAreaCHouse() {
System.out.println("需要找C区的房子......");
}
}
3.代理对象
public class MediaryOrg implements InvocationHandler {
private Object realObj;
//必须传入一个真实的对象 这里使用Object即可传入任意子类
public MediaryOrg(Object realObj){
this.realObj = realObj;
}
public Object getProxyObj(){
return Proxy.newProxyInstance(
realObj.getClass().getClassLoader(),
realObj.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打电话给中介机构询问找房子事宜");
Object returnObj = method.invoke(realObj, args);
System.out.println("中介机构已经找好了房子");
System.out.println("------------------");
return returnObj;
}
}
4.具体调用过程
public class Main {
public static void main(String[] args) {
/** 动态代理 */
MediaryOrg org = new MediaryOrg(new Tenant());
ProxyMethod proxyMethod = (ProxyMethod) org.getProxyObj();
proxyMethod.findHouse();
proxyMethod.findAreaAHouse();
proxyMethod.findAreaBHouse();
proxyMethod.findAreaCHouse();
}
}