继续整理记录这段时间来的收获,详细代码可在我的Gitee仓库Java设计模式克隆下载学习使用!
5.结构型模式
5.1 概述
- 根据如何将类或对象按某种布局组成更大的结构,分为类结构模式和对象结构模式,前者采用继承机制来组织接口和类,后者采用组合或聚合来组合对象
- 对象结构模式比类结构模式具有更大灵活性—因组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”
5.2 代理模式
5.2.1 概述
- 背景:由于某些原因需要给某对象提供一个代理以控制对该对象的访问
- 特点:访问对象不适合或者不直接引用目标对象,代理对象作为可以访问对象和目标对象的中介
- 根据代理类生成时机不同分为静态代理和动态代理,静态代理在编译器生成,后者在Java运行时动态生成
- 动态代理分为JDK代理和CGLIB代理
5.2.2 结构
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法
- 真实主题(Real Subject)类:实现抽象主题类的具体业务,是代理对象所代表的真实对象
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,可以访问,控制或扩展真实主题的功能
5.2.3 静态代理
火车站卖票案例
代码:
//抽象主题
public interface SellTickets {
void sellTickets();
}
//真实主题
public class TrainStation implements SellTickets{
@Override
public void sellTickets() {
System.out.println("火车站卖票");
}
}
//代理类
public class ProxyPoint implements SellTickets{
private TrainStation station = new TrainStation();
@Override
public void sellTickets() {
System.out.println("代理商收取服务费");
station.sellTickets();
}
}
5.2.4 JDK动态代理
5.2.4.1 案例
- 使用案例同上
- 抽象主题和真实主题同上
- 代码
注意:
ProxyFactory不是代理模式中的代理类
public class ProxyFactory{
// 声明目标对象
private TrainStation station = new TrainStation();
public SellTickets getProxyObjects()
{
/*ClassLoader loader, 类加载器,用于加载代理类,可以通过目标对象获取类加载器
Class<?>[] interfaces,代理类实现的接口的字节码对象
InvocationHandler h 代理对象调用的调用处理程序
* */ SellTickets proxyInstance = (SellTickets) Proxy.newProxyInstance(
station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*Object 代理对象,和proxyInstance是同一个对象,在invoke中基本不用
Method 代理对象调用方法,对接口中方法进行封装的对象,这里是sellTickets
Object[] 调用方法参数
* */ @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理商收取服务费(JDK动态代理)");
// 执行目标对象的方法
Object invoke = method.invoke(station, args);
return invoke;
}
}
);
return proxyInstance;
}
}
//测试
public static void main(String[] args) {
// 获取代理对象
// 创建代理工厂对象
ProxyFactory proxyFactory = new ProxyFactory();
// 使用代理工厂创建代理对象
SellTickets proxyObjects = proxyFactory.getProxyObjects();
// 调用买票方法
proxyObjects.sellTickets();
}
5.2.4.2 案例中的代理类
- 使用arthas-boot.jar来查看具体的代理类等信息
- 解压后使用CMD打开
- 在测试类加代码使程序一直运行,如图
- 命令
java -jar arthas-boot.jar
,如图 - 继续输入2,如图
- 命令
com.sun.proxy.$Proxy0
,如图 - 代理类$Proxy0重点代码
public final class $Proxy0 extends Proxy implements SellTickets { private static Method m3; public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); } static { m3 = Class.forName("com.demo.patternDesign.ProxyModel.DynamicJDKProxy.SellTickets").getMethod("sellTickets", new Class[0]); }
// 代理类调用真实对象方法
public final void sellTickets() {
this.h.invoke(this, m3, null);
}
}
```
- 执行流程
- 测试类通过代理对象调用sell方法
- 根据多态,执行代理类$Proxy0的sell方法
- 代理类$Proxy0的sell方法又调用InvocationHandle接口中的自实现类对象的invoke方法(测试类加收取服务费那方法)
- invoke方法通过反射执行真实对象类TrainStation中的sell方法
5.2.5 CGLIB动态代理
和之前一样的案例
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
- CGLIB包介绍:高性能代码生成包,可为没有实现接口类提供代理
- 代码
// 代理工厂
public class ProxyFactory implements MethodInterceptor {
// 创建真实对象
private TrainStation trainStation = new TrainStation();
public TrainStation getProxyObject(){
// 创建Enhance对象,类似于JDK代理中的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
enhancer.setSuperclass(TrainStation.class);
设置回调函数
enhancer.setCallback(this);
// 创建代理对象
TrainStation proxyObject = (TrainStation) enhancer.create();
return proxyObject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理商收取服务费(cglib代理)");
Object invoke = method.invoke(trainStation,objects);
return invoke;
}
}
// 测试代码
public static void main(String[] args) {
// 创建代理工厂对象
ProxyFactory proxyFactory = new ProxyFactory();
// 获取代理对象
TrainStation proxyObject = proxyFactory.getProxyObject();
proxyObject.sellTickets();
}
5.2.6 3 种方式对比
5.2.6.1 JDK和CGLIB
- CGLIB代理
- 底层采用ASM字节码生成框架,使用字节码技术生成代理类
- 不能对声明为final的类或方法进行代理,因CGLIB原理是动态生成被代理的子类
- JDK1.6之前比JAVA使用反射效率要高
- 两者效率问题
- JDK1.6,1.7,1.8后在调用次数较少情况下JDK代理效率高于CGLIB代理效率
- 仅当进行大量调用且在JDK1.6,1.7中CGLIB代理效率高
- 选择:有接口使用JDK动态代理,没有接口使用CGLIB代理
5.2.6.2 动态和静态
- 动态优点:声明的所有方法都被转移到调用处理器一个集中方法处理
- 静态缺点:若接口增加方法,静态代理除了所有实现类需要实现此方法外,所有处理类也需要实现此方法,增加代码冗余与复杂性
5.2.7 优缺点
5.2.7.1 优点
- 在客户端与目标对象之间起一个中介作用和保护目标对象作用
- 可以扩展目标对象的功能
- 能将客户端与目标对象分离,一定程度上降低系统耦合度
5.2.7.2 缺点
增加系统复杂度
5.2.8 使用场景
5.2.8.1 远程(Remote)代理
本地服务通过网络请求远程服务:可将网络通信部分隐藏,只保留本地服务一个接口,使其通过此接口访问远程服务提供功能,而不必过多关心通信细节。
5.2.8.2 防火墙(Firewall)代理
将浏览器配置成代理模式,防火墙将请求转发给互联网,当互联网响应时,代理服务器将信息再转发给浏览器
5.2.8.3 保护(Protect or Access)代理
控制一个对象访问,若需要,可以给不同对象以不同访问权限