代理模式
一、什么是代理
顾名思义,代理就是代替别人对一些事物进行处理,真实角色将业务交给代理人去完成。举个例子:房东A想要进行房间出租,但他觉得房子出租太麻烦不想等,所以他将房子就委托给中介(Proxy),让中介去完成相关的业务,这个过程就叫代理。
这个过程中用户直接找中介租房,而不是房东
二、代理模式
我们首先对代理模式中的角色进行解析:
- 抽象角色:一般使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实对象,代理真实对象后,我们会继续做一些附属操作。
代理模式就是将代理这过程体现的思想运用到Java代码的设计架构中。具体思想:
- 代理角色和真实角色具有相同联系的业务
- 用户直接找代理咨询相关业务不直接找真实角色
现在,我就将这租房的过程用Java代码实现,分析一下代理模式到底是什么东西。
租房是一个业务(抽象角色),所以它是一个需要被实现的接口:
public interface Rent {
public void rent();
}
这个业务(Rent接口)是由真实角色(房东Host类)委托的,所以真实角色(房东Host类)就需要将其实现:
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
代理角色(中介RentProxy类)就需要接受这个业务,所以也需要继承业务(Rent接口),并且因为是真实角色(房东Host类)委托的,所以真实角色(房东Host类)这一个类需要被封装为代理角色(中介RentProxy类)的一个属性。
public class RentProxy implements Rent{
private Host host; //真实角色
public RentProxy(){}
public RentProxy(Host host){
this.host = host;
}
@Override
public void rent() { //代理类处理真实角色委托的业务
host.rent(); //核心业务
}
public void seeHouse(){ //代理类自己扩展的业务
System.out.println("中介带你看房");
}
}
而客户(Client类)则直接与代理角色(RentProxy类)进行交互,而不与真实角色进行交互
public class Client {
public static void main(String[] args) throws Exception{
Rent host = new Host();//实例真实角色
RentProxy rentProxy = new RentProxy((Host) host);//实例代理角色
rentProxy.rent();//代理角色执行委托的业务
}
}
这个实现过程只是代理模式设计的一个小小的例子,看起来有点晦涩难懂,其实你只需要理解两点
- 业务是一个待解决的接口,真实角色和代理角色都需要实现这个业务
- 真实角色的业务是交给代理角色来处理的,真实角色就是代理角色的一个属性,并且代理角色会调用真实角色的方法
三、代理模式的好处以及弊端
通过上面一个例子,我们肯定的看不出代理模式的好处,因为一个业务需要一个接口两个类来实现,显然代码量增加了,所以代理模式的缺点就是:
一个真实角色会产生一个代理角色,一个代理只能代理同一类的真实角色所以会导致代码量翻倍
但是你仔细思考一下,当我有许多具有同类业务需要被实现时,我创建的许多真实角色只需要调用一个代理类来执行实现,并且我需要扩展其他业务时,我只需要在代理类中写代码,而不需要去动真实角色的代码。所以代理模式的优点:
- 使真实角色的操作更加纯粹简单,只专注于核心业务
- 可以提取公共操作部分(扩展其他公共业务)
四、静态代理
静态代理是在程序被JVM虚拟机加载前就已经编译好的,也就是说程序是被写死了,一但启动就不能改了,我用代码实现的过程就是静态代理,所以当你需要修改业务时,你需要停止程序后才能进行修改。
五、动态代理
动态代理是利用Java反射机制来动态实现代理过程,那么我们要想深入了解动态代理实现,就需要了解一下反射机制,这里我不做过多的描述,只是简单说一下反射机制原理:
对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法;
当我们写的类被编译时,Java虚拟机会自动生成一个Class类,并且将我们写的类的信息全部保存在这个类中,我们能通过这个Class类调用我们所写的类方法。
换句话说,正常程序是由Java源码到.class文件的字节码,而反射则是由.class字节码到Java源码的。(个人理解)
知道反射以后,我们再来聊一聊动态代理:
动态代理不仅需要反射机制而且还要了解两个类:
- Proxy:获取代理类
- InvocationHandler:调用处理程序
动态代理的三个组成部分
- 抽象角色:一般是接口或者抽象类(同静态代理)
- 真实角色:真实角色:被代理的角色 (同静态代理)
- 代理角色:动态获取代理类的程序类
光看概念肯定是看不懂,所以我来以房东租房问题的实现来介绍动态代理,与静态代理相同的部分代码就不重复写了,想看的见上:
代理角色:动态获取代理类的程序类
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
//获取代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
//调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(rent,args);
return result;
}
}
代码中getProxy()方法是用来获取代理类的,可能你会产生疑问,不知道Proxy.newProxyInstance()的作用,其实就是返回一个接口的代理类,而这个代理类包含了Rent接口的所有方法。其中有三个参数
- 类加载器(暂时没弄明白)
- 需要代理的接口
- 指派方法调用的调用处理程序(InvocationHandler或其子类)
客户(调用程序):
public class Client {
public static void main(String[] args) throws Exception{
Host host = new Host();//真实角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();//创建代理程序
pih.setRent(host);//设置需要代理的接口
Rent proxy = (Rent) pih.getProxy();//获取代理类
proxy.rent();//代理类执行对应真实角色的方法
}
}
到这里,个人理解的动态代理就算结束了,可能你们角色动态代理没啥好的,那是因为我们在代理程序类中把需要代理的接口写死了,如果将Rent接口换成Object类型的话,那么就可以动态代理任何类了。所以现奉上万能代理程序类:
public class ProxyInvocationHandler implements InvocationHandler {
private Object object;
public void setObject(Object object){
this.object = object;
}
public Object getProxy(){
return Proxy.newProxyInstance(rent.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object,args);
return result;
}
}