目录
一、什么是代理模式
二、特点
三、应用场景
四、代理模式的实现
4.0 代码结构
4.1 静态代理
4.2 JDK动态代理
4.3 Cglib代理
五、总结
一、什么是代理模式
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
二、特点
优点:
- 代理模式可以隐藏真是对象的实现细节,使客户端无需知晓真实对象的工作方式和结构。
- 通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行扩展、优化或添加安全措施。
- 代理模式实现起来简单,易于扩展和维护,符合面向对象设计原则中的开闭原则。
缺点:
- 代理模式可能会引入额外的复杂性和间接性,增加程序设计和维护的难度。
- 对象代理可能会降低系统性能,特别是在处理大数据量或频繁调用的情况下,因为代理需要额外的计算和网络通信开销。
三、应用场景
- 远程代理: 在客户端与远程对象之间建立一个代理对象,代理对象通过网络与远程对象进行通信,隐藏了底层的网络细节,使得客户端可以像调用本地对象一样访问远程对象。
- 虚拟代理: 用于控制访问一些资源开销较大的对象,只有在需要的时候才真正去创建或加载,从而节省系统开销。
- 安全代理: 用于控制资源的访问权限,保护真实对象的信息不被非法访问和篡改。在访问真实对象之前,代理可以先进行身份验证、鉴权等操作。
- 缓存代理: 用于提高系统性能,将频繁访问的对象或方法的结果缓存起来,可以减少真实对象的调用次数。(比如缓存是代理、真实对象就是mysql)
- 日志记录代理: 用于记录对象的方法调用日志,方便后续的调试和问题排查。
- 延迟加载代理: 用于减少系统启动时间和资源占用,将部分对象或方法的加载时间推迟到真正需要的时候再进行初始化。
- 买火车票不一定到火车站买,也可以去代售点。
- 银行卡和支票都是真实钱的代理对象。
四、代理模式的实现
4.0 代码结构
下面就以房东和租客为例,分别介绍一下静态代理、jdk动态代理和cglib代理。
4.1 静态代理
静态代理是一种在代码编写期进行代理类和被代理类的关联的代理方式。
具体实现是创建一个代理类,通常需要实现与被代理类相同的接口或继承被代理类。
房东接口类:Landlord1Service,注意,静态代理实现它的真实对象只能有一个,多个的话,代理对象不能确定哪个对象需要被代理,会导致报错,JDK动态代理没这个问题。
/*** 房东* @author Created by njy on 2023/5/30*/
public interface Landlord1Service {/*** 出租* @param money 金额* @return*/void rent(Integer money);
}
租客:TenantImpl
import org.springframework.stereotype.Component;
/*** 租客* @author Created by njy on 2023/5/30*/
@Component
public class TenantImpl implements Landlord1Service {@Overridepublic void rent(Integer money) {System.out.println("租下"+money+"元一个月的房子");}
}
静态代理:ProxyImpl
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/*** 中介* @author Created by njy on 2023/5/30*/
@Component
public class ProxyImpl implements Landlord1Service {/*** 房东有很多套房子,不想亲自出马了,于是找来了中介*/@Autowiredprivate Landlord1Service target;/*** 优点就是在不改变原来的实现类的情况下对方法实现了增强* 缺点是如果原来的接口新增了方法,那么这里也要对应实现新的方法* @param money 金额* @return*/@Overridepublic void rent(Integer money) {System.out.println("[静态代理]交中介费");target.rent(money);System.out.println("[静态代理]中介负责维修管理");}
}
测试:
/*** @author Created by njy on 2023/5/30*/
@SpringBootTest
public class TestProxy {@Autowiredprivate TenantImpl tenant;@Autowiredprivate ProxyImpl proxy;//1.静态代理@Testvoid TestStatic(){tenant.rent(1000);System.out.println();proxy.rent(2000);}
}
适用场景:
- 当代理对象只有一个时,可以使用静态代理
- 当被代理的类的接口比较稳定时,可以使用静态代理
- 当需要为多个被代理的类提供代理时,会导致代理类过多,不方便管理和维护,所以不建议使用静态代理。
4.2 JDK动态代理
JDK动态代理是一种比较常见的代理方式,它是在程序运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。
房东接口类:Landlord2Service
/*** @author Created by njy on 2023/5/30*/
public interface Landlord2Service {/*** 出租* @param money* @return*/void rent(Integer money);
}
租客1:Teant1Impl
import org.springframework.stereotype.Component;/*** @author Created by njy on 2023/5/30*/
@Component
public class Teant1Impl implements Landlord2Service{@Overridepublic void rent(Integer money) {System.out.println("tenant1租下"+money+"元一个月的房子");}
}
租客2:Teant2Impl
import org.springframework.stereotype.Component;/*** @author Created by njy on 2023/5/30*/
@Component
public class Tenant2Impl implements Landlord2Service {@Overridepublic void rent(Integer money) {System.out.println("tenant2租下"+money+"元一个月的房子");}
}
JDK动态代理:JDKProxy
import org.springframework.stereotype.Component;
import java.lang.reflect.Proxy;
/*** JDK动态代理:就是把代理抽象了一下* @author Created by njy on 2023/5/30*/
public class JDKProxy {private Object target;public JDKProxy(Object target){this.target=target;}/*** 给目标对象生成代理对象* @return 代理生成的对象*/public Object getProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),//这里是要实现jdk代理InvocationHandler的接口,lambda表达式(proxy,method,args)->{//执行对象方法System.out.println("[JDK动态代理]交中介费");method.invoke(target,args);System.out.println("[JDK动态代理]中介负责维修管理");return null;});}
}
Test:
/*** @author Created by njy on 2023/5/30*/
@SpringBootTest
public class TestProxy {@Autowiredprivate Teant1Impl teant1;@Autowiredprivate Tenant2Impl tenant2;//2.JDK动态代理@Testvoid TestJDK(){Landlord2Service proxyInstance1 = (Landlord2Service) new JDKProxy(teant1).getProxyInstance();proxyInstance1.rent(2500);System.out.println();Landlord2Service proxyInstance2 = (Landlord2Service) new JDKProxy(tenant2).getProxyInstance();proxyInstance2.rent(2500);}
}
适用场景:
- 对象必须实现一个或多个接口
- 代理类的代理方法不需要额外的逻辑
4.3 Cglib代理
CGLIB代理是在运行时动态生成代理类的方式,它使用的库是cglib,和JDK代理相比,它不是动态的生成一个实现了接口的代理类,而是直接在内存中构建一个被代理类的子类,并重写父类的方法来进行代理。
房东类:Landlord3Service
import org.springframework.stereotype.Component;
/*** @author Created by njy on 2023/5/30*/
@Component
public class Landlord3Service {/*** 出租房屋* @param money* @return*/public void rent(Integer money){System.out.println("租下"+money+"元一个月的房子");}
}
Cglib代理类:CglibProxy
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*** JDKProxy:cglib子类代理工厂* 1.代理的类不能为final* 2.目标对象的方法如果为final/static,那么就不会被拦截,也不会执行目标对象的业务方法* @author Created by njy on 2023/5/30*/
public class CglibProxy implements MethodInterceptor {/*** 目标对象*/private final Object target;public CglibProxy(Object target){this.target=target;}public Object getProxyInstance(){//1.工具类Enhancer en=new Enhancer();//2.设置父类en.setSuperclass(target.getClass());//3.设置回调函数en.setCallback(this);//4.创建子类(代理对象)return en.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("[Cglib代理]交中介费");method.invoke(target,objects);System.out.println("[Cglib代理]中介负责维修管理");return null;}
}
测试:
/*** @author Created by njy on 2023/5/30*/
@SpringBootTest
@RequiredArgsConstructor
public class TestProxy {@Autowiredprivate Landlord3Service landlord3Service;//3.Cglib代理@Testvoid TestCglib(){Landlord3Service proxyInstance = (Landlord3Service) new CglibProxy(landlord3Service).getProxyInstance();proxyInstance.rent(3000);}
}
适用场景:
- 被代理的类没有实现接口或者无法实现接口
- 代理类的代理方法需要进行额外的逻辑,如事务处理等。
五、总结
- 对于没有实现接口的类,只能使用CGLIB代理
- 对于实现了接口的类,可以使用JDK代理或者CGLIB代理,如果要求比较高的话,建议使用JDK代理。
- 对于单个代理类的情况,并且被代理类实现了接口,可以使用静态代理。
- 对于多个被代理类的情况,建议使用JDK代理或CGLIB代理。