设计模式结构型——代理模式

目录

代理模式的用途

代理模式的实现

静态代理

JDK动态代理

CGLIB动态代理

代理模式的特点

与其他模式比较


        代理模式(Proxy Pattern)是一种结构型设计模式,它允许通过创建一个代理对象来间接访问原始对象。代理模式的核心思想是将对目标对象的访问进行控制,并在访问前后执行一些额外操作,以增强或限制原始对象的功能。

        在代理模式中,代理对象和原始对象都实现相同的接口,使得客户端无需知道实际的对象,只需要通过代理去访问目标对象。代理对象可以拦截对目标对象的访问,然后决定是否允许、何时以及如何访问目标对象。

        总之,代理模式通过引入代理对象,实现了对目标对象的间接访问和控制,提供了更加灵活和安全的对象访问方式。

代理模式的用途

代理模式的常见用途包括:

远程代理:通过代理对象在不同的地址空间中访问远程对象,隐藏了网络通信细节。
虚拟代理:延迟创建开销较大的对象,直到真正需要使用时才进行创建,以提升性能。
安全代理:控制对敏感对象的访问,限制非授权用户的操作权限。
智能代理:在访问对象前后执行额外的逻辑,如缓存结果、记录日志、实现懒加载等。

代理模式的实现

代理模式的角色

抽象主题角色(Subject):定义了真实主题(Real Subject)和代理(Proxy)之间的共同接口,代理类和被代理类都要实现该接口,这样代理对象可以替代真实主题进行操作。

真实主题角色(Real Subject):定义了代理所代表的真实对象,是最终执行业务逻辑的对象,供代理角色调用。

代理角色(Proxy):实现了抽象主题接口,并维护一个指向真实主题对象的引用,是真实角色的代理, 需要持有真实角色的引用。代理对象可以在执行真实主题操作前后进行一些额外处理。

        通过代理模式,客户端可以通过与代理对象进行交互来完成任务,代理对象在必要时会调用真实对象来执行具体的业务逻辑。这种方式可以增加额外的功能,同时也可以隐藏真实对象的细节,实现了客户端与真实对象之间的解耦。

代理模式的类图

设计模式结构型——代理模式_第1张图片

代理模式的分类

代理模式分为:静态代理,JDK动态代理,CGLIB动态代理三类。

静态代理、JDK动态代理和CGLIB动态代理都是常见的代理模式实现方式,它们在实现上有一些区别:

静态代理:

  1. 静态代理需要手动编写代理类,代理类和真实类实现相同的接口或继承相同的父类。
  2. 在编译期间,代理类的代码就已经确定,无法在运行时动态修改代理行为。
  3. 需要为每个真实类编写一个对应的代理类,导致代码冗余。

JDK动态代理:

  1. JDK动态代理利用Java反射机制生成代理类,无需手动编写代理类。
  2. 基于接口的代理,代理对象必须实现一个或多个接口。
  3. 代理类是在运行时动态生成的,可以通过InvocationHandler接口在代理类的方法前后插入额外逻辑。
  4. JDK动态代理只能代理实现了接口的类,对于没有实现接口的类不能进行代理。

CGLIB动态代理:

  1. CGLIB是针对类进行代理的方式,通过继承来实现代理。
  2. CGLIB动态代理不需要目标类实现接口,因此可以代理没有实现接口的类。
  3. 代理类是通过字节码技术在运行时动态生成的,生成的代理类是目标类的子类。
  4. CGLIB代理相比JDK动态代理的效率略低。

        综上所述,静态代理在编译期间就确定代理类,需要为每个真实类编写代理类;JDK动态代理通过反射在运行时动态生成代理类,代理对象必须实现接口;而CGLIB动态代理是针对类进行代理,不需要实现接口,通过继承在运行时生成代理类。根据具体需求和场景,选择适合的代理方式。

下面将依次展现三种代理模式的实现

静态代理

其代码实现如下

抽象主题角色代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 抽象主题角色(Subject) 代理人
 * @date 2023/07/23 10:57:42
 */
public interface Agency {
    void renting();
}

真实主题角色代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 真实主题角色(Real Subject) Evan
 * @date 2023/07/23 10:59:29
 */
public class Evan implements Agency{

    @Override
    public void renting() {
        System.out.println("Evan 有一百套房子要出租 ");
    }
}

代理角色代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 代理角色(Proxy) 房屋代理人
 * @date 2023/07/23 11:00:31
 */
public class ProxyAgency implements Agency {

    private Agency agency;

    public ProxyAgency(Agency agency){
        this.agency = agency;
    }

    @Override
    public void renting() {
        System.out.println("向房客出租房屋");
        this.agency.renting();
        System.out.println("完成售后服务");
    }
}

测试代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 客户类
 * @date 2023/07/23 11:03:21
 */
public class Client {

    public static void main(String[] args) {
        Agency evan = new Evan();
        Agency agency = new ProxyAgency(evan);
        agency.renting();
    }
}

测试截图

设计模式结构型——代理模式_第2张图片

JDK动态代理

编写一个jdk代理实例的基本步骤如下:

  1. 编写业务接口
    因为jdk代理是基于接口的,因此,只能将业务方法定义成接口,但它可以一次生成多个接口的代理对象
  2. 编写调用处理器
    即编写一个java.lang.reflect.InvocationHandler接口的实现类,代理对象的业务逻辑就写成该接口的invoke方法中
  3. 生成代理对象
    有了业务接口和调用处理器后,将二者作为参数,通过Proxy.newProxyInstance方法便可以生成这个(或这些)接口的代理对象。比如上述示例代码中的businessProxy对象,它拥有greeting()这个方法,调用该方法时,实际执行的就是invoke方法。

其代码实现如下

抽象主题角色代码

package com.common.demo.pattern.proxyJdk;

/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 抽象主题角色(Subject) 代理人
 * @date 2023/07/23 10:57:42
 */
public interface JdkAgency {
    void renting();
}

真实主题角色代码

package com.common.demo.pattern.proxyJdk;

/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 真实主题角色(Real Subject) JdkEvan
 * @date 2023/07/23 10:59:29
 */
public class JdkEvan implements JdkAgency {

    @Override
    public void renting() {
        System.out.println("JdkEvan 有一百套房子要出租 ");
    }
}

代理角色代码

package com.common.demo.pattern.proxyJdk;

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

/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 代理角色(Proxy) 房屋代理人
 * @date 2023/07/23 11:00:31
 */
public class JdkProxyAgency implements InvocationHandler {

    //真实对象
    private Object target;

    /**
     * 建立代理对象和真实对象的代理关系方法,并返回代理对象
     *
     * @param target 真实对象
     * @return 代理对象
     */
    public Object bing(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("带领房客看房...签租房协议");
        Object result = method.invoke(target, args);
        System.out.println("售后服务");
        return result;
    }
}

测试代码

package com.common.demo.pattern.proxyJdk;


/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 客户类
 * @date 2023/07/23 11:03:21
 */
public class JdkClient {

    public static void main(String[] args) {

        JdkProxyAgency jdkProxyAgency = new JdkProxyAgency();
        //绑定关系,因为挂在JdkAgency接口下,所以声明代理对象 jdkProxyAgency
        JdkAgency proxy = (JdkAgency) jdkProxyAgency.bing(new JdkEvan());
        //注意 此时JdkEvan对象已经是一个代理对象,它会进入代理的逻辑方法invoke
        proxy.renting();
    }
}

测试截图

设计模式结构型——代理模式_第3张图片

CGLIB动态代理

编写一个cglib代理实例的基本步骤如下:

  1. 添加依赖:首先,需要在项目中添加 CGLIB 的相关依赖。可以通过 Maven 或其他构建工具将 CGLIB 加入到项目中。

  2. 创建目标类:定义一个目标类(被代理类),它不需要实现任何接口。

  3. 创建拦截器类:编写一个拦截器类,实现 MethodInterceptor 接口,并重写 intercept 方法。该方法在代理对象的方法调用前后执行额外的逻辑。

  4. 创建代理对象:使用 CGLIB 的 Enhancer 类来生成代理对象。设置目标类为父类,设置拦截器为回调方法。

  5. 调用代理对象:通过代理对象调用目标类的方法,这时会先调用拦截器中的逻辑,再调用目标类的方法。

抽象主题角色代码(可不需要)

真实主题角色代码

package com.common.demo.pattern.proxyCglib;

/**
 * @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 真实主题角色(Real Subject) CglibEvan
 * @date 2023/07/23 10:59:29
 */
public class CglibEvan{
    public void renting() {
        System.out.println("CglibEvan 有一百套房子要出租 ");
    }
}

代理角色代码

package com.common.demo.pattern.proxyCglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 代理角色(Proxy) 房屋代理人
 * @date 2023/07/23 11:00:31
 */
public class CglibProxyAgency implements MethodInterceptor {
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("带领房客看房...签租房协议");
        // 动态的回调父类当中的方法
        methodProxy.invokeSuper(o, objects);
        System.out.println("售后服务");
        return o;
    }
}

测试代码

package com.common.demo.pattern.proxyCglib;

import net.sf.cglib.proxy.Enhancer;

/**
 * @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 客户类
 * @date 2023/07/23 11:03:21
 */
public class CglibClient {

    public static void main(String[] args) {

        //生成空的字节码对象
        Enhancer enhancer = new Enhancer();
        // 设置这个字节码对象的父类(目标对象)
        enhancer.setSuperclass(CglibEvan.class);
        //设置被增强的方法
        enhancer.setCallback(new CglibProxyAgency());
        //得到代理对象
        CglibEvan factory = (CglibEvan) enhancer.create();
        //执行被代理的方法
        factory.renting();
    }
}

测试截图

设计模式结构型——代理模式_第4张图片

代理模式的特点

优点:

  1. 隐藏对象的具体实现:代理模式通过代理对象与客户端进行交互,客户端无需知道实际的对象是如何实现的,从而将对象的具体实现细节隐藏起来。
  2. 控制对对象的访问:代理对象可以控制对目标对象的访问,并在访问前后执行一些额外操作。例如,在访问一个敏感对象时,代理可以验证用户的权限;在访问一个远程对象时,代理可以处理网络通信等。
  3. 扩展原始对象的功能:代理模式可以通过在代理对象中添加一些额外的功能,来扩展原始对象的功能。这样可以避免直接修改原始对象的代码,符合开闭原则。
  4. 分离客户端和目标对象:代理模式使得客户端只需要与代理对象进行交互,无需直接与目标对象交互。这样可以降低系统的耦合度,提高系统的灵活性和可维护性。
  5. 懒加载:在使用虚拟代理时,可以延迟创建开销较大的对象,直到真正需要使用时才进行创建。这样可以提升系统的性能,并减少资源的占用。
  6. 透明性:代理模式可以实现透明的访问,即客户端无需感知自己正在使用的是代理对象还是目标对象,可以将代理视为目标对象的透明扩展。

缺点:

  1. 引入代理对象会增加系统的复杂性,涉及到多个类之间的交互,增加了开发和维护的成本。
  2. 由于代理模式需要额外的对象来进行封装和管理,可能会导致系统的运行效率降低。
  3. 代理模式在某些情况下可能会造成请求的处理速度变慢,特别是远程代理的情况下会涉及到网络通信的延迟。
  4. 如果代理对象的实现不当,可能会导致系统出现更多的问题,如资源泄漏、并发访问的问题等。

使用场景: 

  1. 远程代理(Remote Proxy):在客户端和远程对象之间建立代理,使得客户端能够通过代理访问远程对象,隐藏了网络通信的细节。

  2. 虚拟代理(Virtual Proxy):当创建一个对象的成本很高时,可以先使用代理对象来替代真实对象,延迟真实对象的创建。例如,图片加载时可以使用虚拟代理,在需要显示图片时再真正加载。

  3. 安全代理(Protection Proxy):控制对真实对象的访问权限。代理对象可以检查调用者是否具有执行特定操作的权限,从而保护真实对象的安全性。

  4. 缓存代理(Caching Proxy):为开销较大的计算结果提供缓存,当再次请求相同的数据时,直接返回缓存结果,避免重复计算。

  5. 日志记录代理(Logging Proxy):在调用真实对象的方法前后添加日志记录功能,用于记录方法的调用信息、参数等,方便调试和跟踪。

  6. 延迟加载代理(Lazy Loading Proxy):延迟加载即只在真正需要时才创建真实对象,代理对象会在第一次访问时进行初始化。这种方式可以提高系统启动速度和内存占用。

  7. AOP 切面编程(Aspect-Oriented Programming):代理模式常被应用于 AOP 中,通过代理将横切逻辑(如事务管理、日志记录)与业务逻辑分离。

注意事项:

  1. 接口设计要合理:在定义抽象主题(Subject)时,应该仔细考虑需要暴露给代理对象的方法,避免过于冗杂或不必要的接口方法。

  2. 选择适当的代理类型:代理模式有静态代理和动态代理两种实现方式。静态代理需要手动编写代理类,而动态代理则可以在运行时生成代理对象。根据具体需求选择合适的代理类型。

  3. 理解代理对象与真实对象的关系:代理对象作为真实对象的代表,应该能够处理与真实对象相关的事务,并在必要时将请求转发给真实对象。同时,代理对象还可以在调用前后执行额外的操作,如权限验证、性能监控等。

  4. 考虑代理对象的生命周期:代理对象的生命周期可能与真实对象不同。需要确保代理对象的创建、销毁等操作符合实际需求,避免产生过多的代理对象或无效的代理对象。

  5. 避免滥用代理模式:代理模式适用于在访问真实对象之前或之后添加额外逻辑的场景。但过度使用代理模式可能会导致代码复杂性增加,降低系统性能。

  6. 应用场景考虑:代理模式适用于很多场景,比如远程代理、安全代理、延迟加载等。在应用代理模式时,要明确自己的需求,选择合适的代理实现。

与其他模式比较

代理模式和装饰者模式的不同

        装饰器模式:强调的是增强自身,增强后你还是你,只不过能力更强了而已。

        代理模式:强调要让别人(代理类)帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)

代理模式和外观模式的不同

        代理模式:是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

        外观模式:定义了一个高层接口,为多个子系统中的接口提供一个一致的界面,不对目标功能进行增强。

 更多消息资讯,请访问昂焱数据(https://www.ayshuju.com)

你可能感兴趣的:(设计模式,代理模式)