Java设计模式-代理模式 理论代码相结合

简介: Java设计模式-代理模式 理论代码相结合

微信截图_20220524184322.png



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

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

共勉


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


设计模式系列


一、前言


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


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


1)概述:


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


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


2)结构:


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


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


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


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


3)静态代理和动态代理


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


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


  • 动态在程序运行时,运用反射机制动态创建而成


二、静态代理



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


2.1、小案例


先看看图:


微信截图_20220524184540.png


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){}


查看代理类的结构:


微信截图_20220524184750.png


我们将我们获得的代理类的名字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优缺点:


代理模式的主要优点有:


  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;


  • 代理对象可以扩展目标对象的功能;


  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性


其主要缺点是:


  • 代理模式会造成系统设计中类的数量增加


  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;


  • 增加了系统的复杂度;


四、自言自语


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


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


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


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


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


持续更新中


目录
相关文章
|
8天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
22天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
35 5
Java反射机制:解锁代码的无限可能
|
18天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
52 3
|
19天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
18天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
5月前
|
设计模式 缓存 安全
Java设计模式的单例模式应用场景
Java设计模式的单例模式应用场景
60 4
|
2月前
|
设计模式 安全 Java
Java 编程中的设计模式:单例模式的深度解析
【9月更文挑战第22天】在Java的世界里,单例模式就像是一位老练的舞者,轻盈地穿梭在对象创建的舞台上。它确保了一个类仅有一个实例,并提供全局访问点。这不仅仅是代码优雅的体现,更是资源管理的高手。我们将一起探索单例模式的奥秘,从基础实现到高级应用,再到它与现代Java版本的舞蹈,让我们揭开单例模式的面纱,一探究竟。
41 11
|
3月前
|
设计模式 存储 负载均衡
【五】设计模式~~~创建型模式~~~单例模式(Java)
文章详细介绍了单例模式(Singleton Pattern),这是一种确保一个类只有一个实例,并提供全局访问点的设计模式。文中通过Windows任务管理器的例子阐述了单例模式的动机,解释了如何通过私有构造函数、静态私有成员变量和公有静态方法实现单例模式。接着,通过负载均衡器的案例展示了单例模式的应用,并讨论了单例模式的优点、缺点以及适用场景。最后,文章还探讨了饿汉式和懒汉式单例的实现方式及其比较。
【五】设计模式~~~创建型模式~~~单例模式(Java)
|
2月前
|
设计模式 Java 安全
Java设计模式-单例模式(2)
Java设计模式-单例模式(2)
|
6月前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式