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<?>[] 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?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?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文件里增加如下代码:

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

使用方法和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代理模式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 轻松掌握Java代理模式

    定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 特点: 1.真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰. 2.代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用. 3.扩展性高 企业级开发和常用框架中的应用:最著名的就是spring的aop,还有spring的bea

  • Java基础之让你彻底搞懂代理模式

    一.代理模式 什么是代理模式? 先来生活常用例子:你想买票,你没必要去车站买:而是可以去一个代售点,代售点代理车站卖票,这就是一个简单的代理模式! 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上.在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层. 总结:我们访问实例对象时是通过代理对象访问的,这样比较灵活也可以添加一些

  • 浅谈JAVA设计模式之代理模式

    代理模式 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能.这种类型的设计模式属于结构型模式. 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口. 介绍 意图: 为其他对象提供一种代理以控制对这个对象的访问. 主要解决: 在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上.在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时

  • Java简单实现动态代理模式过程解析

    基本知识:JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能生成代理对象. 步骤: 1.首先建一个接口类,并提供一个实现类: public interface ISubject { public int add(int a, int b); } public class Subject implements ISubject { @Override public int add(int a, int b) { return a + b; } } 2.创建一

  • java代理模式与动态代理模式详解

    1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 生活中的例子:过年加班比较忙,没空去买火车票,这时可以打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费.但要清楚票务中心自己并不卖票,只有火车站才真正卖票,票务中心卖给你的票其实是通过火车站实现的.这点很重要!

  • Java线程创建静态代理模式代码实例

    一.背景 在通过Runnable接口创建线程时,启动线程需要借助Thread类,这里就涉及到了静态代理模式. 二.实例 以歌手演出为例,在演出的这个过程中,歌手与他的助理他们有一个共同的目标"完成这场演出". 为啥需要歌手需要有助理呢? 因为举办好一场演出有很多繁琐的事情要做,为了让歌手专心完成"唱歌"这件事,助理就需要在背后帮助歌手做很多事情. 1.助理负责帮助歌手做一些辅助工作,例如帮忙宣传.帮忙计划行程.帮忙订机票等等. 2.歌手负责唱歌这件事情. 三.实例的

  • Java代理模式实例详解【静态代理与动态代理】

    本文实例讲述了Java代理模式.分享给大家供大家参考,具体如下: 即Proxy Pattern,23种java常用设计模式之一.代理模式的定义:对其他对象提供一种代理以控制对这个对象的访问. Java的代理模式是Java中比较常用的设计模式,分为2中代理:静态代理与动态代理(JDK动态代理和cglib动态代理) 优点: 职责清晰 真实角色只需关注业务逻辑的实现,非业务逻辑部分,后期通过代理类完成即可. 高扩展性 不管真实角色如何变化,由于接口是固定的,代理类无需做任何改动. 缺点: 很明显的一点

  • Java代理模式详细解析

    代理模式是我们比较常用的设计模式之一.其中新思想是为了提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象.这些额外的操作通常需要与实际对象进行通信,代理模式一般涉及到的角色有: 抽象角色:声明真实对象和代理对象的共同接口: 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象.同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装. 真实角色:代理角色所代表的真实对象,

  • Java通俗易懂系列设计模式之代理模式

    前言 国内程序员好像普遍对百度都没好感,而且百度近些年产生了不少负面的新闻,像16年的魏则西事件,近期的导演吴京黑白照事件,以及最近作家六六斥百度李彦宏:"你是做搜索引擎还是骗子首领",还有一件就是与程序员有关的:搜索Julia语言,在百度和Google得出首条搜索结果的差异性而被吐槽.Google虽然受欢迎,但是在国内因内容审查问题未解决而不能使用,如果我们要使用它就必须使用代理服务器,由于放置代理服务器的地区区域可以访问google,所以我们可以先访问代理服务器,通过代理服务器转发

  • 23种设计模式(7) java代理模式

    23种设计模式第七篇:java代理模式 定义: 为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用 角色: 1.抽象角色:声明真实对象和代理对象的共同接口. 2.代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象.同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装. 3.真实角色:

  • Java代理模式实例分析

    本文实例讲述了Java代理模式.分享给大家供大家参考,具体如下: 一 不用代理模式的酒商 1 代码 class RealSubject // 真实角色(红酒厂商) { public void sell() { System.out.println("我是红酒厂商,欢迎品尝购买"); } } public class NoProxytest { // 客户端 public static void main( String[] args ) { RealSubject sub = new

随机推荐