# 概念

代理模式,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

# UML

<img src="/img/proxy.png">

# 三种代理模式

  • 静态代理
  • JDK 动态代理
  • CGLib 动态代理

# 举个栗子

假设我现在想买一台 Switch,但是现在国内又买不到怎么办?那就只能找代购了,在代理模式的角度来看的话,这里的 就是 真实的对象(RealSubject)代购 就是一个 代理对象(Proxy)买Switch 就是抽象对象的行为 (Subject)

# 静态代理

Subject:

a
/**
 * 抽象对象接口
 */
public interface Subject {
    /**
     * 声明需要被代理的方法
     * 让代理对象来帮我们买一台 Switch
     */
    public void buySwitch();
}

RealSubject:

a
/**
 * 真实对象
 */
public class RealSubject implements Subject {
    /**
     * 我只想买一台 Switch
     */
    @Override
    public void buySwitch() {
        System.out.println("我只想买一台Switch");
    }
}

Proxy:

a
**
 * 代理对象(代购)
 */
public class Proxy implements Subject {
    @Override
    public void buySwitch() {
        // 引用并创建真实对象实例
        RealSubject realSubject = new RealSubject();
        // 调用真实对象的方法,进行代理购买 Switch
        realSubject.buySwitch();
        // 代购进行一些额外的操作
        this.buyGameCard();
    }
    /**
     * 顺便买一些游戏卡
     */
    private void buyGameCard() {
        System.out.println("再顺便买一些游戏卡");
    }
}

Client:

a
/**
 * 客户端调用
 */
public class Run {
    public static void main(String[] args){
        Subject proxy = new Proxy();
        proxy.buySwitch();
    }
}

Result:

我只想买一台Switch
再顺便买一些游戏卡

优点:方便、快捷
缺点:每一个真实对象要有一个对应的代理类,并且行为多了会比较冗余,不满足 开闭原则

开闭原则:对扩展开放,对修改关闭。

# 栗子延伸

假设这个时候买了 Switch,又想玩其他的游戏。比如只狼啥的,那就得再买个 PS4,按照我们刚刚的静态代理来看的话,那就得在 抽象行为对象(Subject) 里面再加一个 buyPS4() 的方法,并且对应的对象跟代理都需要进行实现这个方法,要是再多几个其他的也就太冗余了,这个时候就得考虑是不是可以 动态的进行代理 了?

# JDK 动态代理

真实对象跟抽象行为对象添加 buyPS4() 方法,修改抽象对象为 DynamicHandler
DynamicHandler:

a
/**
 * JDK 动态代理
 */
public class DynamicHandler implements InvocationHandler {
    // 真实对象
    private Object targetObject;
    // 创建代理对象 这段也可以不在此类,也可以放在客户端里面
    public Object createProxy(Object targetOjbect) {
        this.targetObject = targetOjbect;
        /*
         * 创建代理对象
         * Proxy.newProxyInstance (ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * loader:代理类的类加载器
         * interfaces:指定代理类所实现的接口
         * h:动态代理对象在调用方法的时候,关联的 InvocationHandler 对象
         */
        return Proxy.newProxyInstance(targetOjbect.getClass().getClassLoader(),
                targetOjbect.getClass().getInterfaces(), this);
    }
    /**
     * InvocationHandler 接口所定义的唯一的一个方法,该方法负责集中处理动态代理类上的所有方法的调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上执行
     *
     * @param proxy  代理类的实例
     * @param method 代理类被调用的方法
     * @param args   调用方法的参数
     * @return Object
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 触发真实对象之前或者之后可以做一些额外操作
        Object result = null;
        System.out.println("method:" + method.getName() + " proxy:" + proxy.getClass().getName());
        result = method.invoke(this.targetObject, args);// 通过反射执行某个类的某方法
        this.buyGameCard();
        return result;
    }
    /**
     * 顺便买一些游戏卡
     */
    private void buyGameCard() {
        System.out.println("再顺便买一些游戏卡");
    }
}

Client:

a
/**
 * 客户端调用
 */
public class Run {
    public static void main(String[] args) throws Exception  {
        DynamicHandler dynamicHandler = new DynamicHandler();
        Subject subject = (Subject) dynamicHandler.createProxy(new RealSubject());
        subject.buySwitch();
        subject.buyPS4();
    }
}

Result:

我只想买一台Switch
再顺便买一些游戏卡
我想再买一台PS4
再顺便买一些游戏卡

疑问:为啥请求 subject 的对象会跑到 DynamicHandler 里面执行 invoke () 方法呢?

源码分析:

a
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        /*
         * 生成指定的代理类
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * 调用代理类的构造函数
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            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);
        }
    }

源码解析:
getProxyClass (loader, interfaces) 创建代理类Proxy0.Proxy0.Proxy0 类 实现了 Subject 接口,并继承了 Proxy 类.

$Proxy0:

public final class $Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    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 buySwitch() 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 void buyPS4() throws  {
        try {
            super.h.invoke(this, m4, (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("design.proxy.statics.Subject").getMethod("buySwitch");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("design.proxy.statics.Subject").getMethod("buyPS4");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

观察 m3、m4 这两个方法,然后看对应的方法里面都会有一个 super.h.invoke () 方法,这个 super.h 就是我们的 DynamicHandler代理对象

步骤:
Proxy.newProxyInstance 生成 $Proxy0 -> $Proxy0 调用 bySwitch 方法 -> DynamicHandler.invoke () -> 通过反射再调用真实对象请求的方法

# CGLib 动态代理

a
public class DynamicHandler implements MethodInterceptor {
    private Object target;// 业务类对象,供代理方法中进行真正的业务方法调用
    // 相当于 JDK 动态代理中的绑定
    public Object getInstance(Object target) {
        this.target = target;  // 给业务对象赋值
        Enhancer enhancer = new Enhancer(); // 创建加强器,用来创建动态代理类
        enhancer.setSuperclass(this.target.getClass());  // 为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        // 设置回调:对于代理类上所有方法的调用,都会调用 CallBack,而 Callback 则需要实现 intercept () 方法进行拦
        enhancer.setCallback(this);
        // 创建动态代理类对象并返回
        return enhancer.create();
    }
    // 实现回调方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        proxy.invokeSuper(obj, args); // 调用业务类(父类中)的方法
        this.buyGameCard();
        return null;
    }
    /**
     * 顺便买一些游戏卡
     */
    private void buyGameCard() {
        System.out.println("再顺便买一些游戏卡");
    }
}

Client:

a
/**
 * 客户端调用
 */
public class Run {
    public static void main(String[] args) throws Exception  {
        DynamicHandler handler = new DynamicHandler();
        RealSubject subject = (RealSubject) handler.getInstance(new RealSubject());
        subject.buySwitch();
        subject.buyPS4();
    }
}

Result:

我只想买一台Switch
再顺便买一些游戏卡
我想再买一台PS4
再顺便买一些游戏卡

原理跟 JDK 的类似,只不过不是继承 Proxy 了,而是通过 Enhancer 类操作节码生成代理对象来继承真实对象,然后进行方法拦截进入 DynamicHandler 的 intercept () 方法,因为是继承的真实对象所以真实的对象不能被 final 修饰。

# 区别

·静态代理是JDK 动态代理CGLib 动态代理
优点方便、快,适合代理对象比较少的场景易扩展,适合代理对象比较多得场景易扩展,比反射调用方法快一点,没有太大的性能问题
缺点不好扩展,违反开闭原则必须提供接口,并且因为通过反射来调用方法,消耗性能ASM 操作生成类比较慢,真实对象不能为 final

# AOP 中的应用

# AOP 中的一些术语

  • 1. 通知 (Advice):
    通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。
  • 2. 连接点 (Joinpoint):
    程序能够应用通知的一个 “时机”,这些 “时机” 就是连接点,例如方法被调用时、异常被抛出时等等。
  • 3. 切入点 (Pointcut)
    通知定义了切面要发生的 “故事” 和时间,那么切入点就定义了 “故事” 发生的地点,例如某个类或方法的名称,spring 中允许我们方便的用正则表达式来指定
  • 4. 切面 (Aspect)
    通知和切入点共同组成了切面:时间、地点和要发生的 “故事”
  • 5. 引入 (Introduction)
    引入允许我们向现有的类添加新的方法和属性 (Spring 提供了一个方法注入的功能)
  • 6. 目标 (Target)
    即被通知的对象,如果没有 AOP, 那么它的逻辑将要交叉别的事务逻辑,有了 AOP 之后它可以只关注自己要做的事(AOP 让他做爱做的事)
  • 7. 代理 (proxy)
    应用通知的对象,详细内容参见设计模式里面的代理模式
  • 8. 织入 (Weaving)
    把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
    (1) 编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如 AspectJ 的织入编译器
    (2) 类加载时:使用特殊的 ClassLoader 在目标类被加载到程序之前增强类的字节代码
    (3) 运行时:切面在运行的某个时刻被织入,SpringAOP 就是以这种方式织入切面的,原理应该是使用了 JDK 的动态代理技术

# 拼多多版本 AOP

a
/**
 * 前置增强
 */
public interface BeforeAdvice {
    public void before();
}
a
/**
 * 后置增强
 */
public interface AfterAdvice {
    public void after();
}

然后以 JDK 的动态代理为例子修改一下:

a
/**
 * JDK 动态代理
 */
public class DynamicHandler implements InvocationHandler {
    // 真实对象
    private Object targetObject;
    // 前值增强
    private BeforeAdvice beforeAdvice;
    // 后置增强
    private AfterAdvice afterAdvice;
    public BeforeAdvice getBeforeAdvice() {
        return beforeAdvice;
    }
    public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
        this.beforeAdvice = beforeAdvice;
    }
    public AfterAdvice getAfterAdvice() {
        return afterAdvice;
    }
    public void setAfterAdvice(AfterAdvice afterAdvice) {
        this.afterAdvice = afterAdvice;
    }
    // 创建代理对象 这段也可以不在此类,也可以放在客户端里面
    public Object createProxy(Object targetOjbect) {
        this.targetObject = targetOjbect;
        /*
         * 创建代理对象
         * Proxy.newProxyInstance (ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
         * loader:代理类的类加载器
         * interfaces:指定代理类所实现的接口
         * h:动态代理对象在调用方法的时候,关联的 InvocationHandler 对象
         */
        return Proxy.newProxyInstance(targetOjbect.getClass().getClassLoader(),
                targetOjbect.getClass().getInterfaces(), this);
    }
    /**
     * InvocationHandler 接口所定义的唯一的一个方法,该方法负责集中处理动态代理类上的所有方法的调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上执行
     *
     * @param proxy  代理类的实例
     * @param method 代理类被调用的方法
     * @param args   调用方法的参数
     * @return Object
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 触发真实对象之前或者之后可以做一些额外操作
        Object result = null;
        if(beforeAdvice != null) {
            beforeAdvice.before();
        }
        result = method.invoke(this.targetObject, args);// 通过反射执行某个类的某方法
        if(afterAdvice != null) {
            afterAdvice.after();
        }
        return result;
    }
}

Client:

a
/**
 * 客户端调用
 */
public class Run {
    public static void main(String[] args) throws Exception  {
        DynamicHandler dynamicHandler = new DynamicHandler();
        Subject subject = (Subject) dynamicHandler.createProxy(new RealSubject());
        dynamicHandler.setBeforeAdvice(new BeforeAdvice() {
            @Override
            public void before() {
                System.out.println("听说任天堂出了一款不错的游戏,所以。。。");
            }
        });
        dynamicHandler.setAfterAdvice(new AfterAdvice() {
            @Override
            public void after() {
                System.out.println("再买点游戏卡");
            }
        });
        subject.buySwitch();
    }
}

Result:

听说任天堂出了一款不错的游戏,所以。。。
我只想买一台Switch
再买点游戏卡

这里我们只是简单的表明了一下前后通知配合动态代理的使用,真正的 AOP 涉及到切入点、切面什么时候织入等等。。想要了解的同学可以自行后续去研究,这里主要就是突出代理模式的应用。

# 总结

讲完这个栗子,总结一下,代理模式在 Java 中主要用于系统的解耦,如同中介机构,可以为目标类提供代理服务,以控制对对象的访问,目标类的任何方法在执行前都必须经过代理类,这样代理类就可以用来负责请求的预处理、过滤、将请求分派给目标类处理、以及目标类执行完请求后的后续处理。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

vayi 微信支付

微信支付

vayi 支付宝

支付宝

vayi 贝宝

贝宝