为什么用结构性模式?
结构性模式
关注点“怎样组合对象/类
?”所以我们关注下类的组合关系类结构型模式
关心类的组合,由多个类可以组合成一个更大的(继承)对象结构型模式
关心类与对象的组合,通过关联关系
在一个类中定义另一个类的实例对象(组合)- 根据“
合成复用原则
”,在系统中尽量使用关联关系来替代继承关系
,因此大部分结构型模式都是对象结构型模式
。
- 适配器模式(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 {@Overridepublic void tikTok() {System.out.println("张三 tiktok");}
}
/*** 代理一般都是和被代理对象属于同一个接口*/
public class TikTokProxy implements ManTikTok {private ManTikTok manTikTok; // 被代理对象public TikTokProxy(ManTikTok manTikTok) {this.manTikTok = manTikTok;}@Overridepublic 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 {@Overridepublic void tikTok() {System.out.println("张三 tiktok");}@Overridepublic 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;}/*** 定义目标方法的拦截逻辑:每个方法都会进来的*/@Overridepublic 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 CatManTikTok 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 若方法调用过程中出现异常,则抛出*/@Overridepublic 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 到底是什么?怎么生成的?
区别装饰器和代理模式
- 装饰器和代理之间的区别很细微,可以认为装饰器是代理的一个子集。
- 静态代理就是装饰器的方式。