一、学习目标
代理模式是一种常用的设计模式,它主要用于在不修改原有对象代码的情况下,通过引入一个代理对象来控制对原有对象的访问,从而增强原有对象的功能。代理模式主要分为两种:静态代理和动态代理。尽管Spring框架本身更多地利用了动态代理来实现其功能,但理解静态代理是理解Spring代理机制的基础
代理模式(Proxy Pattern)是程序设计中的一种重要设计模式,属于结构型设计模式。它主要通过为其他对象提供一个代理以控制对这个对象的访问,从而在客户端和目标对象之间起到中介的作用。
代理模式主要包含以下三个角色:
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
代理模式主要分为以下几种类型:
- 静态代理:在代码编译阶段就已经生成了代理类,代理类需要实现与目标对象相同的接口。静态代理在不修改目标对象的前提下对目标对象的方法进行增强,但需要为每个目标对象创建一个代理类,导致系统中类的数量增加,维护成本较高。
- 动态代理:在程序运行时,通过反射机制动态生成的代理类。Java中可以使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现动态代理。动态代理可以为多个目标对象创建一个代理类,减少了类的数量,并且更加灵活地处理不同的情况。但由于使用了反射机制,性能相对静态代理略低。 - CGLIB代理:CGLIB代理是通过继承目标对象的方式来创建代理类。它是第三方库(如Spring AOP)常用的一种代理方式,适用于没有实现接口的类。CGLIB代理不需要目标对象实现接口,可以代理任何类,但由于采用了继承的方式,目标类不能是
final
类,代理类也不能是final
方法。相对于JDK动态代理,CGLIB代理的性能较高,但由于使用了字节码生成技术,可能会增加复杂性。
优点
- 职责清晰:真实角色只关注实际的业务逻辑,而代理对象负责非业务逻辑的操作,使得编程更加简洁清晰。
- 降低耦合度:代理模式在一定程度上降低了系统的耦合度,提高了系统的扩展性。
- 保护作用:代理对象在客户端和目标对象之间起到中介的作用,可以保护目标对象不被直接访问。
- 增强功能:代理模式可以在不改变原始对象的情况下,为其增加额外的功能,如日志记录、权限校验等。
二、静态代理
1.定义一个接口,目标类和代理类都将实现这个接口
public interface Travel {
//实现旅游功能
public void travel();
}
2.目标类实现上述接口,定义业务逻辑
public class Spot implements Travel{
//spot为旅游目的地,实现旅游功能
public void travel() {
System.out.println("接待游客");
}
}
3代理类实现上述接口,并持有目标类的一个实例
public class Proxy implements Travel{
private Spot spot;
public Proxy(Spot spot) {
this.spot = spot;
}
public void travel() {
plan();
ticket();
spot.travel();
}
public void plan(){
System.out.println("做攻略");
}
public void ticket(){
System.out.println("订票");
}
}
4.客户通过代理类来调用目标类
public class Client {
public static void main(String[] args) {
Spot spot=new Spot();
Proxy proxy=new Proxy(spot);
proxy.travel();
}
}
5.优缺点
优点:
1.实现简单,易于理解。
2.可以在不修改目标对象的前提下扩展目标对象的功能。
缺点:
1.如果目标对象很多,需要为每一个目标对象创建一个代理类,这会导致类爆炸,增加系统的复杂度和维护成本。
2.如果接口增加方法,则目标对象和代理类都需要修改,这违反了开闭原则
三、动态代理
1.定义一个接口。
public interface Travel {
//实现旅游功能
public void travel();
}
2.创建一个实现了该接口的目标类。
public class Spot implements Travel{
public void travel() {
System.out.println("接待游客");
}
}
3.创建一个实现了InvocationHandler
接口的代理类,在invoke
方法中编写增强逻辑。
// 定义一个实现了InvocationHandler接口的类,用于创建动态代理对象
public class ProxyInvocationHandler implements InvocationHandler {
// 持有被代理对象的引用
private Travel travel;
// 设置被代理对象的方法
public void setTravel(Travel travel) {
this.travel = travel;
}
// 获取动态代理对象的方法
// 这里需要注意,获取代理对象的类加载器应该是被代理对象的类加载器,而不是ProxyInvocationHandler的类加载器
public Object getProxy(){
// 使用Proxy类的newProxyInstance方法创建代理对象
// 第一个参数:类加载器,加载代理类的ClassLoader
// 第二个参数:被代理对象实现的接口数组,代理类需要实现这些接口
// 第三个参数:实现了InvocationHandler接口的处理器对象
return Proxy.newProxyInstance(
this.travel.getClass().getClassLoader(), // 使用被代理对象的类加载器
travel.getClass().getInterfaces(), // 获取被代理对象实现的接口数组
this // 将当前对象作为InvocationHandler
);
}
// 实现InvocationHandler接口的invoke方法
// 该方法会在代理对象调用方法时被自动调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用被代理对象的方法之前,执行一些前置操作,如做攻略和订票
plan();
ticket();
// 调用被代理对象的方法,使用反射机制
// method.invoke(被代理对象, 参数数组)
Object result = method.invoke(travel, args);
// 返回被代理对象方法的执行结果
return result;
}
// 前置操作:做攻略
public void plan(){
System.out.println("做攻略");
}
// 前置操作:订票
public void ticket(){
System.out.println("订票");
}
}
4.使用Proxy.newProxyInstance
方法创建代理对象。
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
travel.getClass().getInterfaces(), this);
}
5.通过代理对象调用目标方法,自动执行增强逻辑。
public class Client {
public static void main(String[] args) {
Spot spot=new Spot();
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTravel(spot);
Travel proxy=(Travel)pih.getProxy() ;
proxy.travel();
}
}
6.优缺点
优点
解耦:将切面逻辑与业务逻辑分离,降低了系统的耦合度。
灵活:可以在不修改目标对象代码的情况下,为目标对象添加额外的功能或逻辑。
易于维护:切面逻辑可以集中管理,便于维护和升级。
缺点
性能开销:动态代理的创建和调用过程中存在反射或字节码生成等开销,可能会影响系统性能。
限制:JDK动态代理只能代理实现了接口的类;CGLIB动态代理不能代理final修饰的类和方法。