一、简介
代理模式:在不改变原始类的情况下,通过引入代理类给原始类附件其他功能。在实际中,我们经常听说或者看到代理模式,它主要是以下三种:
- 基于接口实现代理模式
- 基于继承实现代理模式
- 基于反射实现代理模式(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());
}
}
基于反射实现代理模式的优缺点如下:
优点:
- 灵活性和动态性: 反射允许在运行时动态地创建代理对象,并在运行时处理方法调用。这使得代理对象的创建和行为可以根据需要动态改变,增加了系统的灵活性。
- 通用性: 反射代理可以适用于各种类型的类和对象,不需要强制实现特定的接口或继承特定的类。
- 简化代码: 反射代理可以简化代码,因为不需要为每个类或接口创建专门的代理。通过反射,可以编写通用的代理处理器来处理不同类型的对象。
缺点:
- 性能开销: 反射代理通常会带来一定的性能开销,因为涉及到动态的方法调用和运行时的类型检查。相比直接调用方法,反射调用通常更为缓慢。
- 复杂性和易错性: 反射涉及到动态地检查类的结构和方法,这可能导致代码更加复杂,也更容易出现运行时错误。因为在编译时无法捕获到所有的错误,所以需要更多的测试来确保代理的正确性。
- 限制性: 一些高级的编译器优化和静态分析工具可能无法应用于反射代码,因为反射调用的目标在编译时并不是确定的。
- 不利于调试和维护: 反射代理的使用可能增加代码的复杂性,使得代码的调试和维护变得更加困难。 因为方法和属性的调用是在运行时决定的,不容易跟踪代码逻辑。
总体来说,虽然反射提供了一种动态创建代理对象的强大工具,但使用时需要权衡性能开销、代码复杂性以及维护难度。在一些情况下,反射代理能提供非常便利的解决方案,但在性能敏感或对代码可维护性要求较高的情况下,需要慎重使用。