设计模式—代理模式(Ⅰ版)

101 阅读8分钟

代理模式

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时直接访问目标对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。


Ⅰ结构

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题(RealSubject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

Ⅱ 静态代理

【例】本文以火车站和临时售票点为例,前者为目标对象(即被代理对象),后者为代理对象,业务方法为卖票。

因为火车站类和临时售票点类都能够卖票,因此两者都要实现卖票接口,重写卖票业务方法。

卖票接口

package com.example.designmodel.Creative.Proxy.Static;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:42
 * @description :   火车票售卖接口 -----抽象主题接口/类:通过接口或抽象类声明真实对象和代理对象实现的业务方法
 */
public interface TrainTicketsSale {
​
    /**
     * 卖票方法
     */
    void sell();
}

火车站类

package com.example.designmodel.Creative.Proxy.Static;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:44
 * @description : 火车站 ----真实对象类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
 */
public class RailwayStation implements TrainTicketsSale {
​
    @Override
    public void sell() {
        System.out.println("火车站售票业务~~~");
    }
}
​

临时售票点类

增强代理业务

package com.example.designmodel.Creative.Proxy.Static;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:46
 * @description : 临时售票点 ---代理对象:实现了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
 */
public class TemporaryTicketOffice implements TrainTicketsSale{
​
    /**
     * 创建真实对象,通过引用真实对象,从而可以访问、控制或扩展真实主题的功能
     */
    private final RailwayStation railwayStation = new RailwayStation();
​
    @Override
    public void sell() {
        System.out.println("业务增强---临时售票业务");
        railwayStation.sell();
    }
}

测试类

package com.example.designmodel.Creative.Proxy.Static;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:49
 * @description : 客户端购票
 */
public class ClientBuyTickets {
    public static void main(String[] args) {
//        创建代理对象
        TemporaryTicketOffice temporaryTicketOffice = new TemporaryTicketOffice();
​
//        通过调用代理对象的方法,间接调用真实对象
        temporaryTicketOffice.sell();
    }
}
​
/**
 * 使用代理对象的好处:
 *  1.可以隐藏真实对象,我们将代理对象暴露给外部,避免真实对象被修改。
 *  2.代理对象实际上在功能上是对真实对象的扩展增强。
 *  3.真实对象可以专心实现核心业务,而将其余非核心业务代码交给代理对象实现,使得核心业务和非核心业务之间实现解耦。
 */

Ⅲ 动态代理

JDK代理

静态代理有个很不好的地方,如果说我们需要被代理的类有很多,那么就要创建很多代理类,这样看起来非常麻烦,因此我们尝试使用JDK动态代理,使得在程序运行阶段动态帮我们生成代理对象。

卖票接口

package com.example.designmodel.Creative.Proxy.Dynamic.JDK;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:42
 * @description :   火车票售卖接口 -----抽象主题接口/类:通过接口或抽象类声明真实对象和代理对象实现的业务方法
 */
public interface TrainTicketsSale {
​
    /**
     * 卖票方法
     */
    void sell();
}

出具发票接口

package com.example.designmodel.Creative.Proxy.Dynamic.JDK;
​
/**
 * @author : Rimeco
 * @create : 2022/8/11  21:35
 * @description : 开发票
 */
public interface Invoice {
​
    void notice();
}

火车站类

package com.example.designmodel.Creative.Proxy.Dynamic.JDK;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:44
 * @description : 火车站 ----真实对象类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
 */
public class RailwayStation implements TrainTicketsSale, Invoice {
​
    @Override
    public void sell() {
        System.out.println("火车站售票业务~~~");
    }
​
    @Override
    public void notice() {
        System.out.println("开发票!!!");
    }
}

代理工厂类

请阅读注解

package com.example.designmodel.Creative.Proxy.Dynamic.JDK;
​
import java.lang.reflect.Proxy;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:58
 * @description :   代理对象工厂 : 意思是我们有一个代理对象工厂,传入不同的被代理对象,JDK可以帮我们创建对应的代理对象,他的好处是相对于静态代理,我们不用实际的创建静态代理类(因为这样很麻烦,有一个被代理类就要创建一个代理类,JDK动态代理相当于匿名内部类的使用)
 *                  缺点:被代理对象必须实现接口,因为我们要通过接口获取被代理对象的行为Proxy.newProxyInstance中也必须传入被代理类所实现的接口。
 */
public class ProxyFactory<T> {
​
    /**
     * 获取代理对象
     * @param t 被代理对象代理类型
     * @return
     */
    public T getProxyObject(T t){
        //        调用Proxy类的静态方法newProxyInstance创建代理对象(不是代理类!!!)
        /**
         * ClassLoader loader :定义代理类的类加载器接口,用于加载代理类,使用真实对象的类加载器即可
         * Class<?>[] interfaces:代理类实现的接口列表
         * InvocationHandler h :代理对象的调用处理程序
         */
        //        返回具有代理类的指定调用处理程序的代理实例,该代理类由指定的类加载器定义并实现指定的接口
        return (T) Proxy.newProxyInstance(
            t.getClass().getClassLoader(),
            t.getClass().getInterfaces(),
            /**
                 * InvocationHandler是一个函数式接口,因此我们使用lambda表达式创建匿名内部类 : public Object invoke(Object proxy, Method method, Object[] args);
                 *
                 *  proxy : 在方法上调用该方法的代理实例
                 *  method: 对应于在代理实例上调用的接口方法的 Method 实例。 Method对象的声明类将是声明该方法的接口,该接口可能是代理类继承该方法的代理接口的超接口。
                 *  args : 一个对象数组,其中包含在代理实例上的方法调用中传递的参数值,如果接口方法不接受任何参数,则返回 null。
                 *  return:从代理实例上的方法调用返回的值。如果接口方法声明的返回类型是原始类型,那么该方法返回的值必须是对应原始包装类的实例;否则,它必须是可分配给声明的返回类型的类型。
                 */
            (proxy, method, args) -> {
                System.out.println("业务增强---临时售票业务");
                //                    反射机制,传入真实对象和参数,通过真实对象调用抽象主题方法,返回生成的代理对象
                return method.invoke(t, args);
            }
        );
    }
}

测试类

package com.example.designmodel.Creative.Proxy.Dynamic.JDK;
​
import java.util.Arrays;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:49
 * @description : 客户端购票
 */
public class ClientBuyTickets {
    public static void main(String[] args) {
​
        TrainTicketsSale railwayStation = new RailwayStation();
        //        获取代理对象
        ProxyFactory<TrainTicketsSale> proxyFactory = new ProxyFactory<>();
​
        TrainTicketsSale proxyObject = proxyFactory.getProxyObject(railwayStation);
​
        proxyObject.sell();
​
        System.out.println(Arrays.asList(proxyObject.getClass().getInterfaces()));
        //        proxy类只能转为接口类,不能转化为本类
        ((Invoice)proxyObject).notice();
​
        System.out.println(proxyObject.getClass());
​
        //        while (true){
        //        }
​
    }
}
​
/**
 * JDK动态代理实际上是在运行时动态生成代理类,通过代理类创建代理对象,代理对象调用抽象主题方法,从而间接引用真实对象。
 */

InvocationHandler接口中的invoke方法拦截了所以被代理对象的方法,因此在该方法中我们可以执行被代理对象的方法。

Cglib代理

比JDK代理更间接,因为它不用实现接口,也就是说它可以将让代理类间接使用自身的方法。

引入cglib依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

火车站类

package com.example.designmodel.Creative.Proxy.Dynamic.Cglib;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:44
 * @description : 火车站
 */
public class RailwayStation {
​
    public void sell() {
        System.out.println("火车站售票业务~~~");
    }
}

代理工厂类

package com.example.designmodel.Creative.Proxy.Dynamic.Cglib;
​
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
​
import java.lang.reflect.Method;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:58
 * @description :   cglib底层相当于为被代理创建一个子类,子类会继承被代理类的方法,该子类就是代理类
 */
public class ProxyFactory<T> {
​
    /**
     *
     * @return 返回RailwayStation的子类
     */
    public T getProxyObject(T t){
//        创建Enhancer对象,类似JDK代理中的Proxy类,动态为这个类创建一个子类
        Enhancer enhancer = new Enhancer();
//        设置父类的字节码对象(类模板),指定代理类的父类
        enhancer.setSuperclass(t.getClass());
//        设置回调函数
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            System.out.println("Cglib代理!");
            return method.invoke(t,objects);
        });
//        创建代理对象
        return (T) enhancer.create();
    }
}

测试类

package com.example.designmodel.Creative.Proxy.Dynamic.Cglib;
​
/**
 * @author : Rimeco
 * @create : 2022/8/8  22:49
 * @description : 客户端购票
 */
public class ClientBuyTickets {
    public static void main(String[] args) {
        RailwayStation railwayStation = new RailwayStation();
​
//        创建代理工厂对象
        ProxyFactory<RailwayStation> proxyFactory = new ProxyFactory();
​
//        获取代理对象(RailwayStation的子类)
        RailwayStation proxyObject = proxyFactory.getProxyObject(railwayStation);
​
//        代理对象调用,实际调用intercept
        proxyObject.sell();
​
        System.out.println(proxyObject.getClass());
​
        while (true){
​
        }
    }
}

调用被代理对象中的不同方法是,实际上是调用了intercept()拦截方法,method.invoke(t,objects)传入的是调用方法的方法对象。

Ⅳ 对比

  • JDK代理和CGLIB代理

    使用CGLib实现动态代理,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理[final修饰的类不能被继承,final修饰的方法不能被重写],因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理和静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题