代理模式

代理模式,可以分为两种,一种是静态代理,一种是动态代理。

两种代理从虚拟机加载类的角度来讲,本质上都是一样的,都是在原有类的行为基础上,加入一些多出的行为,甚至完全替换原有的行为。

静态代理采用的方式就是我们手动的将这些行为换进去,然后让编译器帮我们编译,同时也就将字节码在原有类的基础上加入一些其他的东西或者替换原有的东西,产生一个新的与原有类接口相同却行为不同的类型。

定义

为其它的对象提供一种代理,以控制这个对象的访问

使用场景

当不想直接访问某个对象的时候,就可以通过代理

  1. 不想买午餐,同事帮忙带
  2. 买车不用去厂里,去4s店
  3. 去代理点买火车票,不用去车站

静态代理示例代码

Movable接口:

interface Movable {
    void move();
}

TankTimeProxy:

class TankTimeProxy implements Movable {
    Movable m;

    public TankTimeProxy(Movable m) {
        this.m = m;
    }

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        m.move();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

TankLogProxy:

class TankLogProxy implements Movable {
    Movable m;

    public TankLogProxy(Movable m) {
        this.m = m;
    }

    @Override
    public void move() {
        System.out.println("start moving...");
        m.move();
        long end = System.currentTimeMillis();
        System.out.println("stopped!");
    }
}

Tank:

/**
 * 问题:我想记录坦克的移动时间
 * 最简单的办法:修改代码,记录时间
 * 问题2:如果无法改变方法源码呢?
 * 用继承?
 * v05:使用代理
 * v06:代理有各种类型
 * 问题:如何实现代理的各种组合?继承?Decorator?
 * v07:代理的对象改成Movable类型-越来越像decorator了
 */
public class Tank implements Movable {
    /**
     * 模拟坦克移动了一段儿时间
     */
    @Override
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

客户端:

public class Main {
    public static void main(String[] args) {

        Tank t = new Tank();
        TankTimeProxy ttp = new TankTimeProxy(t);
        TankLogProxy tlp = new TankLogProxy(ttp);
        tlp.move();
    }
}

运行截图:

alt

代理模式中的静态代理模式存在一些特点:

  1. 1个静态代理 只服务1种类型的目标对象
  2. 若要服务多类型的目标对象,则需要为每种目标对象都实现一个静态代理对象

在目标对象较多的情况下,若采用静态代理,则会出现 静态代理对象量多、代码量大,从而导致代码复杂的问题

动态代理原理:

  • 设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现
    1. 即:在使用时再创建动态代理类 & 实例
    2. 静态代理则是在代理类实现时就指定与目标对象类(RealSubject)相同的接口
  • 通过Java 反射机制的method.invoke(),通过调用动态代理类对象方法,从而自动调用目标对象的方法

代码示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Random;

/**
 * 问题:我想记录坦克的移动时间
 * 最简单的办法:修改代码,记录时间
 * 问题2:如果无法改变方法源码呢?
 * 用继承?
 * v05:使用代理
 * v06:代理有各种类型
 * 问题:如何实现代理的各种组合?继承?Decorator?
 * v07:代理的对象改成Movable类型-越来越像decorator了
 * v08:如果有stop方法需要代理...
 * 如果想让LogProxy可以重用,不仅可以代理Tank,还可以代理任何其他可以代理的类型
 * (毕竟日志记录,时间计算是很多方法都需要的东西),这时该怎么做呢?
 * 分离代理行为与被代理对象
 * 使用jdk的动态代理
 * <p>
 * v09: 横切代码与业务逻辑代码分离 AOP
 * v10: 通过反射观察生成的代理对象
 * jdk反射生成代理必须面向接口,这是由Proxy的内部实现决定的
 */
public class Tank implements Movable {

    /**
     * 模拟坦克移动了一段儿时间
     */
    @Override
    public void move() {
        System.out.println("Tank moving claclacla...");
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class TimeProxy implements InvocationHandler {
    Movable m;

    public TimeProxy(Movable m) {
        this.m = m;
    }

    public void before() {
        System.out.println("method start..");
    }

    public void after() {
        System.out.println("method stop..");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //Arrays.stream(proxy.getClass().getMethods()).map(Method::getName).forEach(System.out::println);

        before();
        Object o = method.invoke(m, args);
        after();
        return o;
    }

}

interface Movable {
    void move();
}

客户端代码:

public class Main {
    public static void main(String[] args) {
        Tank tank = new Tank();

        // 以下是jdk1.8及其之前的
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // 以下是jdk1.9及其之后的
        // System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

        Movable m = (Movable) Proxy.newProxyInstance(Tank.class.getClassLoader(),
                new Class[]{Movable.class}, //tank.class.getInterfaces()
                new TimeProxy(tank)
        );

        m.move();
    }
}

运行截图:

alt

设置

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

后可以将JDK动态代理生成的中间文件如何保存到本地。

alt

以下是根据字节码反编译出来的代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements Movable {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void move() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("dynamicproxy.Movable").getMethod("move");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}