【代理模式】总结,你值得拥有!

点赞的靓仔,你最帅哦!

源码已收录github 查看源码,别忘了star哦!

什么是代理模式

代理模式,一个类代表另外一个类的功能。

在生活中并不缺乏代理模式的例子,比如火车票代售点,代理火车站销售火车票,代售点本身是没有火车班次的,而是代理了火车站的功能;房产中介代理房主销售房屋,中介本身没有房屋,而是代理房主的房屋进行销售。

那么问题来了,同样的功能,为什么需要代理呢?买房需要花费很多时间和精力,可能你不愿意这样,那么你可以用代理;买房可能需要复杂的程序,可能你并不熟悉,那么你需要代理来帮你处理。卖出房屋可能需要一定的宣传,你可能也不会,那么你更需要代理。那么可以看到,代理除了不具备被代理本身的功能之外,它可以在这个功能的基础上做很多事情,那这也就是代理模式的作用:隔离调用者与被调用者,功能增强

静态代理

静态代理是指类的创建编译过程在程序运行之前已经确定。即通过手动编码的方式来进行代理,案例类图结构如下。


image

在案例中,SellHouse为接口,定义销售房屋方法,而HouseMaster则为销售房屋的房主,SellProxy为销售代理,由销售代理对房主的销售进行代理并增强。
具体代码如下:

SellHouse代码

package demo.pattren.proxy.statics;

public interface SellHouse {
    void sell();
}

实现类HouseMaster

package demo.pattren.proxy.statics;

//房屋主人
public class HouseMaster implements SellHouse {
    @Override
    public void sell() {
        System.out.println("我是房屋的主人,我买掉房子");
    }
}

代理类SellProxy

package demo.pattren.proxy.statics;

//销售代理
public class SellProxy implements SellHouse{
    //通过注入被代理对象实现代理功能
    private SellHouse sellHouse;
    public SellProxy(SellHouse sellHouse){
        this.sellHouse = sellHouse;
    }
    @Override
    public void sell() {
        before();
        //买掉房子
        sellHouse.sell();
        after();
    }
    //前置增强
    private void before(){
        System.out.println("新代理房屋销售");
        System.out.println("我先来做一下宣传");
        System.out.println("根据实际情况合房主沟通调整一下售价");
    }
    //后置增强
    private void after(){
        System.out.println("房子卖掉了,我提取佣金");
    }
}

测试类

package demo.pattren.proxy.statics;

public class Test {
    public static void main(String[] args) {
        SellHouse house = new SellProxy(new HouseMaster());
        house.sell();
    }
}

动态代理

静态代理的实现是通过程序员编码代码来完成,虽然静态代理使代码有了一定的解耦,但是非常不灵活,如果做代理,那么每个接口都需要编写代理类,造成代码冗余,下面我们来看代理模式的另外一种实现——动态代理。接口类和实现类房主保持不变,而使用另外一种代理。
动态代理类SellDynamicProxy

package demo.pattren.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//动态代理
public class SellDynamicProxy implements InvocationHandler {
    private Object object;

    public SellDynamicProxy(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        //买掉房子
        Object result = method.invoke(object, args);
        after();
        return result;
    }
    //前置增强
    private void before(){
        System.out.println("新代理房屋销售");
        System.out.println("我先来做一下宣传");
        System.out.println("根据实际情况合房主沟通调整一下售价");
    }
    //后置增强
    private void after(){
        System.out.println("房子卖掉了,我提取佣金");
    }
}

动态代理测试类


package demo.pattren.proxy.dynamic;

import demo.pattren.proxy.statics.HouseMaster;
import demo.pattren.proxy.statics.SellHouse;
import java.io.IOException;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) throws IOException {
        SellHouse sellHouse = new HouseMaster();
        SellHouse proxy  = (SellHouse)Proxy.newProxyInstance(
                SellHouse.class.getClassLoader(),
                new Class[]{SellHouse.class},
                new SellDynamicProxy(sellHouse));
        proxy.sell();
    }
}

类图结构如下。

image

从代码看到,静态代理和动态代理的代理类和调用有明显的区别,首先,静态代理的代理类需要实现和业务类相同的接口,然后通过注入的形式调用业务类的功能,而动态代理则不同,动态代理则是实现InvocationHandler类,重写invoke方法来实现代理;而调用也有所不同,调用使用JDK提供的ProxynewProxyInstance方法来创建对象。

动态代理底层原理

动态代理创建的是SellHouse的一个子类,通过Debug来看一下创建的到底是什么对象。

image

发现生成了一个类$Proxy0@572,那么JDK是如何去创建这样一个并不存在的Java类呢?我们通过代码入口Proxy.newProxyInstance来一探究竟。

Proxy.newProxyInstance

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    //关键代码
    Class<?> cl = getProxyClass0(loader, intfs);
}

Proxy.getProxyClass0

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
     //接口数量不能大于65535                                  
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //从缓存获取
    return proxyClassCache.get(loader, interfaces);
}

WeakCache.get

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    expungeStaleEntries();
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    //缓存为空则新建
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }
    //新建并放入缓存
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
}

下面是创建类的关键代码

Proxy.ProxyClassFactory.apply

private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    //所有生成类名的前缀
    private static final String proxyClassNamePrefix = "$Proxy";
    //用于类名的编号
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
       Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }

            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }

            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }

        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            //如果package为空,则使用proxy包
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        //生成字节码数组
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            //通过类加载器生成代理类
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

现在,代理类创建完成,接下来就是通过类创建对象了。
Proxy.newProxyInstance方法接收三个参数,第一个是类加载器,第二个是代理的接口,第三个是一个
InvocationHandler,这个参数是干什么的的? 我们再回到SellDynamicProxy类, 其实现了InvocationHandler接口,而传入的对象正是SellDynamicProxy类型。而InvocationHandler与生成的代理类也是有关系的,通过源码来一探究竟。
Proxy.newProxyInstance

 @CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    //创建class代理类对象
    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;
                }
            });
        }
        //通过反射创建对象,并且InvocationHandler是构造函数的一个参数
        return cons.newInstance(new Object[]{h});
        //...more code
}

现在整个结构基本清楚了,首先通过Java反射与Proxy代理动态的在内存中创建class对象,并通过反射创建对象。而创建对象使用了InvocationHandler作为参数,由此可以证明动态生成的代理类有一个InvocationHandler的有参构造器。
下面通过文件流的方式,讲class字节码写入硬盘。

//生成class字节码,在源码中能够看到也有使用这个方法
byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{SellHouse.class});
//将字节码写入硬盘
FileOutputStream fileOutputStream = new FileOutputStream("D://$Proxy0.class");
fileOutputStream.write($Proxy0s);
fileOutputStream.close();

然后通过反编译(如果是IDEA,直接将文件拖动到IDEA即可查看),查看生成的源码。
如果上面的代码看得比较晃眼,那么也不用去看。但是一定要清楚这样一个方法做了什么事情。

import demo.pattren.proxy.statics.SellHouse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements SellHouse {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    public $Proxy0(InvocationHandler var1) throws  {
        //调用Proxy的方法,为Proxy的h属性赋值
        super(var1);
    }
    //省略代码
    public final boolean equals(Object var1) {}
    //省略代码
    public final String toString(){}
    //重写sell方法
    public final void sell() throws  {
        try {
            //调用SellDynamicProxy的sell方法,m3就是SellHouse定义的sell方法,在静态代码块中查看
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //省略代码
    public final int hashCode(){}
    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("demo.pattren.proxy.statics.SellHouse").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

看了这么多代码已然晕车,没关系,来画个图。


image

CgLib动态代理

JDK的动态代理技术使用还是很方便的,但是也有一定的限制,那就是被代理的类需要有一个接口,当被代理类没有接口的情况下,可以选择另外一种动态代理方式——Cglib。

CgLib动态代理底层使用基于ASM的字节码技术,Cglib的代理结构非常简单,即应用Java集成机制实现代理效果。

image

目标类使用上一个案例的HouseMaster,下面编写拦截器类CgLibInteceptor

package demo.pattren.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CgLibInteceptor implements MethodInterceptor {
    //被代理的目标对象
    private Object target;
    public Object getInstance(final Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        //买掉房子
        Object result = method.invoke(target, objects);
        after();
        return result;
    }
    //前置增强
    private void before() {
        System.out.println("新代理房屋销售");
        System.out.println("我先来做一下宣传");
        System.out.println("根据实际情况合房主沟通调整一下售价");
    }
    //后置增强
    private void after() {
        System.out.println("房子卖掉了,我提取佣金");
    }
}

然后编写测试类:

package demo.pattren.proxy.cglib;

import demo.pattren.proxy.statics.HouseMaster;
import org.springframework.cglib.core.DebuggingClassWriter;

import java.io.IOException;

public class CgLibTest {
    static{
        //将cglib生产的class写入本地,然后再使用idea反编译查看,写入static即保证在运行期执行
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideaWorkspace\\demo2\\src\\main\\java\\demo\\pattren\\proxy\\cglib");
    }
    public static void main(String[] args) throws IOException {
        HouseMaster sellHouse = new HouseMaster();
        CgLibInteceptor cgLibProxy = new CgLibInteceptor();
        HouseMaster h = (HouseMaster)cgLibProxy.getInstance(sellHouse);
        h.sell();
    }
}

与预期一致,通过Inteceptor类成功实现了sell()方法的前后增强功能。但是前面也提到了Cglib是通过集成来实现代理的,在测试类中,我们加入了一段static静态代码块,目的是将Cglib在运行过程中生成的代理类写入硬盘。
通过Idea的反编译,我们来查看代理类的代码,由于代码过多,仅截取关键部分。


image

通过反编译的代码发现,生成的代理类确实是目标类的实现。


image

另外,使用继承的方式有一个弊端,那就是对于定义了final属性的方法是无法实现代理的。

常用框架的动态代理

  1. SpringAop
    Spring的面向切面编程极大的简化了我们的日常开发工作,而Spring的Aop则是应用了动态代理技术,并且支持JDK动态代理和Cglib代理两种方式,这个问题也经常出现于面试题中。

问:请解释Spring动态代理?
答:Spring动态代理有两种方式,当目标类是接口时,默认使用JDK动态代理,而没有接口的情况下使用Cglib代理。
问:他们的区别?
答:JDK动态代理使用Proxy与InvocationHandler实现,其使用反射机制生成被代理的实现类,要求目标类有实现接口。Cglib使用ASM字节码技术生成被代理类的子类,是继承关系。在JDK低版本时,JDK动态代理的生成效率较高,而运行效率较低,而Cglib的生成效率低但是运行效率高,但随着JDK的优化,在1.8时运行差距已经非常小。 参考 : https://blog.csdn.net/xlgen157387/article/details/82497594

  1. MyBatis动态代理
    相信大家对Mybatis的Mapper接口 + XML的开发模式已经非常熟悉,在开发的时候,我们只需要编写Mapper接口与XML就可以实现DAO操作。 通过本篇文章的学习,相信你已经知道MyBatis的套路,其实就是使用JDK动态代理生成了Mapper接口的实现类,从而根据XML的定义完成DAO操作。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容