栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

设计模式 - 代理模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

设计模式 - 代理模式

文章目录

前言1. 概述2. 结构3. 静态代理4. JDK动态代理

1. 代码2. 实现过程 5. cglib动态代理6. 三种模式的对比7. 优缺点

1. 优点2. 缺点 8. 使用场景


前言

文章参考黑马的设计模式讲义以及c语言中文网教程C语言中文网教程,菜鸟教程:菜鸟教程。还有一些自己的理解做出来的笔记。黑马视频: 黑马设计模式。下面介绍代理模式,代理模式在Java中的运用很多,其中比较场景的是 SpringAOP 中使用的


提示:以下是本篇文章正文内容,下面案例可供参考


1. 概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的用法在日常生活也很常见,比如明星和经纪人,以及租房和中介以及买车票和 12306 等,这些事情在日常生活中需要第三者介入去负责,从整体上看就是代理的模式。

而在 Java 中当两个类之间需要解耦的时候又或者因为权限原因要禁止某个用户访问某个页面,这个过程我们可以使用代理模式帮我们去做。

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



2. 结构

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

抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。



3. 静态代理

下面来看火车站售票的例子:用户需要买票的时候如果每次需要跑到火车站去买,就很麻烦了。所以可以通过一些代售点或者手机app来购买车票,这样就方便很多。我们把这些代售点或者app叫做代理类。

1、抽象主题类(卖票方法)

public interface SellTickets {
    void sell();
}

2、真实主题类(火车站实现卖票方法)

public class TrainStation implements SellTickets{

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

3、代理对象(帮我们去火车站买票的)

public class ProxyPoint implements SellTickets{
    private TrainStation station = new TrainStation();

    @Override
    public void sell() {
        System.out.println("代理点收取一些服务费用");
        station.sell();
    }
}

4、客户端类,找代理类去买车票

public class Client {
    public static void main(String[] args) {
        ProxyPoint pp = new ProxyPoint();
        pp.sell();
        //代理点收取一些服务费用
        //火车站卖票
    }
}



4. JDK动态代理

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

和静态代理上面不同的是,动态代理的代理类是在程序执行的过程中动态创建的,而不是一开始就固定好的。代理对象是动态返回的,我们先来看下面的代码:


1. 代码

1、卖票的接口

public interface SellTickets {
    void sell();
}

2、火车站(实现卖票的接口)

public class TrainStation implements SellTickets {

    @Override
    public void sell() {
        System.out.println("火车站卖票");
    }
}

3、代理工厂类,用来返回代理对象的

public class ProxyFactory {
    //声明目标对象
    private TrainStation station = new TrainStation();



    public SellTickets getProxyObject(){
        //返回代理对象
         
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点收取一定的服务费用");
                        Object invoke = method.invoke(station, args);
                        return invoke;
                    }
                }
        );
        return proxyObject;
    }
}

4、客户端类

public class Client {
    public static void main(String[] args) throws IOException {
        //1.获取代理对象
        ProxyFactory factory = new ProxyFactory();
        //2.使用getProxyObject方法获取代理对象
        SellTickets proxyObject = factory.getProxyObject();
        //3.代理对象调用卖电脑方法
        proxyObject.sell();
		//输出代理对象的类
        System.out.println(proxyObject.getClass());
        //让程序一直运行
        System.in.read();
    }
}

5、执行结果



2. 实现过程

下面我们来看上面的实现方法,最主要的就是 getProxyObject 方法。

里面的三个参数:

    代理类的类加载器(要返回的代理对象的类加载器),在这个例子中,这个参数就是要返回的TrainStation的类,返回的 proxy 是这个类型的真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口(卖票的接口)里面的 invoke 方法就是在执行方法之前做一个加强的,当代理对象 proxy 调用里面的方法的时候就会进入 invoke 流程,在这个流程中可以对这个方法(这俄个例子中是 proxyObject.sell() )前后进行一些准备工作,类似于 AOP 的增强。返回值就是方法执行的结果

要注意的是,ProxyFactory 并不是代理类,代理类是根据传入的参数在程序运行的过程中生成的类,我们使用阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:

1、让程序一直运行

2、运行 jar 包

3、使用指令 jad + 全类名

4、得到下面的代码:

package com.sun.proxy;

import com.jianglianghao.proxy.jdk_proxy.SellTickets;
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 SellTickets {
    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.jianglianghao.proxy.jdk_proxy.SellTickets").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);
        }
    }
}

5、把其中一些不必要的代码删去,得到简化之后的代理类

我们看到下面的代理类执行 sell 方法的时候实际上是执行了我们传入的第三个参数 InvocationHandler 中的 invoke 方法。而且可以看到

代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

package com.sun.proxy;

import com.jianglianghao.proxy.jdk_proxy.SellTickets;
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 SellTickets {
	//这里就是要执行的方法
    private static Method m3;
	
	//调用父类的invocationHandler方法进行赋值,也就是Proxy 类中的 h
	//注意这里的invocationHandler就是我们在 ProxyFactory 里面调用 
	//newProxyInstance的时候传入的具体实现类
    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    //下面是父类Proxy 的代码
    //protected InvocationHandler h;
	//protected Proxy(InvocationHandler h) {
    //    Objects.requireNonNull(h);
    //    this.h = h;
    //}
    
    static {
  		    //对方法3进行赋值,这个方法就是我们定义的卖票的方法
            m3 = Class.forName("com.jianglianghao.proxy.jdk_proxy.SellTickets").getMethod("sell", new Class[0]);
    }
 	
 	//代理类调用 sell 方法的时候就会执行这里的操作
    public final void sell() {
       //使用父类中的 h 来执行 invoke方法
        this.h.invoke(this, m3, null);
        //返回
        return;
    }
}

到这里,我们就可以得到代码整体的流程是什么样了:

    使用 ProxyFactory 中的 Proxy.newProxyInstance 方法根据传入的类和接口以及回调方法创建一个动态的代理类在客户端调用 sell() 方法的时候,实际上根据多态的特性,执行的是代理类($Proxy0)中的sell()方法代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法,然后返回了结果

明白了整个流程之后,我们再来看如果想要对多个方法都是用一个代理类,那么只需要在 invoke 方法中调用 method.getName 获取方法的名字,然后根据不同方法的名字做不同的处理就可以了。



5. cglib动态代理

同样是上面的案例,我们再次使用CGLIB代理实现。这种代理模式针对的是没有接口的代理模式。如果没有接口,那么就无法使用 JDK 的动态代理,因为 JDK 的动态代理需要传入接口,下面就来看看这种代理模式是怎么样使用的:

1、引入 maven


    cglib
    cglib
    2.2.2

2、火车站卖票

public class TrainStation{

    public void sell() {
        System.out.println("火车站卖票");
    }
}

3、动态工厂

//工厂类,用来获取代理对象
public class ProxyFactory implements MethodInterceptor {

    //声明火车站对象
    private TrainStation station = new TrainStation();

    public TrainStation getProxyObject(){
        //1. 创建 Enhance对象,类似jdk代理中的proxy代理类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类的字节码对象,指定父类(代理类的父类)
        enhancer.setSuperclass(TrainStation.class);
        //3. 设置回调函数,传入MethodInterceptor的子实现类对象,也就是ProxyFactory,重写intercept方法,回调函数就是这个,等到代理类调用方法的时候就会
        //调用这个类里面定义的intercept方法
        enhancer.setCallback(this);
        //4. 创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }
	
	//在代理类调用方法的时候会调用本类中的intercept方法
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        method.invoke(station, objects);
        System.out.println("方法执行了");
        return null;
    }
}


4、客户端类

public class Client {
    public static void main(String[] args) {
        //创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        //获取代理对象
        TrainStation proxyObject = factory.getProxyObject();

        proxyObject.sell();
    }
}

5、执行结果



6. 三种模式的对比

jdk代理和CGLIB代理

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

动态代理和静态代理

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题



7. 优缺点 1. 优点

代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。代理对象可以扩展目标对象的功能。比如上面的卖票方法,我们使用了动态代理之后,可以在调用 invoke 方法之前做一些其他的处理,达到AOP的效果代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度动态代理模式进行方法调用的时候所有的方法最终都会在同一个方法里面被调用,实现集中处理的效果



2. 缺点

增加了系统的复杂度。特别是动态代理,如果不了解底层,那么有可能会不知道这个方法的执行原理是什么。在目标类和客户端类之间增加一个代理类,导致请求的速度变慢了



8. 使用场景

远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

最后还有一种就是使用代理模式对方法进行增强。





如有错误,欢迎指出!!!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/732175.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号