为什么用结构性模式?
结构性模式
关注点“怎样组合对象/类
?”所以我们关注下类的组合关系类结构型模式
关心类的组合,由多个类可以组合成一个更大的(继承)对象结构型模式
关心类与对象的组合,通过关联关系
在一个类中定义另一个类的实例对象(组合)- 根据“
合成复用原则
”,在系统中尽量使用关联关系来替代继承关系
,因此大部分结构型模式都是对象结构型模式
。
- 适配器模式(Adapter Pattern):两个不兼容接口之间适配的桥梁。
- 桥接模式(Bridge Pattern):相同功能抽象化与实现化解耦,抽象与实现可以独立升级。
- 过滤器模式(Filter、Criteria Pattern):使用不同的标准来过滤一组对象。
- 组合模式(Composite Pattern):相似对象进行组合,形成树形结构。
- 装饰器模式(Decorator Pattern):向一个现有的对象添加新的功能,同时又不改变其结构。
- 外观模式(Facade Pattern):向现有的系统添加一个接口,客户端访问此接口来隐藏系统的复杂性。
- 享元模式(Flyweight Pattern):尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
- 代理模式(Proxy Pattren):一个类代表另一个类的功能。
🍭结构性模式之代理模式(Proxy Pattern)
🍎代理模式
- 代理模式(Proxy Pattern),给某一个对象提供一个代理,并由
代理对象控制原对象的引用
,对象结构型模式。这种也是静态代理。
代理模式包含如下角色:
- Subject:抽象主体角色(抽象类或接口)
- Proxy:代理主体角色(代理对象类)
- RealSubject:真实主体角色(被代理对象类)
代理模式分为:
- 静态代理
- JDK动态代理
- Cglib动态代理
🍔静态代理代码实现
/**
* 抽象主体。被代理角色能干什么
*/
public interface ManTikTok {
void tikTok();
}
/**
* Subject:主体
*/
public class ZhuicatTikTok implements ManTikTok {
@Override
public void tikTok() {
System.out.println("张三 tiktok");
}
}
/**
* 代理一般都是和被代理对象属于同一个接口
*/
public class TikTokProxy implements ManTikTok {
private ManTikTok manTikTok; // 被代理对象
public TikTokProxy(ManTikTok manTikTok) {
this.manTikTok = manTikTok;
}
@Override
public void tikTok() {
// 增强功能
System.out.println("渲染直播间");
manTikTok.tikTok();
}
}
public class MainTest {
public static void main(String[] args) {
ManTikTok manTikTok = new ZhuicatTikTok();
TikTokProxy tikTokProxy = new TikTokProxy(manTikTok);
tikTokProxy.tikTok();
}
}
渲染直播间
张三 tiktok
可以发现:静态代理就是装饰器,我们可以认为装饰器模式是代理模式的一种
使用静态代理时,每一次代理的代理对象不一样,就需要创建不同的代理类。
🍔JDK动态代理代码实现
/**
* 抽象主体。被代理角色能干什么
*/
public interface ManTikTok {
void tikTok();
}
public interface SellTikTok {
Integer sell();
}
/**
* Subject:主体
*/
public class ZhuicatTikTok implements ManTikTok,SellTikTok {
@Override
public void tikTok() {
System.out.println("张三 tiktok");
}
@Override
public Integer sell() {
System.out.println("只要666");
return 666;
}
public void haha() {
System.out.println("哈哈");
}
}
public class JdkTikTokProxy<T> implements InvocationHandler {
private T target;
public JdkTikTokProxy(T target) {
this.target = target;
}
/**
* ClassLoader loader:当前被代理对象的类加载器
* Class<?>[] interfaces:当前被代理对象所实现的所有接口
* InvocationHandler h:被代理对象再执行目标方法时我们使用h可以定义拦截增强方法
*/
// 获取被代理对象的代理对象
public static <T> T getProxy(T t) {
Object o = Proxy.newProxyInstance(
t.getClass().getClassLoader(),
t.getClass().getInterfaces(),
new JdkTikTokProxy(t));
return (T) o;
}
/**
* 定义目标方法的拦截逻辑:每个方法都会进来的
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 反射执行
System.out.println("真正执行被代理对象的方法");
Object invoke = method.invoke(target, args);
System.out.println("返回值为:" + invoke);
return invoke;
}
}
/**
* 动态代理模式
* 代理对象和目标对象的相同点时需要实现同一个接口
*/
public class MainTest {
public static void main(String[] args) {
ZhuicatTikTok tikTok = new ZhuicatTikTok();
// new Cat
ManTikTok proxy = JdkTikTokProxy.getProxy(tikTok);
// proxy.tikTok();
// proxy.hashCode();
((SellTikTok) proxy).sell();
// 能不能代理被代理对象奔雷自己的方法?不能,proxy只能转为接口类型
// ((ZhuicatTikTok) proxy).haha();
System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));
}
}
真正执行被代理对象的方法
只要666
返回值为:666
[interface com.xl.web.proxy.synamic.ManTikTok, interface com.xl.web.proxy.synamic.SellTikTok]
缺点:被代理对象必须实现接口才行
🍔Cglib动态代理代码实现
引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class ZhuicatTikTok {
public void tikTok() {
System.out.println("张三 tiktok 哈哈哈哈哈");
}
}
/**
* 使用 cglib 帮我们创建出代理对象
*/
public class CglibProxy {
// 为任意对象创建代理
public static <T> T createProxy(T t) {
// 1、创建一个增强器
Enhancer enhancer = new Enhancer();
// 2、设置要增强哪个类的功能。增强类为t类动态创建一个子类
enhancer.setSuperclass(t.getClass());
// 3、设置回调
enhancer.setCallback(new MethodInterceptor() {
/**
* 拦截并处理被代理对象的方法调用
* @param o 被代理对象
* @param method 将要执行的目标方法
* @param args 目标方法的参数数组
* @param methodProxy 方法代理对象,用于调用目标方法
* @return 返回经过拦截处理后的方法结果
* @throws Throwable 若方法调用过程中出现异常,则抛出
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 在调用实际方法前的操作,例如记录日志、权限校验等
System.out.println("拦截钱");
// 通过methodProxy调用原始方法,并获取返回值
// Object result = methodProxy.invokeSuper(o, args);
// 或【本质就是执行 目标对象的 方法】
Object result = method.invoke(t, args);
// 在调用实际方法后的操作,例如更新数据状态、发送通知等
System.out.println("拦截后");
// 返回处理后的结果
return result;
}
});
return (T) enhancer.create();
}
}
public class MainTest {
public static void main(String[] args) {
ZhuicatTikTok zhuicatTikTok = new ZhuicatTikTok();
ZhuicatTikTok proxy = CglibProxy.createProxy(zhuicatTikTok);
proxy.tikTok();
System.out.println("========");
proxy.hashCode();
}
}
拦截钱
张三 tiktok 哈哈哈哈哈
拦截后
========
拦截钱
拦截后
原本的类
cglib代理类 继承了 原本的类
🍕应用场景
- 什么场景用到?
- Mybatis 的 mapper 到底是什么?怎么生成的?
- Mybatis 采用的是 JDK 动态代理
- UserMapper,Mybatis 帮我们写实现 MapperProxy
- Seata 的 DataSourceProxy 是什么?
- DruidDataSource 存在的 Proxy 模式
- Mybatis 的 mapper 到底是什么?怎么生成的?
区别装饰器和代理模式
- 装饰器和代理之间的区别很细微,可以认为装饰器是代理的一个子集。
- 静态代理就是装饰器的方式。