设计模式之代理模式

代理模式

headfirst中的定义:代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。嘿嘿,这是什么鬼?来张图说明下吧。
在这里插入图片描述

RealSubject 是真正做事情的对象,他是被Proxy代理和控制的对象。
Proxy持有RealSubject的引用,有的时候Proxy还会负责RealSubject对象的创建和销毁。客户和RealSubject的交互都必须通过Proxy。 因为任何用到RealSubject的地方,都可以用Proxy取代。Proxy也控制了对RealSubject的访问,在某些情况下,我们可能需要这样的控制。 这些情况包括RealSubject是远程的对象,RealObject创建开销大,或RealSubject需要被保护。–from headfirst

不论是动态代理还是静态代理的本质都是“访问控制”.
如何理解"控制访问" ,举个例子,比如说大名鼎鼎的dubbo框架,我们调用
api 接口的时候,实际上是在调用代理(动态代理)的方法,这个方法会利用网络转发到远程执行,再由代理将结果转给调用者。 代理对象封装了和网络打交道的细节,以及对象的序列化和反序列化,以及负载均衡等等,有非常多的细节,结合上面的UML类图,“访问控制”通俗点说就是调用Proxy 的 request()方法实际上调用的是RealSubject的request()方法,Proxy在调用RealSubject的request()方法之前可以写一些代码,来进行“访问控制”。

静态代理模式

public class Test {
    public static void main(String[] args) {
        Proxy proxy =new Proxy();
        proxy.request();
    }
}
public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("RealSubject request 执行");
    }
}
public class Proxy implements Subject{
    private RealSubject realSubject;
    public  Proxy(){
        this.realSubject = new RealSubject();
    }
    @Override
    public void request() {
        //在调用realSubject的request方法之前可以写一些代码来做"访问控制"
        String userId = "";
        if (!userId.equals("coderduck")) {
            System.out.println("哈哈哈,你不是coderduck,我不让你调用");
        }else {
            this.realSubject.request();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Proxy proxy =new Proxy();
        proxy.request();
    }
}
运行结果: 哈哈哈,你不是coderduck,我不让你调用

哇,代理模式原来如此简单哦,嘿嘿,是的,就是如此简单,那动态代理又是什么呢?大家想想如果有n个类需要做相似的访问控制,是不是需要n个代理类呢?哇,这多麻烦呢,鲁迅曾经说过,一个不懒的程序员不是一个好程序员,有没有一种好办法我给他一个接口,然后告诉他我要做的“控制”,然后帮我返回一个代理对象呢?这就是JDK的动态代理。

动态代理

废话不多说直接上代码,哈哈

package com.wjl.design_patterns.proxy_pattern.dynamic_proxy;

import com.wjl.design_patterns.proxy_pattern.static_proxy.RealSubject;
import com.wjl.design_patterns.proxy_pattern.static_proxy.Subject;
import com.wjl.design_patterns.proxy_pattern.static_proxy.Subject2;

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

/**
 * @author wangjinlinag
 * @Classname Test
 * @Description TODO
 * @Date 2021/2/21 12:51 上午
 */
public class Test {
    public static void main(String[] args) {
        InvocationHandlerImpl invocationHandler = new InvocationHandlerImpl(new RealSubject());
        Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class[]{Subject.class}, invocationHandler);
        proxy.request();

        InvocationHandlerImpl invocationHandler2 = new InvocationHandlerImpl(new Subject2() {
            @Override
            public void request2() {
                System.out.println("RealSubject2 request2 执行");
            }
        });
        Subject2 proxy2 = (Subject2) Proxy.newProxyInstance(Subject2.class.getClassLoader(), new Class[]{Subject2.class}, invocationHandler2);
        proxy2.request2();

    }
}
class InvocationHandlerImpl implements InvocationHandler{
    private Object realObject;
    public InvocationHandlerImpl(Object realObject){
        this.realObject = realObject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在调用realSubject的request方法之前可以写一些代码来做"访问控制"
        System.out.println("哈哈哈,我是一些访问控制");
        return method.invoke(realObject,args);

    }
}
运行结构:
哈哈哈,我是一些访问控制
RealSubject request 执行
哈哈哈,我是一些访问控制
RealSubject2 request2 执行

可以看到虽然代码有一点复杂,但是做到了可以为任何对象生成代理对象,做到统一的"访问控制"。

动态代理原理

步骤1: 生成字节码对象(或者从缓存里面找字节码对象)
步骤2: 第二个红框就是根据字节码对象获取构造器
步骤3: 通过反射来创建对象(传入我们的自定义InvocationHandler 对象 h.)

    @CallerSensitive
    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);
        }

        /*
         * Look up or generate the designated proxy class.
         */
         //生成字节码对象(或者从缓存里面找字节码对象)
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
			//步骤2: 第二个红框就是根据字节码对象获取构造器
            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;
                    }
                });
            }
            //步骤3: 通过反射来创建对象
            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);
        }
    }

字节码

通过上面的分析我们知道动态代理的原理是jdk替我们生成了代理对象的class字节码,然后根据class字节码创建代理对象,那么这个class字节码长什么样呢?其实可以把把它输出到磁盘上,然后再拖入dea反编译就大概知道这个类是什么样子的了。

 byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{Subject.class});
        try(
                FileOutputStream fos =new FileOutputStream(new File("/Users/a/Desktop/$Proxy.class"))
        ){
            fos.write(bytes);
            fos.flush();
        }catch (Exception e){
            e.printStackTrace();
        }
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.wjl.design_patterns.proxy_pattern.static_proxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy(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 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 request() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.wjl.design_patterns.proxy_pattern.static_proxy.Subject").getMethod("request");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

关键点1: 生成的类实现了Subject接口,这就解释了为什么我们经常说的
jdk动态代理是基于接口的”。
在这里插入图片描述
关键点2: 当我们调用代理对象的方法时,就会调用InvocationHandler的invoke方法.哈哈,这样就会进入我们实现Invoke的方法来进行统一的“访问控制”了,这个h就是我们传入的InvocationHandlerImpl的对象

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值