代理模式
前言:
我们一般在租房子时会去找中介,为什么呢?因为你对该地区房屋的信息掌握的不够全面,希望找一个更熟悉的人去帮你做;再比如我们打官司需要请律师,因为律师在法律方面有专长,可以替我们进行操作,表达我们的想法;再比如在淘宝上面买东西,你使用支付宝平台支付,卖家请物流公司发货,在这个过程汇总支付宝、物流公司都扮演者“第三者”的角色在帮你完成物品的购买,这里的第三者我们可以将其称之为代理者,在我们实际生活中这种代理情况无处不在!
一.什么是代理模式
代理模式的设计动机是通过代理对象来访问真实对象,通过建立一个对象代理类,由代理对象控制原对象的引用,从而实现对真实对象的操作。在代理模式种,代理主要起到了一个中介的作用,用于协调与连接调用者(即客户端)和被调用者(即目标对象),在一定程度上降低了系统的耦合度,同时也保护了目标对象。但缺点是在调用者与被调用者增加了地阿里对象,可能会造成请求的处理速度变慢。
二.分类
代理模式分为三类:1.静态代理;2.动态代理;3.CGLIB代理
三.UML结构图
- Subject:抽象角色,声明了真实对象和代理对象的共同接口;
- Proxy:代理角色,实现了与真实对象相同的接口,所以在任何时刻都能够代理真实对象,并且代理对象内部包含了真实对象的引用,所以它可以操作真实对象,同时也可附加其他的操作,相当于对真实对象进行封装。
四.特点
优点:
- 代理模式可以隐藏真实对象的实现细节,使客户端无需知道真实对象的工作方式和结构。
- 通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行扩展,优化或添加安全措施。
- 代理模式实现起来简单,易于扩展和维护,符合面向对象设计原则中的开闭原则。
缺点:
- 代理模式可能会引入额外的复杂性和间接性,增加程序设计和维护的难度。
- 对象代理可能会降低系统性能,特别是在处理大数据或频繁调用的情况下,因为代理需要额外的计算和网络通信开销。
五.应用场景
- AOP:通过定义切面,切入点和通知等,Spring AOP在运行时生成代理对象,将切面逻辑织入到目标对象的方法调用中。代理对象在方法调用前后执行附加操作,如日志记录,性能监控等。
- 动态代理(JDK动态代理,CGLIB代理等):当Bean类实现了接口时,Spring使用JDK动态代理来为Bean生成代理对象;当Bean类没有实现接口时,Spring使用CGLIB代理来生成代理对象。
六.代码实现
1.静态代理:
静态代理是一种在代码编写期进行代理类和被代理类的关联的代理方式。具体实现是创建一个代理类,通常需要实现与被代理类相同的接口或继承被代理类。
房东接口类:Landlord1Service,
注意:静态代理实现它的真实对象只能有一个,多个的话,代理对象不能确定哪个对象需要被代理,会导致报错,JDK动态代理没这个问题
/**
* 房东
* @author Created by njy on 2023/5/30
*/
public interface Landlord1Service {
/**
* 出租
* @param money 金额
* @return
*/
void rent(Integer money);
}
租客:TenantImpl
/**
* 租客
* @author Created by njy on 2023/5/30
*/
@Component
public class TenantImpl implements Landlord1Service {
@Override
public void rent(Integer money) {
System.out.println("租下"+money+"元一个月的房子");
}
}
静态代理:ProxyImpl
/**
* 中介
* @author Created by njy on 2023/5/30
*/
@Component
public class ProxyImpl implements Landlord1Service {
/**
* 房东有很多套房子,不想亲自出马了,于是找来了中介
*/
@Autowired
private Landlord1Service target;
/**
* 优点就是在不改变原来的实现类的情况下对方法实现了增强
* 缺点是如果原来的接口新增了方法,那么这里也要对应实现新的方法
* @param money 金额
* @return
*/
@Override
public 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 {
@Autowired
private TenantImpl tenant;
@Autowired
private ProxyImpl proxy;
//1.静态代理
@Test
void TestStatic(){
tenant.rent(1000);
System.out.println();
proxy.rent(2000);
}
}
使用场景:
- 当代理对象只有一个时,可以使用静态代理。
- 当被代理的类的接口比较稳定时,可以使用静态代理。
- 当需要为多个被代理的类提供代理时,会导致代理类过多,不方便管理和维护,所以不建议使用静态代理。
2.JDK动态代理
JDK动态代理是一种比较常见的代理方式,他是在程序运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。
房东接口类:Landlord2Service
/**
* @author Created by njy on 2023/5/30
*/
public interface Landlord2Service {
/**
* 出租
* @param money
* @return
*/
void rent(Integer money);
}
租客1:Teant1Impl
/**
* @author Created by njy on 2023/5/30
*/
@Component
public class Teant1Impl implements Landlord2Service{
@Override
public void rent(Integer money) {
System.out.println("tenant1租下"+money+"元一个月的房子");
}
}
租客2:Teant2Impl
/**
* @author Created by njy on 2023/5/30
*/
@Component
public class Tenant2Impl implements Landlord2Service {
@Override
public void rent(Integer money) {
System.out.println("tenant2租下"+money+"元一个月的房子");
}
}
JDK动态代理:JDKProxy
/**
* 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 {
@Autowired
private Teant1Impl teant1;
@Autowired
private Tenant2Impl tenant2;
//2.JDK动态代理
@Test
void TestJDK(){
Landlord2Service proxyInstance1 = (Landlord2Service) new JDKProxy(teant1).getProxyInstance();
proxyInstance1.rent(2500);
System.out.println();
Landlord2Service proxyInstance2 = (Landlord2Service) new JDKProxy(tenant2).getProxyInstance();
proxyInstance2.rent(2500);
}
}
使用场景:
- 对象必须实现一个或多个接口。
- 代理类的代理方法不需要额外的逻辑。
3.Cglib代理
CDLIB代理是在运行时动态生成代理类的方法,它使用的库是cglib,和JDK代理相比,它不是动态的生成一个实现了接口的代理类,而是直接在内存中构建一个被代理的子类,并重写父类的方法来进行代理。
房东类:Landlord3Service
/**
* @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
/**
* 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();
}
@Override
public 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 {
@Autowired
private Landlord3Service landlord3Service;
//3.Cglib代理
@Test
void TestCglib(){
Landlord3Service proxyInstance = (Landlord3Service) new CglibProxy(landlord3Service).getProxyInstance();
proxyInstance.rent(3000);
}
}
使用场景:
- 被代理的类没有实现接口或者无法实现接口。
- 代理类的代理方法需要进行额外的逻辑,如事务处理等。
七.总结
- 对于没有实现接口的类,只能使用CGLIB代理。
- 对于实现了接口的类,可以使用JDK代理或者CGLIB代理,如果要求比较高的话,建议使用JDK代理。
- 对于单个代理类的情况,并且被代理类实现了接口,可以使用静态代理。
- 对于多个被代理类的情况,建议使用JDK代理或者CGLIB代理。