常用设计模式之代理模式【简单易懂】

学前先思

为什么要学习代理模式?
因为在常用的框架中会用到代理模式,如Spring框架:面向切面编程的底层实现!


什么是代理模式?

下面我们通过一个租房的案例来理解代理模式

在现实生活中:

总所周知,现在的大部分租房,离不开三个角色:租客中介房东

房东提供房子,要出租房屋,中介提供租房服务。他们两者都有个共同的点都是要实现租房这个目的,从而获取利益。

在这里插入图片描述

以前大部分是租客直接找房租,但是由于房东需要打广告、需要编写合同条款等等之类的事,由于中介的产生,房东只需要提供房子,无需花费额外的经历去发布信息,编写合同,与租客进行各种沟通。现在这些事情,都可以交给中介去完成,而且更加专业。在这个过程中,就可以理解为是一个代理模式。


在代码中:
代理模式的特征就是代理类和被代理类有相同的接口,代理类主要负责为被代理类预处理消息,过滤消息并把消息转发个给委托类,以及事后处理消息。代理类与被代理类之间会存在关联关系,一个代理类的对象与一个被代理类的对象关联,代理类本身并不实现真正的服务,而是通过被代理类存在的方法,来提供特定的服务。

代理模式结构图

在这里插入图片描述

  • Subject类(抽象类/接口),定义了RealSubject和Proxy的公用接口,这样就在任何使用RealSubject的地方都可以用Proxy
  • RealSubject类,定义了Proxy所代表的真实实体(被代理类)
  • Proxy类,保存一个引用使得代理可以访问实体(被代理类),并提供一个与Subject的接口相同的接口,这样代理就可以用来代替实体。
代理模式简图

在这里插入图片描述


代理模式的分类:

  • 静态代理
  • 动态代理

代理模式的优点:

  • 代理模式能将代理对象与真实被调用的目标对象分离。

  • 一定程度上降低了系统的耦合度,扩展性好。

  • 可以起到保护目标对象的作用。

  • 可以对目标对象的功能增强。

-公共业务在发生扩展时,方便集中管理

代理模式的缺点:

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

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

  • 增加了系统的复杂度。

代码步骤:
1、接口
2、真实角色(目标对象)
3、代理角色
4、客户端访问代理角色

代理模式之加深理解

前面我们讲到了代理模式的优点是降低系统耦合度对目标对象起到保护作用实现业务分工,方便集中管理。下面代码可以更好的理解他的这些优点

1、下面模拟实际开发,下面我们要实现增删改查操作,具体实现如下:

抽象角色-UserService

public interface UserService {
    
    
    public void add();

    public void update();

    public void delete();

    public void query();
}

真实角色-UserServiceImpl

public class UserServiceImpl implements UserService{
    
    
    @Override
    public void add() {
    
    
        System.out.println("增加用户");
    }

    @Override
    public void update() {
    
    
        System.out.println("修改用户");
    }

    @Override
    public void delete() {
    
    
        System.out.println("删除用户");
    }

    @Override
    public void query() {
    
    
        System.out.println("查询用户");
    }
}

现在BOSS有一个需求,就是在调用各自方法时,要增添一个日志功能,在没有使用代理方法时,我们需要在真实角色里面去修改或添加代码,如在调用增加方法时,只用日志功能,对应的代码如下:

public class UserServiceImpl implements UserService{
    
    
    @Override
    public void add() {
    
    
        System.out.println("调用了add方法");
        System.out.println("增加用户");
    }

上面的方法,是存在缺陷的,上面是在一个方法内增加日志功能,如果有很多情况下,代理量很高,而且改变了原有的实现类代码,下面的解决方法是添加一个代理

代理角色-UserServiceProxy

在代理角色中,我们需要通过set方法注入真实角色来实现原有的租房功能,并添加新的日志方法

public class UserServiceProxy implements UserService{
    
    
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
    
    
        this.userService = userService;
    }


    @Override
    public void add() {
    
    
        log("add");
        userService.add();
    }

    @Override
    public void update() {
    
    
        log("update");
        userService.update();
    }

    @Override
    public void delete() {
    
    
        log("delete");
        userService.delete();
    }

    @Override
    public void query() {
    
    
        log("query");
        userService.query();
    }

    public void log(String msg){
    
    
        System.out.println("[Debug]-:调用了"+msg+"方法");
    }
}

客户端-client

现在,我们可以直接去访问代理者,还实现各种基础的方法和附属的日志方法。这些都是没有改变原有的实现类下进行的。

public class Client {
    
    

    public static void main(String[] args) {
    
    
        UserServiceImpl userService = new UserServiceImpl();

        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);
        proxy.add();
    }
}

实现结果:
在这里插入图片描述

以上就是通过代码来理解代理模式的优点


一、静态代理

在学代理模式之前,我们还要对几个角色进行剖析

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决(比如上面租房这个目的)
  • 真实角色:被代理的角色(比如上面的房东
  • 代理角色:代理真实角色,代理真实角色后,一般会做一些附属操作(比如说中介
  • 客户:访问代理对象的人(租客

静态代理特点:代理对象与目标对象一起实现相同的接口或者继承相同父类,它由程序员创建或特定工具自动生成源代码,即在编译时就已经将接口,被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成,简单来说就是在程序员程序运行之前,就把持有目标对象的代理对象给写死,里面的目标对象也是写死的


下面我们通过具体的代码来进行深入理解:
我们可以通过代码来对租房这个过程进行实例化的操作,从而清楚代理模式在代码中的实际运用

代码实现

1、抽象角色

//租房
//抽象方法
public interface Rent {
    
    
    public void rent();
}

2、真实角色

/**
 * @author 朱霸霸
 * @date 2022/6/4 12:28
 */
//房东
//真实角色,目的要租房,因此要实现租房接口
public class Host implements Rent {
    
    

    @Override
    public void rent() {
    
    
        System.out.println("房东要出租房子");
    }
}

在以前,我们租房,可以直接找房东如下

public class Client {
    
    
    public static void main(String[] args) {
    
    
        Host host = new Host();
        host.rent();
    }
}

但显示是不容易找到更好的房东和房子,这是我们需要去找中介,因为中介手里有很多资源,因此下面产生了代理角色

3、代理角色


代理的原则是多组合少继承


在创建代理者角色之前,我们要想一想,中介本身是没有房子的,他是需要从房东那里获取到房子,因此首先要获取房东对象,至于建立房东对象,则要遵循上面的原则,中介除了在满足房东租房这个目的之外,他可能还会有自己的小算盘和自己的功能和方法,比如说他可以带租客看不同的房源

public class Proxy implements Rent{
    
    
    private Host host;//封装

    public Proxy(){
    
    }

    public Proxy(Host host) {
    
    
        this.host = host;
    }

    //租房功能
    @Override
    public void rent() {
    
    
        host.rent();
    }

    //看房功能
    public void seeHouse(){
    
    
        System.out.println("中介带你看房子...");
    }
    public void sign(){
    
    
        System.out.println("赶紧给我签合同...");

    }
    //手中介费,赚取差价
    public void free(){
    
    
        System.out.println("我是要赚差价滴...");
    }
}

4、客户端访问代理角色(租客)

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //中介拿取到房东客源
        Host host = new Host();
        //代理,中介帮房东租房子,一般会有一些附属操作,一些小算盘赚取差价
        Proxy proxy = new Proxy(host);

        //你不用面对房东,直接找中介租房即可
        proxy.rent();


    }
}


二、动态代理(JDK动态代理)

动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术


代码应用:

角色分析:

  • 动态代理和静态代理角色一样

  • 动态代理的代理类是在程序运行时创建的通过动态的方式来生成,运用了反射机制,不是我们直接 手动生成。

  • 动态代理分为两大类:基于接口的动态代理基于类的动态代理

    • 基于接口-----JDK动态代理
    • 基于类--------cglib动态代理

我们在使用JDK动态代理实现时,需要了解两个东西:Proxy(代理类):提供了静态方法来创建类似于接口实例的对象,但允许自定义的方法调用。 要为某个接口创建代理实例Foo InvitationHandler(接口):调用处理程序

一、InvitationHandler(接口):是由代理实例的调用处理程序实现的接口

  • 它是由代理实例的调度处理程序实现的一个接口
  • 每一个代理实现都有一个关联的调用程序,当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法

invoke方法:处理代理实例上的方法调用并返回结果。

  • 参数
    • proxy :调用该方法得代理实例
    • method:代理类的哪一个方法,也可以是一个接口
    • args:包含的方法调用传递代理实例的参数值的对象的阵列

简单来说:InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。在invoke方法中执行被代理对象target的相应方法

二、Proxy(代理类):

代理类是在运行时创建的实现指定的接口列表(称为代理接口)的类 。 代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,它实现接口InvocationHandler 。 通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke方法,传递代理实例,标识调用方法的java.lang.reflect.Method对象以及包含参数的类型为Object的数组。 调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。

proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的父类。

Proxy提供的方法

构造方法
Proxy​(InvocationHandler h): 从具有指定值的子类(通常为动态代理类)构造新的 Proxy实例。

成员方法

类型 方法 描述
static InvocationHandler getInvocationHandler​(Object proxy) 返回指定代理实例的调用处理程序。
static Class<?> getProxyClass​(ClassLoader loader, Class<?>… interfaces) 在命名模块中生成的代理类被封装,并且不能访问其模块外的代码。 Constructor.newInstance将在不可访问的代理类上调用IllegalAccessException 。 使用newProxyInstance(ClassLoader, Class[], InvocationHandler)代替创建一个代理实例。
static boolean isProxyClass​(Class<?> cl) 如果给定的类是代理类,则返回true。
static Object newProxyInstance​(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回指定接口的代理实例,该代理实例将方法调用分派给指定的调用处理程序。

代码实现

1、创建一个代理类和被代理类公共的接口

2、创建一个被代理类,被代理类要实现上面的接口

重点是下面:

3、创建一个可以动态生成代理的类,让该类动态生成代理类,该类要实现InvocationHandler接口,在该类在需要有一个被代理对象的实例target,InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。在invoke方法中执行被代理对象target的相应方法。在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。

/**
 * @author 朱霸霸
 * @date 2022/6/5 19:19
 * 使用该类自动生成代理类
 */

public class ProxyInvocationHandler implements InvocationHandler {
    
    
   //被代理目标
    private Object target;
   //通过set方法注入
    public void setTarget(Object target) {
    
    
        this.target = target;
    }

    //返回生成一个类型为Object的代理类
    //static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    /**参数:
     *  ClassLoader loader:该类的反射对象/类加载器来定义代理类
     *  Class<?>[] interfaces :被代理的接口
     *  InvocationHandler h :调度方法调用的调用处理函数
     */
    //返回指定接口的代理实例,该代理实例将方法调用分派给指定的调用处理程序。
   
    public Object getProxy(){
    
    
      return   Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this );
    }

    //处理代理实例上的方法调用并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    	//proxy:代表动态代理对象
    	//method:代表正在执行的方法
    	//args:代表调用目标方法时传递的参数

        //动态代理的机制就是使用反射
        log(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }

    public void log(String msg){
    
    
        System.out.println("执行了"+msg+"方法");
    }

}

3、Client类

在客户端访问动态代理对象时,我们需要提供真实角色(被代理角色),并创建一个与代理对象相关联的InvocationHandler ,通过这个对象来注入被代理角色,最后动态生成代理类,并执行方法。

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //真实角色(被代理角色)
        UserServiceImpl userService = new UserServiceImpl();

        //创建一个与代理对象相关联的InvocationHandler 
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        //设置要代理的对象
        pih.setTarget(userService);

        //动态生成代理类
        UserService proxy =(UserService) pih.getProxy();
        proxy.query();
    }

}

注明:部分内容引用自
张彦峰ZYF

猜你喜欢

转载自blog.csdn.net/qq_49385719/article/details/125120207