代理模式(Proxy Pattern)

122 阅读7分钟

攻略大全

1. 粘贴攻略

2 造火箭攻略

3. 拧螺丝攻略

3.1 静态代理(Static Proxy Pattern)

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。

代理抽象:

/**
 * @author Huadao
 * @date Created in 2022/4/13
 * @desc 代理抽象
 */
public interface IAnimal {

    void eat();
}

被代理类:

/**
 * @author Huadao
 * @date Created in 2022/4/13
 * @desc 被代理类
 */
public class ImplHuman implements IAnimal {

    @Override
    public void eat() {
        System.out.println("xxxx");
    }
}

代理类:

/**
 * @author Huadao
 * @date Created in 2022/4/13
 * @desc 代理类,实现被代理类的接口实现中所想代理的接口
 */
public class ProxyHuman implements IAnimal {

    /**
     * 持有被代理类的引用
     */
    private IAnimal mIAnimal;

    public ProxyHuman(IAnimal iAnimal) {
        mIAnimal = iAnimal;
    }

    @Override
    public void eat() {
        // 代理对象可以做一些额外操作后再调用被代理对象的方法
        beforeEat();

        // 调用的是被代理类实例的方法
        mIAnimal.eat();

        // 代理对象调用被代理对象的方法后可以再接着做一些额外操作
        afterEat();
    }

    private void beforeEat() {
        System.out.println("吃之前");
    }

    private void afterEat() {
        System.out.println("吃之后");
    }
}

最终调用:

// 通过代理对象调用被代理对象的方法
ProxyHuman proxyHuman = new ProxyHuman(new ImplHuman());
proxyHuman.eat();

3.1.1 优点

  • 可以隐藏委托类的实现。
  • 可以实现客户端与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

3.1.2 缺点

  • 一对一会出现静态代理对象量多、代码量大,从而导致代码复杂、可维护性差的问题。
  • 一对多会出现代理对象扩展能力差的问题。

3.1.3 应用场景

image.png

3.2 动态代理(Dynamic Proxy Pattern)

代理类在程序运行时创建的代理方式被称为动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。

JDK的动态代理:

/**
 * @author huadao
 * @date 2021/7/23 15:13
 * desc:
 */
public class JavaTest {

    public static void main(String[] args) {
        // 动态代理ImplHuman
        proxyTest(new ImplHuman());
    }

    public static void proxyTest(Object target) {
        // 类加载器
        // 接口数组
        // 方法回调的接口
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                new Class[]{IAnimal.class, IGod.class}, new MyHumanInvocationHandler(target));

        // 此时,通过proxy实例调用被代理对象的方法
        IAnimal implProxy = (IAnimal) proxy;
        implProxy.eat();

        // 此时,调用的是实例了IGod接口的动态代理类实例proxy自身的方法
        IGod iGod = (IGod) proxy;
        iGod.fly();
    }

    static class MyHumanInvocationHandler implements InvocationHandler {

        /**
         * 要代理的具体对象
         */
        Object mTarget;

        public MyHumanInvocationHandler(Object target) {
            mTarget = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 方法调用的回调
            // proxy:动态生成的代理类实例
            // method:代理类实例调用的方法的名字
            // args:代理类实例调用的方法的参数

            // 可加入额外的操作处理
            System.out.println("动态调用前");

            Object invoke = null;

            // 有被代理的具体实例时,是常用场景
            if (mTarget != null) {
                if ("eat".equals(method.getName())) {
                    invoke = method.invoke(mTarget, args);
                }

            } else {
                // 没有被代理的具体实例时,如Retrofit
                // 从invoke()可反推出,proxy实例所实现的接口方法中,只是为了传递proxy、method、args
                // 是没有具体的业务逻辑实现的
                // 可在此对所调用的空方法进行具体的逻辑实现
                // 如Retrofit动态代理生成service时:loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
                if ("fly".equals(method.getName())) {
                    invoke = invoke();
                }
            }

            // 可加入额外的操作处理
            System.out.println("动态调用后");

            // 方法返回值
            return invoke;
        }

        private Object invoke() {
            System.out.println("fly");
            return null;
        }

    }
    
}
动态调用前
eat ImplHuman
动态调用后

动态调用前
fly
动态调用后

3.2.1 优点

  • 只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码
  • 更强的灵活性
  1. 设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现
  2. 在使用时(调用目标对象方法时)才会动态创建动态代理类 & 实例,不需要事先实例化

3.2.2 缺点

  • 效率低
    相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法
  • 应用场景局限
    因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类 创建代理类

即只能动态代理 实现了接口的类

3.2.3 应用场景

  • 基于静态代理应用场景下,需要代理对象数量较多的情况下使用动态代理
  • AOP 领域
  1. 定义:即 Aspect Oriented Programming = 面向切面编程,是OOP的延续、函数式编程的一种衍生范型
  2. 作用:通过预编译方式和运行期动态代理实现程序功能的统一维护。
  3. 优点:降低业务逻辑各部分之间的耦合度 、 提高程序的可重用性 & 提高了开发的效率
  4. 具体应用场景:日志记录、性能统计、安全控制、异常处理等

3.2.4 cglib动态代理

JDK动态代理是通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类(通过修改字节码来实现代理)。 注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

3.3 JDK的动态代理源码解析

3.3.1 JDK11版本

Proxy.newProxyInstance 会创建一个Class,与静态代理不同,这个Class不是由具体的.java源文件编译而来,即没有真正的文件,只是在内存中按照Class格式生成一个实现了所有传进来的接口的Class,仅此而已。

经过一系列的参数检测与解析后会生成对应的class数组(生成的class数据数组会根据JDK的配置来决定是否输出到本地文件中),再调用native方法将class数据数组转化生成class实例并缓存到内存中的代理缓存。

image.png

  • 试图从代理缓存中获取代理类的构造器: image.png

  • 查看构建者模式的构造过程: image.png image.png image.png

  • native方法声明class实例 image.png

  • 参数配置决定了生成class数据数组时是否输出对应代理类文件
    image.png image.png

3.3.2 Android版本

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    

    /*
     * Look up or generate the designated proxy class.
     */
     // 生成代理类
    Class<?> cl = getProxyClass0(loader, intfs);

    try {
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        
        if (!Modifier.isPublic(cl.getModifiers())) {
            cons.setAccessible(true);
        }
        
        // 让Proxy实例持有InvocationHandl的实例引用
        // 进而通过invoke()实现方法调用的信息扩散
        return cons.newInstance(new Object[]{h});
        
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

image.png

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>  {
    // prefix for all proxy class names
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique proxy class names
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        ...
        
        /*
         * Verify that the Class object actually represents an
         * interface.
         */
         // 接口类型检测,为什么我们说JDK的动态代理只能传入接口的原因
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(
                interfaceClass.getName() + " is not an interface");
        }
        
        ...
        // 一系列的参数检测与数据解析处理后
        
        /*
         * Choose a name for the proxy class to generate.
         */
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        // 调用native方法在内存中生成代理类并返回
        return generateProxy(proxyName, interfaces, loader, methodsArray,
                             exceptionsArray);

            }
}
@FastNative
private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                             ClassLoader loader, Method[] methods,
                                             Class<?>[][] exceptions);
// END Android-changed: How proxies are generated.

4. 复制攻略

4.1 Carson带你学设计模式:这是一份全面 & 详细的设计模式学习指南