代理模式概述
-
Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题。
-
代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
-
所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
-
为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
-
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
-
更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
代理模式是常见的设计模式之一,顾名思义,代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。(为真实对象提供代理,然后供其他对象通过代理访问真实对象)
在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司。
代理模式属于结构型模式。代理模式也叫委托模式。
生活中,比如代购、打官司等等,实际上都是一种代理模式。
使用场合举例:
- 如果需要委托类处理某一业务,那么我们就可以先在代理类中统一处理然后在调用具体实现类。
角色说明:
Subject(抽象主题类):接口或者抽象类,声明真实主题与代理的共同接口方法。
RealSubject(真实主题类):也叫做被代理类或被委托类,定义了代理所表示的真实对象,负责具体业务逻辑的执行,客户端可以通过代理类间接的调用真实主题类的方法。
Proxy(代理类):也叫委托类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。
Client(客户端类):使用代理模式的地方。
从UML图中,可以看出代理类与真正实现的类都是继承了抽象的主题类,这样的好处在于代理类可以与实际的类有相同的方法,可以保证客户端使用的透明性。
代理模式的两种实现的方式
代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。下面我们主要讲解一下这两种代理模式。
- 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
- 动态代理:在程序运行时运用反射机制动态创建而成,是根据代理的对象,动态创建代理类和代理对象。
静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理类的源码是在程序运行期间根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
静态代理每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类。
所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。
静态代理
静态代理的前提,那就是真实类(被代理类)和代理类要实现同一个接口,代理类中翅有真实类的引用,调用代理类的方法其实最终调到真实类,在代理类中实现真实类的方法同时可以进行真实类方法的增强处理,在一个代理类中就可以完成对多个真实对象的注入工作。
其实代理的一般模式就是静态代理的实现模式:
- 首先创建一个接口(JDK代理都是面向接口的)
- 然后创建具体实现类来实现这个接口
- 创建一个代理类同样实现这个接口,不同之处在于,具体实现类的方法中需要将接口中定义的方法的业务逻辑功能实现,而代理类中的方法只要调用具体类中的对应方法即可,这样我们在需要使用接口中的某个方法的功能时直接调用代理类的方法即可,将具体的实现类隐藏在底层。
案例
- 以海外代购为例,在国内的人想买国外的东西只能去找国外的人去进行代购。
创建抽象主题类
人都是有购买这个方法的:
public interface People {
void buy();//购买
}
创建真实主题类
国内的人想购买某些产品,定义具体的购买过程:
public class Domestic implements People {
@Override
public void buy() {//具体实现
System.out.println("国内要买一个包");
}
}
创建代理类
海外的代购党需要知道是谁(持有真实主题类的引用)想购买啥产品:
public class Oversea implements People {
People mPeople;//持有People类的引用
public Oversea(People people) {
mPeople = people;
}
@Override
public void buy() {
System.out.println("我是海外代购:");
mPeople.buy();//调用了被代理者的buy()方法,
}
}
测试
public void test() {
People domestic = new Domestic(); //创建国内购买人
People oversea = new Oversea(domestic); //创建海外代购类并将domestic作为构造函数传递
oversea.buy(); //调用海外代购的buy()
}
输出结果
我是海外代购:
国内要买一个包
租房案例
以租房为例,租客找房东租房,然后中间经过房屋中介,以此为背景,它的UML图如下:
public interface IRentHouse {
void rentHouse();
}
public class RentHouse implements IRentHouse {
@Override
public void rentHouse() {
System.out.println("实现租房");
}
}
public class IntermediaryProxy implements IRentHouse {
private IRentHouse iRent;
public IntermediaryProxy(IRentHouse iRentHouse) {
iRent=iRentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
iRent.rentHouse();
System.out.println("中介负责维修管理");
}
}
//client测试类
public class TestStaticProxy {
public static void main(String[] args) {
//定义租房
IRentHouse iRentHouse = new RentHouse();
//定义中介
IRentHouse intermediaryProxy = new IntermediaryProxy(iRentHouse);
//中介租房
intermediaryProxy.rentHouse();
}
}
静态代理类优点
解耦:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合)。
静态代理的缺点
从静态代理的代码中可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,代理类也要增加相应的方法,这样构建的代理类的代码量是非常大的,所以就引进动态代理。
这种实现方式很直观也很简单,但其缺点是代理类必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
- 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。
举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)
即静态代理类只能为特定的接口(Service)服务,如想要为多个接口服务则需要建立很多个代理类。
动态代理
不事先写好代理类,运行时会动态生成代理类,Proxy.newProxyInstance()创建代理对象。
- 动态代理有别于静态代理,是根据被代理的对象,代理对象是基于接口动态生成的,这样就可以避免静态代理中代理类接口过多的问题。
- 动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。
从静态代理的代码中可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,这样构建的代理类的代码量是非常大的,所以就引进动态代理.
动态代理允许使用一种方法的单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务,看到这句话,可以容易的联想到动态代理的实现与反射密不可分。
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理实例,来完成具体的功能。
根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类。
所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。
Proxy ,InvocationHandler
在 java 的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
/**
* @param proxy 指代我们所代理的那个真实对象
* @param method 指代的是我们所要调用真实对象的某个方法的Method对象
* @param args 指代的是调用真实对象某个方法时接受的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。
使用 Proxy 类的 newProxyInstance 方法生成一个代理对象:
Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法。
/**
* @param loader 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
* @param interfaces 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
* @param h 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
其步骤如下:
- 编写一个委托类的接口,即静态代理的(Subject接口)
- 实现一个真正的委托类,即静态代理的(RealSubject类)
- 创建一个类,实现InvocationHandler接口,并重写该invoke方法
- 在测试类中,生成动态代理的对象。
第一二步骤,和静态代理一样,不过说了。第三步,代码如下
- 接口
public interface People {
void buy();//购买
}
- 真实主题类
public class Domestic implements People {
@Override
public void buy() {//具体实现
System.out.println("国内要买一个包");
}
}
创建一个类,实现InvocationHandler接口
动态代理允许使用一种方法的单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务。
我们创建的并不是所谓的代理类,而是一个可以帮助我们返回代理对象的辅助类。
public class DynamicProxy implements InvocationHandler {//实现InvocationHandler接口
private Object obj;//被代理的对象
public DynamicProxy(Object obj) {
this.obj = obj;
}
//重写invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("海外动态代理调用方法: "+method.getName());
Object result = method.invoke(obj, args);//调用被代理的对象的方法
return result;
}
}
public void test() {
People domestic = new Domestic(); //创建国内购买人
DynamicProxy proxy = new DynamicProxy(domestic);
ClassLoader classLoader = domestic.getClass().getClassLoader(); //获取ClassLoader
//通过 Proxy 创建海外代购实例 ,实际上通过反射来实现的。
People oversea = (People) Proxy.newProxyInstance(classLoader,
new Class[]{People.class}, proxy);
oversea.buy();//调用海外代购的buy()
}
//真实对象
People domestic = new Domestic();
//oversea :代理对象
//Proxy.newProxyInstance 运行时会生成一个代理类
People oversea = (People) Proxy.newProxyInstance(domestic.getClass().getClassLoader(),
new Class[]{People.class},
new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("海外动态代理调用方法: " + method.getName());
Object result = method.invoke(this, args);//调用被代理的对象的方法
return result;
}
}
);
oversea.buy();
}
创建动态代理的对象,需要借助Proxy.newProxyInstance。该方法的三个参数分别是:
//CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类的实例
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象。
输出:
海外动态代理调用方法: buy
国内要买一个包
应用场景
- 当一个对象不能或者不想直接访问另一个对象时,可以通过一个代理对象来间接访问。为保证客户端使用的透明性,委托对象和代理对象要实现同样的接口。
- 被访问的对象不想暴露全部内容时,可以通过代理去掉不想被访问的内容。