继建造者模式后,又继续开启了代理模式啦。😁 Java设计模式系列-代理模式。你我一起坚持,让我们一起加油,还不会就一起学一学,会了咱就复习一下吧。😁 很喜欢一句话:“八小时内谋生活,八小时外谋生存”

你好,如果喜欢,请一起坚持!!望别日与君相见时,君已有所成。😁

共勉

一张旧图,恍惚间想到旧人

设计模式系列

一、前言

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。

在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。

1)概述:

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

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

2)结构:

代理(Proxy)模式分为三种角色:

  • 抽象角色: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实角色: 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
  • 代理(Proxy)类 : 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

3)静态代理和动态代理

根据代理的创建时期,代理模式分为静态代理和动态代理。

  • 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
  • 动态在程序运行时,运用反射机制动态创建而成

二、静态代理

我们通过一个 客户要去买二手房的经历为例子,以前没有中介的时候,都是直接找到房东去买,现在房东忙着其他的事,没时间搞这个,房产中介就作为一个代理,帮助卖房子,再收手续费。现在我们只需要找房产中介就能搞定这件事情了。

2.1、小案例

先看看图:

2.2、代码

SellHouse (抽象角色:通过接口或抽象类声明真实主题和代理对象实现的业务方法。)

public interface SellHouse {
    /**卖房接口方法*/
    void sell();
}
复制代码

Landlord (实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。 )

public class Landlord implements SellHouse{
    @Override
    public void sell() {
        System.out.println("房东出售房子!!");
    }
}
复制代码

ProxyPoint (代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作)

这里的附加操作就是收手续费啦😁

public class ProxyPoint implements SellHouse{

    private Landlord landlord=new Landlord();

    @Override
    public void sell() {
        System.out.println("房产中介收取中介费,帮助房东卖房子,!!");
        landlord.sell();
    }
}
复制代码

测试:

public class Client {
    public static void main(String[] args) {
        ProxyPoint point = new ProxyPoint();
        point.sell();
        /**
         *房产中介收取中介费,帮助房东卖房子,!!
         * 房东出售房子!!
         */
    }
}

复制代码

从上面测试代码中可以看出我们直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类

三、动态代理

例子还是上面那个哈,图就不给啦

接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

1、代码

SellHouse (抽象角色:通过接口或抽象类声明真实主题和代理对象实现的业务方法。)

public interface SellHouse {
    /**卖房接口方法*/
    void sell();
}
复制代码

Landlord (实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。 )

public class Landlord implements SellHouse {
    @Override
    public void sell() {
        System.out.println("房东出售房子!!");
    }
}
复制代码

ProxyFactory(它是代理类吗?)

public class ProxyFactory {

    private Landlord landlord = new Landlord();

    public SellHouse getProxyObject() {
        /**
         * 使用Proxy获取代理对象
         newProxyInstance()方法参数说明:
         ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
         Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
         InvocationHandler h : 代理对象的调用处理程序
         */
        SellHouse sellHouse=(SellHouse) Proxy.newProxyInstance(
                landlord.getClass().getClassLoader(),
                landlord.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     InvocationHandler中invoke方法参数说明:
                     proxy : 代理对象
                     method : 对应于在代理对象上调用的接口方法的 Method 实例
                     args : 代理对象调用接口方法时传递的实际参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("房产中介收取手续费");
                        // 执行真实对象  如果有返回值就将它返回回去
                        Object o = method.invoke(landlord, args);
                        return o;
                    }
                });
        return sellHouse;
    }
}
复制代码

昨天刚学到一招哈

再回到上面那个问题哈。

我们使用了JDK动态代理,ProxyFactory它是代理类?

答案:并不是的。ProxyFactory不是代理模式中所说的代理类,代理类是程序在运行过程中动态的在内存中生成的类

我们可以先在测试代码中打印一下哈。

System.out.println(proxyFactory.getClass());
System.out.println(house.getClass());
/**
* 输出
* class com.crush.jdk_proxy.ProxyFactory
* class com.sun.proxy.$Proxy0
*/
复制代码

我们可以看到真正动态生成的代理其实是class com.sun.proxy.$Proxy0。这个才是程序运行过程中。

接下来我用我昨天学到的东西,让大家一起看看,~~手法生疏 见谅见谅哈。~~😂

2、动态代理分析

慢慢来哈😁

我们可以通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】Java 诊断工具-下载地址

Arthas官方文档 (这个的在jdk8环境下用,使用到jdk8中的一个tools.jar的工具)

注意:(如果是其他的版本好像启动不了,我是电脑中有8和11,8没有配置环境变量,然后的话,就一直报错,我就将idea换成jdk8的版本,重新编译了,然后直接cmd在jdk8的环境下启动然后就还是可以)。

:为方便监控,我在测试方法中加上了一句while(ture){}

查看代理类的结构:

我们将我们获得的代理类的名字com.sun.proxy.$Proxy0 通过命令 jad来反编译 就可以获得如下数据

[arthas@3012]$ jad com.sun.proxy.$Proxy0
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@b4aac2
  +-sun.misc.Launcher$ExtClassLoader@c21c27

Location:

/*
 * Decompiled with CFR.
 *
 * Could not load the following classes:
 *  com.crush.jdk_proxy.SellHouse
 */
package com.sun.proxy;

import com.crush.jdk_proxy.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 invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.crush.jdk_proxy.SellHouse").getMethod("sell", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

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

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

复制代码

去掉无用信息后:

3、分析流程

程序运行过程中动态生成的代理类

//$Proxy0继承了Proxy实现了SellHouse
public final class $Proxy0 extends Proxy implements SellHouse {
    private static Method m3;

    // 这里用的父类的构造方法 下面有
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        m3 = Class.forName("com.crush.jdk_proxy.SellHouse").getMethod("sell", new Class[0]);
        return;
    }

    
    public final void sell() {
     	// 可以看到这里是真正执行的方法  这里的h 是父类中的 InvocationHandler 成员
        //invoke: 处理代理实例上的方法调用并返回结果。 当在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
        this.h.invoke(this, m3, null);
        return;
    }
}
复制代码

我们再接着看一下 Proxy关键东西哈。

public class Proxy implements java.io.Serializable {

    protected InvocationHandler h;

    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
} 
复制代码

现在我们再看一下我们的代理生成类ProxyFactory

public class ProxyFactory {

    private Landlord landlord = new Landlord();

    public SellHouse getProxyObject() {
        SellHouse sellHouse=(SellHouse) Proxy.newProxyInstance(
                landlord.getClass().getClassLoader(),
                landlord.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("房产中介收取手续费");
                        Object o = method.invoke(landlord, args);
                        return o;
                    }
                });
        return sellHouse;
    }
}

//测试代码
public class Client {

    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        SellHouse house = proxyFactory.getProxyObject();
        house.sell();
    }
}
复制代码

执行流程如下:

1. 在测试类中通过代理对象调用sell()方法
2. 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
复制代码

GCLB的动态代理就没有继续分析啦, 一些使用稍有不同,不过还是可以去了解的哈。😁

4优缺点:

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

  • 代理模式会造成系统设计中类的数量增加
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

四、自言自语

你卷我卷,大家卷,什么时候这条路才是个头啊。😇(还是直接上天吧)

有时候也想停下来歇一歇,一直做一个事情,感觉挺难坚持的。😁

你好,如果你正巧看到这篇文章,并且觉得对你有益的话,就给个赞吧,让我感受一下分享的喜悦吧,蟹蟹。🤗

如若有写的有误的地方,也请大家不啬赐教!!

同样如若有存在疑惑的地方,请留言或私信,定会在第一时间回复你。