设计模式的分类
我们都知道有 23 种设计模式,这 23 种设计模式可分为如下三类:
- 创建型模式(5 种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
- 结构型模式(7 种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式(11 种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式系列文章传送门
什么是代理模式
代理模式属于结构型设计模式,其核心思想是为其他对象提供一种代理来完成对这个对象的访问,代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的分类
代理模式可以分为三类,如下:
- 静态代理:需要程序开发者显示的创建代理类代码,其代理类和被代理的对象在编译期间就确定下来。
- 动态代理:代理类在程序运行时通动态生成,无需程序开发者显式地创建代理类。
- 虚拟代理 :虚拟代理作为创建开销大的对象的代理,会直到我们真正需要使用一个对象的时候才创建它,当对象在创建前和创建中时,由虚拟代理地来扮演对象的,对象创建后,代理就会将请求直接委托给对象。
代理模式的优缺点
优点:
- 代理模式可以隐藏真是对象的实现细节,使客户端无需知晓真实对象的工作方式和结构,简化了客户端。
- 通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行进行功能扩展,可以在访问对象之前或之后执行一些额外的操作。
- 代理模式可以对真实对象进行保护,只允许特定的客户端访问真实对象,避免了真实对象的暴露,从一定程度上体现了安全性。
- 代理模式可以延迟加载真实对象,当需要真实对象时才进行创建和初始。
缺点:
- 增加了系统的复杂性,可以说是设计模式的通用缺点,具体就看是否是利大于弊了。
- 在一定程度上增加了系统的性能消耗,因为代理模式在访问对象时引入了额外的间接层,例如需要使用到反射的技术栈,这可能会对系统的响应时间产生影响。
代理模式的组成
- 抽象主题(Subject)角色:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 具体主题(Real Subject)角色:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,也是最终要引用的对象,也叫被代理对象。
- 代理(Proxy)角色:提供了与真实主题相同的接口,其内部含有对具体主题的引用,它可以访问扩展具体主题的功能。
静态代理
我们以生活中的租房为案例来演示静态代理,假设我是房东我有房子需要出租,但是我没有时间去打理出租的这个过程,因为我委托访房产中介来代理我出租房子,这里面我要出租房子就是具体主题,而中介就是代理角色。
House (抽象主题)
定义了一个需要出租房屋的方法,也就是抽象主题,代码如下:
public interface House {
//出租房子
void rentHouse();
}
MyHouse(具体主题)
我有房子要出租,是具体主题,其实现了抽象主题,代码如下:
public class MyHouse implements House{
@Override
public void rentHouse() {
System.out.println("租房的客户来了");
}
}
HouseRentProxy(静态代理)
我要出租房子,而我又没有时间开打理,因此将此业务委托给中介代理,也就是代理对象,代码如下:
public class HouseRentProxy implements House {
private MyHouse myHouse;
public HouseRentProxy(MyHouse myHouse) {
this.myHouse = myHouse;
}
@Override
public void rentHouse() {
System.out.println("委托代理中介来帮我组房子");
myHouse.rentHouse();
}
}
可以看到,静态代理对象内部含有对真实主题的引用
ClientHouse(客户端)
假设有一个客户要来租房,我通过代理对象完成房屋的出租,代码如下:
public class ClientHouse {
public static void main(String[] args) {
//具体对象
MyHouse myHouse = new MyHouse();
//代理对象
HouseRentProxy houseRentProxy = new HouseRentProxy(myHouse);
//同多代理对象来租房
houseRentProxy.rentHouse();
}
}
执行结果如下:
委托代理中介来帮我组房子
租房的客户来了
可以看到我们通过代理对象完成了房屋的出租。
静态代理使用场景
- 当代理对象较少时候可以使用静态代,因为静态代理需要进行编码开发,如果被代理对象较多,会导致代理类很多。
- 有相同的功能需求的时候可以使用静态代理,这一条是对上一条的补充,只有不用的功能需求才需要不同的代理类,因此静态代理适用用于有相同功能需求的场景。
动态代理
在 Java 中动态代理的实现又分为 JDK 动态代理和 CGLib 动态代理,我们接下来分开分析。
JDK 动态代理
JDK 动态代理是基于接口的代理方式,它是在程序运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成,是 Java 标准库中提供的一种代理方法,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理,JDK 动态代理通过反射机制实现代理功能。
JDK 动态代理的代理类实现了 InvocationHandler 接口,其目的是对具体主题的方法的增强,InvocationHandler 接口中只有一个 invoke 方法,动态代理对象中的映射方法在执行时都是调用的 InvocationHandler 接口中的 invoke 方法,在调用 invoke 方法时,动态代理对象会将 被代理对象的方法和动态代理对象映射的方法的参数传递给 InvocationHandler 的 invoke 方法, invoke 方法的实现是由开发人员编写的,这样开发人员就可以在被代理对象的方法执行前后进行功能增强。
House (抽象主题)
定义了一个需要出租房屋的方法,也就是抽象主题,代码如下:
public interface House {
//出租房子
void rentHouse();
}
MyHouse(具体主题)
我有房子要出租,是具体主题,其实现了抽象主题,代码如下:
public class MyHouse implements House{
@Override
public void rentHouse() {
System.out.println("租房的客户来了");
}
}
MyHouseJdkProxy(动态代理)
JDK 动态代理类,代码如下:
public class MyHouseJdkProxy implements InvocationHandler {
private Object target;
public MyHouseJdkProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理中介帮我找到了租户");
Object invoke = method.invoke(target, args);
System.out.println("代理中介帮我完成了租赁签约");
return invoke;
}
}
ClientHouse(客户端)
客户端使用 JDK 动态代理类完成房屋出租功能,代码如下:
public class ClientHouse {
public static void main(String[] args) {
//被代理类对象
MyHouse myHouse = new MyHouse();
//被代理对象类加载器
ClassLoader classLoader = myHouse.getClass().getClassLoader();
//被代理对象的方法
Class<?>[] interfaces = myHouse.getClass().getInterfaces();
//获取代理对象
MyHouseJdkProxy myHouseJdkProxy = new MyHouseJdkProxy(myHouse);
//生成代理对象
House myHouseProxy = (House) Proxy.newProxyInstance(classLoader, interfaces, myHouseJdkProxy);
myHouseProxy.rentHouse();
}
}
动态代理执行结果如下:
代理中介帮我找到了租户
租房的客户来了
代理中介帮我完成了租赁签约
可以看到我们使用代理类完成了房屋的出租,并且完成了房屋出租前后的增加实现。
CGLIB 动态代理
CGLIB 代理是在运行时动态生成代理类的方式,它使用的是 cglib 库,和 JDK 动态代理相比,它不是动态的生成一个实现了接口的代理类,
而是在运行时动态生成目标类的子类作为代理类,并重写其中的方法来实现代理功能,与 JDK 动态代理不同,CGLIB动态代理没有实现接口,CGLIB 代理是基于类的代理。
CGLIB 代理实现步骤:
- 首先我们需要一个具体主题,也就是被代理类,就是一个业务普通类,区别于 JDK 动态代理,该类不需要实现接口。
- 然后我需要编写一个增强类,或者说代理类,增强的方法基于该类实现,该类需要实现 CGLIB 的 MethodInterceptor 接口,并重写其 intercept 方法,在该方法中实现增强逻辑。
- 在客户端代码中我们要基于 Enhancer 生成目标对象的代理对象。
这里我们同样使用出租房屋这个场景来演示 CGLIB 代理。
MyHouse(具体主题)
我们根据出租房屋抽象出具体主题 MyHouse,代码如下:
public class MyHouse {
public void rentHouse() {
System.out.println("租房的客户来了");
}
}
可以看到这里区别于 JDK 动态代理,这里没有实现接口,就只是定义了一个普通的类。
MyHouseCglibProxy(动态代理)
CGLIB 动态代理增强类,代码如下:
public class MyHouseCglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理中介帮我找到了租户");
Object invokeSuper = methodProxy.invokeSuper(object, objects);
System.out.println("代理中介帮我完成了租赁签约");
return invokeSuper;
}
}
ClientHouse(客户端)
客户端使用 CGLIB 动态代理类完成房屋出租功能,代码如下:
public class ClientHouse {
public static void main(String[] args) {
//cglig 代理的增强类
Enhancer enhancer=new Enhancer();
//设置被代理类 cgliba 根据该类去生成子类
enhancer.setSuperclass(MyHouse.class);
//设置代理类 也就是增强方法
enhancer.setCallback(new MyHouseCglibProxy());
//生成代理对象
MyHouse myHouseProxy = (MyHouse)enhancer.create();
myHouseProxy.rentHouse();
}
}
执行结果如下:
代理中介帮我找到了租户
租房的客户来了
代理中介帮我完成了租赁签约
可以看到我们使用 CGLIB 代理实现了功能增强
动态代理适用场景
- 日志记录,如果我们需要对某个方法进行日志记录,使用动态代理在方法调用前后自动记录日志,非常的方便。
- 方法功能增强,动态代理可以在不修改原有对象的基础上,为对象添加额外的功能,比如事务管理、异常处理等,适合对方法进行功能增强的场景。
- 敏感对象的保护,对于一些不想暴露的对象,我们使用动态代理的方式来访问,在一定程度上起到了对象的保护作用。
总结:本篇我们简单分享了结构型设计模式–代理模式,并举例分析了静态代理和动态代理的具体实现,希望可以帮助到有需要的朋友。
如有不正确的地方欢迎各位指出纠正。