热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Java设计模式之代理模式详解

这篇文章主要介绍了Java设计模式之代理模式详解,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好的帮助,需要的朋友可以参考下

一、代理模式

代理模式就是有一个张三,别人都没有办法找到他,只有他的秘书可以找到他。那其他人想和张三交互,只能通过他的秘书来进行转达交互。这个秘书就是代理者,他代理张三。

再看看另一个例子:卖房子

卖房子的步骤:

1.找买家

2.谈价钱

3.签合同

4.和房产局签订一些乱七八糟转让协议

一般卖家只在签合同的时候可能出面一下,其他的1,2,4都由中介去做。那你问这样有什么用呢?

首先,一个中介可以代理多个卖房子的卖家,其次,我们可以在不修改卖家的代码的情况下,给他实现房子加价、打广告等等夹带私货的功能。

而Java的代理模式又分为静态代理和动态代理

二、静态代理

静态代理中存在着以下的角色:

  • 抽象角色:一般使用接口或者抽象类实现(一般是真实角色和代理角色抽象出来的共同部分,比如卖房子的人和中介都有公共的方法卖房子)
  • 真实角色:被代理的角色(表示一个具体的人,比如卖房子的张三)
  • 代理角色:代理真实角色的中介,一般在代理真实角色后,会做一些附属的操作
  • 客户:使用代理角色来进行一些操作(买房子的)

代码实现:

//接口(抽象角色)
public interface Singer{
	// 歌星要会唱歌
	void sing();
}

实体类男歌手

//具体角色,男歌手
public class MaleSinger implements Singer{
    private String name;
    public MaleSinger(String name) {
        this.name = name;
    }
    @Override
    public void sing() {
        System.out.println(this.name+"男歌手在唱歌");
    }
}

歌手的经纪人

//代理角色
public class Agent implements Singer{
    private MaleSinger singer; //代理角色要有一个被代理角色
    public Agent(MaleSinger singer) {
        this.singer = singer;
    }
    @Override
    public void sing() {
        System.out.println("协商出场费,做会场工作");
        //一定是被代理角色歌手去唱歌
        singer.sing();
        System.out.println("会场收拾,结算费用");
    }
}

客户

//客户
public class Client {
    public static void main(String[] args) {
        MaleSinger singer=new MaleSinger("周杰伦");
        Agent agent=new Agent(singer);
        agent.sing();//通过代理来运行唱歌
    }
}

可以看到抽象角色就包含了具体角色和代理角色公共的方法sing()。然后通过歌手的经纪人在歌手唱歌的前后可以任意增加自己想要增加的代码。从而达到不修改歌手类方法的同时给唱歌增加新功能的目的。

说白了。代理就是在不修改原来的代码的情况下,给源代码增强功能。

小结

静态代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用(经纪人保护周杰伦,外界不能直接接触周杰伦)
  • 代理对象可以扩展目标对象的功能(本来只能唱歌,现在又多了协商出场费,做会场工作等等功能)
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

  • 代理模式会造成系统设计中类的数量增加(多了个代理类)
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢(每次都要先找中介才能找到周杰伦)
  • 增加了系统的复杂度(一开始只是个独立的流浪歌手,然后有了经纪人后就十分复杂了)

三、动态代理

静态代理中,比如上述的例子,我们所写的经纪人只能服务malesinger,不能再服务其他的类型的歌手,这很不现实。因为经纪人肯定能去服务不止一种歌手,甚至可能连歌手都不是,去服务跳舞的了。如果静态代理中要实现这个结果,那我们要手动编写好多个agent类,十分繁琐而复杂。所以就出现了动态代理,动态代理可以自动生成代理人的代码。

JDK原生的动态代理

核心类:InvocationHandler类Proxy类

我们重新写一下Singer接口,给他多一个跳舞的方法

//歌手接口
public interface Singer2 {
    void sing();
    void dance();
}

当然对应的男歌手实现类也要改变

//男歌手实现类
public class MaleSinger2 implements Singer2 {
    private String name;

    public MaleSinger2(String name) {
        this.name = name;
    }

    @Override
    public void sing() {
        System.out.println(this.name+"在唱歌");
    }

    @Override
    public void dance() {
        System.out.println(this.name+"在跳舞");
    }
}

然后我们直接进入客户,测试。

import com.hj.Agent2;
import com.hj.MaleSinger2;
import com.hj.Singer2;

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

public class Client2 {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//设置用于输出jdk动态代理产生的类
        //简单例子,把所有东西放到一段来解释
        System.out.println("实例1------------------------------------------------");
        MaleSinger2 maleSinger = new MaleSinger2("周杰伦");
        //新建代理实例
        //newProxyInstance(ClassLoader loader, 类加载器,不懂的可以去看https://blog.csdn.net/Doraemon_Nobita/article/details/115702012?spm=1001.2014.3001.5501
        //Class<&#63;>[] interfaces, 实现的接口,注意是个数组
        //InvocationHandler h 处理函数)
        Singer2 agent = (Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),
                new Class[]{Singer2.class}, new InvocationHandler() {//匿名内部类的方式实现InvocationHandler接口,对这个看不懂的可以参考https://blog.csdn.net/Doraemon_Nobita/article/details/115506705&#63;spm=1001.2014.3001.5501
                    @Override
                    // 这个invoke就是我们调用agent.sing()后调用的方法
                    // invoke(Object proxy, 代理对象
                    // Method method, method是方法,即我们要调用的方法(是唱歌还是跳舞,在调用的时候会是sing()还是dance())
                    // Object[] args 参数列表,可能你需要传参)
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("协商出场费,做会场工作");
                        //关于invoke的讲解,详情可以参考:https://blog.csdn.net/Doraemon_Nobita/article/details/115702012&#63;spm=1001.2014.3001.5501 调用指定的方法的那部分。
                        //invoke方法的参数,一个是Object类型,也就是调用该方法的对象,
                        //第二个参数是一个可变参数类型,也就是给这个方法的传参,外层的这个已经给我们封装成args了,直接用就是了
                        Object invoke = method.invoke(maleSinger,args);//通过反射获取到的method名我们再invoke激活一下,传入要调用该方法的对象。这里用maleSinger
                        System.out.println("会场收拾,结算费用");
                        return invoke;
                    }
                });
        agent.sing();//可以调用到maleSinger的sing()
        agent.dance();//调用到maleSinger的dance()
        System.out.println("实例2------------------------------------------------");
        //这个简单例子不行啊,我还每次必须写死这里是maleSinger,以后想换别的还得改这里。动态代理岂是如此不便之物。
        //所以我们直接实现一下InvocationHandler接口,取名为Agent2
        MaleSinger2 JayZ=new MaleSinger2("周杰伦");
        MaleSinger2 JJ =new MaleSinger2("林俊杰");
        Singer2 agentJJ=(Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),
                new Class[]{Singer2.class}, new Agent2(JJ));
        Singer2 agentJayZ=(Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),
                new Class[]{Singer2.class}, new Agent2(JayZ));
        //可以看到现在代理人创建就十分方便了
        agentJJ.dance();
        agentJJ.sing();
        agentJayZ.sing();
        agentJayZ.dance();
    }
}

在第一个例子中,可以看到我们需要利用Proxy类的newProxyInstance()方法就可以生成一个代理对象。而newProxyInstance()的参数又有类加载器、实现的接口数组、以及InvocationHandler对象。在这里使用匿名内部类来实现InvocationHandler接口。实现该接口需要实现他的invoke方法,这个方法就是我们代理对象调用原方法的时候会使用到的方法。区别于反射中的invoke方法,它有三个参数分别是代理对象,调用的方法,方法的参数数组。这里代理对象我们不管,调用的方法则是通过反射获取到的我们使用该代理调用sing()方法或者dance()方法的方法名。通过反射中的invoke方法,可以运行这个指定的对象里方法名的方法。

而第二个例子中,为了实现可以代理任何类,我们实现InvocationHandler接口,并把取类名为Agent2。下面是Agent2的代码。

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

public class Agent2 implements InvocationHandler {
    private Object object;//想代理谁都可以,随便啊

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("协商出场费,做会场工作");
        //一定是歌手去唱歌
        Object invoke = method.invoke(object,args);
        System.out.println("会场收拾,结算费用");
        return invoke;
    }
}

可以看出,这里和第一个例子的实现是差不多的,只不过我们使用Object类来代替了之前的写死的MaleSinger类,这样我们就可以代理任何的类型了,只要这个类型需要我们在前后加"协商出场费,做会场工作"、“会场收拾,结算费用”。那可以看到第二个例子中,林俊杰和周杰伦的代理人可以很方便地创建出来,哪怕后面再实现了一个FemaleSinger类,也可以直接生成他的代理人。

加了

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//设置用于输出jdk动态代理产生的类这句代码以后,我们就可以在项目中找到JDK自动生成的代理类代码:

在这里插入图片描述

打开可以看到就是自动生成的一段帮我们写代理的方法。

在这里插入图片描述

可以看到就是调用了h.invoke,这个h就是我们传参为InvocationHandler的对象,调用了我们自己写的invoke方法。

cglib动态代理

我们需要在maven配置文件中导入相应的包。在pom.xml文件里增加如下代码:


    
        cglib
        cglib
        3.3.0
    

使用方法和JDK的动态代理类似,只是我们不需要再实现接口了,定义一个普通类CglibMaleSinger.java

public class CglibMaleSinger {
    public CglibMaleSinger(String name) {
        this.name = name;
    }

    private String name;

    public CglibMaleSinger() {//注意这里一定要有无参构造器,不然之后会报错Superclass has no null constructors but no arguments were given
    }

    public void sing(){
        System.out.println(this.name+"要去唱歌了");
    }
    public void dance(){
        System.out.println(this.name+"要去跳舞了");
    }
}

然后直接在客户端测试:

import com.hj.CglibMaleSinger;
import net.sf.cglib.core.DebuggingClassWriter;
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 CglibClient {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"./class");//用于输出生成的代理class文件,"./class"表示存储在class文件夹中
        CglibMaleSinger JayZ=new CglibMaleSinger("周杰伦");
        Enhancer enhancer = new Enhancer();//定义一个增强器enhancer
        enhancer.setSuperclass(CglibMaleSinger.class);//设置其超类,我们要代理哪个类就传哪个类
        //MethodInterceptor是拦截器,就是把我的方法拦截住然后再去增强
        enhancer.setCallback(new MethodInterceptor() {//设置方法拦截器
            // o 是指被增强的对象,指自己
            // method是拦截后的方法,把父类的方法拦截,增强后写在了子类里
            // objs 参数
            // methodProxy 父类的方法(拦截前的方法对象)
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("谈出场费");
                method.invoke(JayZ,objects);
                System.out.println("出场费谈完了");
                return null;
            }
        });
        CglibMaleSinger cglibMaleSinger = (CglibMaleSinger)enhancer.create();
        cglibMaleSinger.sing();
        cglibMaleSinger.dance();
    }
}

和JDK的动态代理使用方法基本一致,只是invoke方法变成了intercept方法而已。

加上System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"存储路径");语句后,在你自己设置的存储路径下会出现一个包含生成的class文件的文件夹。

在这里插入图片描述

点开hj文件夹下的.class文件

在这里插入图片描述

可以看到是继承了我们的CglibMaleSinger类,并且重写了我们的方法,重写内容中调用了intercept()方法。

在这里插入图片描述

小结

Java动态代理只能够对接口进行代理,不能对普通类进行代理(因为所有生成的代理类的父类为Proxy,java不支持多重继承)CGLIB可以代理普通类Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;而CGLIB使用ASM框架直接对字节码(.class)改了,所以运行的时候是要比Java原生的效率要高些。

到此这篇关于Java设计模式之代理模式详解的文章就介绍到这了,更多相关Java代理模式内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • XNA 3.0 游戏编程:从 XML 文件加载数据
    本文介绍如何在 XNA 3.0 游戏项目中从 XML 文件加载数据。我们将探讨如何将 XML 数据序列化为二进制文件,并通过内容管道加载到游戏中。此外,还会涉及自定义类型读取器和写入器的实现。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 本文探讨了在通过 API 端点调用时,使用猫鼬(Mongoose)的 findOne 方法总是返回 null 的问题,并提供了详细的解决方案和建议。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
  • 将Web服务部署到Tomcat
    本文介绍了如何在JDeveloper 12c中创建一个Java项目,并将其打包为Web服务,然后部署到Tomcat服务器。内容涵盖从项目创建、编写Web服务代码、配置相关XML文件到最终的本地部署和验证。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 本文介绍如何在 Unity 的 XML 配置文件中,将参数传递给自定义生命周期管理器的构造函数。我们将详细探讨 CustomLifetimeManager 类的实现及其配置方法。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.xmlbeans.SchemaType 类的 getBaseEnumType() 方法,提供了多个代码示例,并解释了其在不同场景下的使用方法。 ... [详细]
  • 本文详细介绍了如何解决MyBatis中常见的BindingException错误,提供了多种排查和修复方法,确保Mapper接口与XML文件的正确配置。 ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 本文介绍如何使用布局文件在Android应用中排列多行TextView和Button,使其占据屏幕的特定比例,并提供示例代码以帮助理解和实现。 ... [详细]
author-avatar
嗳灬到此为止_769_836
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有