设计模式(结构型模式)代理模式

一、简介

  代理模式:在不改变原始类的情况下,通过引入代理类给原始类附件其他功能。在实际中,我们经常听说或者看到代理模式,它主要是以下三种:

  • 基于接口实现代理模式
  • 基于继承实现代理模式
  • 基于反射实现代理模式(jdk代理和cglib代理)

二、基于接口实现代理模式

  假设有个支付请求接口,它有个支付请求的方法,如果要实现这个接口的代理,我们的代理类只需要实现这个接口注入到代理类即可。

public class RequestDTO {
	// 省略参数
}

public class ResponseVO {
	// 省略参数
}

public interface IPayService {

    public ResponseVO request(RequestDTO dto);

}

public class PayService implements IPayService{

    @Override
    public ResponseVO request(RequestDTO dto){
        // 省略请求
        System.out.println("处理支付请求");
        return new ResponseVO();
    }
}

  基于接口实现代理模式实现及测试如下:

PayServiceProxy.java

package com.alian.proxy.interfaces;

import com.alian.proxy.RequestDTO;
import com.alian.proxy.ResponseVO;

public class PayServiceProxy implements IPayService {

    private PayService payService;

    public PayServiceProxy(PayService payService) {
        this.payService = payService;
    }

    @Override
    public ResponseVO request(RequestDTO dto) {
        System.out.println("支付请求之前:做你想做的事");
        // 执行业务
        ResponseVO responseVO = payService.request(dto);
        System.out.println("支付请求之后:做你想做的事");
        return responseVO;
    }

    public static void main(String[] args) {
        PayServiceProxy payServiceProxy = new PayServiceProxy(new PayService());
        payServiceProxy.request(new RequestDTO());
    }
}

  总的来说,基于接口实现代理的优点和缺点如下:

优点

  • 松耦合性: 接口代理可以实现松耦合,客户端和被代理对象之间只依赖于接口,而不依赖具体的实现类。这样可以在不影响客户端代码的情况下,更换不同的实现类。
  • 易于扩展: 接口代理使得系统更容易扩展。你可以基于相同的接口实现多个不同的类,并通过依赖注入等方式,轻松地切换或添加实现。
  • 解耦合: 接口代理有助于实现组件之间的解耦合,增强了系统的灵活性和可维护性。这种解耦合使得各个模块更容易单独开发、测试和维护。

缺点

  • 只能代理接口方法: 接口代理只能代理接口中定义的方法,无法直接代理类中的非接口方法。这会限制代理的功能,有时需要继承或使用其他技术来解决这个问题。
  • 需定义接口: 使用接口代理需要定义接口,这可能增加开发成本,尤其是在需要维护大量接口和实现类的情况下。
  • 接口数量增加: 如果系统中有大量的接口和实现类,可能会增加代码量和复杂性,需要更多的管理和维护工作。
  • 性能开销: 在运行时动态代理的过程中,可能会产生额外的性能开销,特别是在方法调用频繁的情况下。这取决于代理模式的具体实现方式,有时会对性能产生一定的影响。

  总体来说,基于接口的代理模式可以提供较好的灵活性和扩展性,但也需要权衡其可能带来的代码维护、性能开销等方面的影响。

三、基于继承实现代理模式

  假设有个支付查询接口,它有个查询请求的方法,如果要实现这个接口的代理,我们的代理类只需要继承这个接口即可。

public class RequestDTO {
	// 省略参数
}

public class ResponseVO {
	// 省略参数
}

public class QueryService{

    public ResponseVO query(RequestDTO dto){
        // 省略请求
        System.out.println("查询请求处理");
        return new ResponseVO();
    }

}

  代理实现:

QueryServiceProxy.java

package com.alian.proxy.extend;

import com.alian.proxy.RequestDTO;
import com.alian.proxy.ResponseVO;

public class QueryServiceProxy extends QueryService {

    @Override
    public ResponseVO query(RequestDTO dto) {
        System.out.println("查询请求之前:做你想做的事");
        // 执行业务
        ResponseVO responseVO = super.query(dto);
        System.out.println("查询请求之后:做你想做的事");
        return responseVO;
    }

    public static void main(String[] args) {
        QueryServiceProxy payServiceProxy = new QueryServiceProxy();
        System.out.println("----------------------请求----------------------------");
        payServiceProxy.query(new RequestDTO());
    }
}

优点

  • 简单直观: 基于继承的代理模式相对简单,因为代理类继承自被代理类,在代理类中可以直接使用父类的方法,并在必要时重写或添加额外逻辑。
  • 性能较高: 相比于基于接口的代理和反射代理,基于继承的代理在方法调用上一般有较好的性能,因为不涉及额外的接口查找或动态方法调用。

缺点

  • 耦合度高: 基于继承的代理模式会增加类之间的耦合度,因为代理类必须继承被代理类。这可能导致继承链变得更加复杂,并限制了代理类的灵活性。
  • 无法代理final类和方法: 如果被代理类或方法被声明为 final,则无法通过继承来实现代理。
  • 单一继承限制: Java 不支持多重继承,这意味着如果被代理类已经有了一个父类,那么代理类就无法再继承其他类。
  • 静态绑定: 基于继承的代理是静态绑定的,即在编译时就确定了父类和子类之间的关系,限制了动态性和灵活性。

  综合来看,基于继承的代理模式相对简单直观,性能也较高,但由于继承的耦合性高、无法代理final类和方法以及单一继承限制等问题,可能导致代码复杂度增加和灵活性下降。在需要考虑代码扩展性和灵活性的情况下,需要慎重选择是否采用基于继承的代理。

四、基于反射实现代理模式

4.1、JDK动态代理

  当我们的类是实现了接口的形式,我们可以采用JDK动态代理。接下来我们用发短信进行举例:

public interface ISmsCodeService {
    void send();
}

public class SmsCodeServiceImpl implements ISmsCodeService {

    public void send() {
        System.out.println("SmsCodeServiceImpl: 发送短信验证码.");
    }
}

拦截器
SmsCodeInvocationHandler.java

package com.alian.proxy.jdkDynamics;

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

public class SmsCodeInvocationHandler implements InvocationHandler {

    private final Object obj;

    public SmsCodeInvocationHandler(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("发送短信验证码之前:做你想做的事");
        Object result = method.invoke(obj, args);
        System.out.println("发送短信验证码之后:做你想做的事");
        return result;
    }
}

JDK动态代理实现

SmsCodeServiceProxy.java

package com.alian.proxy.jdkDynamics;

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

public class SmsCodeServiceProxy {

    public static void main(String[] args) {
        ISmsCodeService smsCodeService = new SmsCodeServiceImpl();
        // 创建代理
        ISmsCodeService proxyObj = createProxy(smsCodeService, new SmsCodeInvocationHandler(smsCodeService));
        if (proxyObj != null) {
            // 调用方法
            proxyObj.send();
        }
    }

    private static <T> T createProxy(T obj, InvocationHandler handler) {
        // 获取类加载器
        ClassLoader classLoader = obj.getClass().getClassLoader();
        // 获取类实现的接口
        Class<?>[] interfaces = obj.getClass().getInterfaces();
        return (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}

4.2、cglib动态代理

  当我们的类只是一个普通的类,这个时候就要采用cglib动态代理了。接下来我们用发邮件进行举例:

public class EmailService {

    public void send() {
        System.out.println("发送邮件");
    }
}
package com.alian.proxy.cglibDynamics;

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

import java.lang.reflect.Method;

public class EmailInvocationHandler implements MethodInterceptor {

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("发送邮件之前:做你想做的事");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("发送邮件之后:做你想做的事");
        return result;
    }
}
package com.alian.proxy.cglibDynamics;

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

public class EmailServiceProxy {

    public static void main(String[] args) {
        // 创建代理
        EmailService proxyObj = createProxy(EmailService.class, new EmailInvocationHandler());
        if (proxyObj != null) {
            // 调用方法
            proxyObj.send();
        }
    }

    // 创建CGLIB代理的通用方法
    private static <T> T createProxy(Class<T> objClass, MethodInterceptor interceptor) {
        // 创建 Enhancer 对象,它是 CGLib 库中的类,用于创建动态代理对象
        Enhancer enhancer = new Enhancer();
        // 设置被代理类
        enhancer.setSuperclass(objClass);
        // 设置拦截器
        enhancer.setCallback(interceptor);
        // 创建代理对象并返回
        return objClass.cast(enhancer.create());
    }

}

基于反射实现代理模式的优缺点如下:

优点:

  • 灵活性和动态性: 反射允许在运行时动态地创建代理对象,并在运行时处理方法调用。这使得代理对象的创建和行为可以根据需要动态改变,增加了系统的灵活性。
  • 通用性: 反射代理可以适用于各种类型的类和对象,不需要强制实现特定的接口或继承特定的类。
  • 简化代码: 反射代理可以简化代码,因为不需要为每个类或接口创建专门的代理。通过反射,可以编写通用的代理处理器来处理不同类型的对象。

缺点:

  • 性能开销: 反射代理通常会带来一定的性能开销,因为涉及到动态的方法调用和运行时的类型检查。相比直接调用方法,反射调用通常更为缓慢。
  • 复杂性和易错性: 反射涉及到动态地检查类的结构和方法,这可能导致代码更加复杂,也更容易出现运行时错误。因为在编译时无法捕获到所有的错误,所以需要更多的测试来确保代理的正确性。
  • 限制性: 一些高级的编译器优化和静态分析工具可能无法应用于反射代码,因为反射调用的目标在编译时并不是确定的。
  • 不利于调试和维护: 反射代理的使用可能增加代码的复杂性,使得代码的调试和维护变得更加困难。  因为方法和属性的调用是在运行时决定的,不容易跟踪代码逻辑。

  总体来说,虽然反射提供了一种动态创建代理对象的强大工具,但使用时需要权衡性能开销、代码复杂性以及维护难度。在一些情况下,反射代理能提供非常便利的解决方案,但在性能敏感或对代码可维护性要求较高的情况下,需要慎重使用。

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值