从源码角度理解Java设计模式——装饰者模式
一、饰器者模式介绍
装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活。
适用场景:动态的给一个对象添加或者撤销功能。
优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个功能按想要的顺序执行,以实现不同效果。
缺点:更多的类,使程序复杂
类型:结构型。
类图:
源码分析中的典型应用
- Java I/O 中的装饰者模式
- Spring Session 中的装饰者模式
- Mybatis 缓存中的装饰者模式
二、给系统添加日志,安全、限流示例
一般系统的安全、日志、限流等业务无关代码可以抽离出来,在Controller前后用切面改造,模板方法模式可以部分解决这个问题:
public abstract class BaseAspect { Logger logger = LoggerFactory.getLogger(BaseCommand.class); public void execute(){ //记录日志 logger.debug("..start.."); //过滤跨站脚本攻击 paramXssAspect(); //限制速率 doRateLimit(); doBusiness(); logger.debug("..end.."); } public abstract void doBusiness(); } class PlaceOrderAspect extends BaseAspect { @Override public void doBusiness() { //下单操作 } } class PayOrderAspect extends BaseAspect { @Override public void doBusiness() { //支付操作 } }
在父类中已经把”乱七八糟“的非业务代码写好了,只留了一个抽象方法等子类去实现,子类变的很清爽,只需关注业务逻辑就可以了。
这种方式最大的缺陷就是父类会定义一切:要执行那些非业务代码,以什么顺序执行等等,子类只能无条件接受。如果有一个子类,不限制速率,那么它也没有办法把它去掉。
利用装饰者模式,针对上面的问题,可以变的很灵活。
//最高层抽象组件 interface IAspect { String doHandlerAspect(); } //基本被装饰类 class AspectImpl implements IAspect{ @Override public String doHandlerAspect() { return "裸跑代码."; } } abstract class AbstractDecorator implements IAspect{ //很重要,组合抽象构件到自己的类中 private IAspect aspect; public AbstractDecorator(IAspect aspect) {//通过IAspect构造自己 this.aspect = aspect; } @Override public String doHandlerAspect() { return this.aspect.doHandlerAspect(); } }
附加记录日志,安全,限流功能:
class LoggerAspect extends AbstractDecorator{ public LoggerAspect(IAspect aspect){ super(aspect); } @Override public String doHandlerAspect() { return super.doHandlerAspect()+"+记录日志."; } } class ParamXssAspect extends AbstractDecorator{ public ParamXssAspect(IAspect aspect){ super(aspect); } @Override public String doHandlerAspect() { return super.doHandlerAspect()+"+过滤危险字符."; } } class LimitAspect extends AbstractDecorator{ public LimitAspect(IAspect aspect){ super(aspect); } @Override public String doHandlerAspect() { return super.doHandlerAspect()+"+限流."; } }
测试一下:
public class Test { public static void main(String[] args) { IAspect aspect = new LimitAspect(new ParamXssAspect(new LoggerAspect(new AspectImpl()))); System.out.println(aspect.doHandlerAspect()); } }
运行结果:
------
裸跑代码.+记录日志.+过滤危险字符.+限流.
------
通过上面可以看出,装饰者模式可以任意次序组装功能,是不是很灵活?另外,也可以把上述三个功能封装成注解@Log、@ParamXss、@AccessLimit,实现可拔插。如果读者想看注解功能完整实现,可以参考我的这个项目:SpringBoot+JWT+Shiro+MybatisPlus实现Restful快速开发后端脚手架
三、源码中的装饰者模式
3.1、Java IO中是体现最明显的装饰者模式。
它基于字符流(InputStream/OutputStream) 和 字节流(Reader/Writer)作为基类,下面画出InputStream、Reader的部分类图:
这里总结几种常用流的应用场景:
流名称 | 应用场景 |
---|---|
ByteArrayInputStream | 访问数组,把内存中的一个缓冲区作为 InputStream 使用,CPU从缓存区读取数据比从存储介质的速率快10倍以上 |
StringBufferInputStream | 把一个 String 对象作为。InputStream。不建议使用,在转换字符的问题上有缺陷 |
FileInputStream | 访问文件,把一个文件作为 InputStream ,实现对文件的读取操作 |
PipedInputStream | 访问管道,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯 |
SequenceInputStream | 把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来 |
DataInputStream | 特殊流,读各种基本类型数据,如byte、int、String的功能 |
ObjectInputStream | 对象流,读对象的功能 |
PushBackInputStream | 推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中 |
BufferedInputStream | 缓冲流,增加了缓冲功能 |
3.2、Spring Session中的ServletRequestWrapper(Response也一样)的装饰者模式。
public class ServletRequestWrapper implements ServletRequest { private ServletRequest request;//组合抽象接口到自己的类中 public ServletRequestWrapper(ServletRequest request) {//可以构造自己 if(request == null) { throw new IllegalArgumentException("Request cannot be null"); } else { this.request = request; } } public ServletRequest getRequest() { return this.request; } public void setRequest(ServletRequest request) { if(request == null) { throw new IllegalArgumentException("Request cannot be null"); } else { this.request = request; } } //省略... }
3.3、Spring Cache中的TransactionAwareCacheDecorator的装饰者模式。
其实从类名就可以看出。
public class TransactionAwareCacheDecorator implements Cache { private final Cache targetCache;//把Cache组合到自己类中 public TransactionAwareCacheDecorator(Cache targetCache) {//通过Cache构造自己 Assert.notNull(targetCache, "Target Cache must not be null"); this.targetCache = targetCache; } public <T> T get(Object key, Class<T> type) { return this.targetCache.get(key, type); } public void put(final Object key, final Object value) { // 判断是否开启了事务 if (TransactionSynchronizationManager.isSynchronizationActive()) { // 将操作注册到 afterCommit 阶段 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { public void afterCommit() { TransactionAwareCacheDecorator.this.targetCache.put(key, value); } }); } else { this.targetCache.put(key, value); } } // ...省略... }
3.4、Mybatis中的装饰者。
Cache为抽象构件类,PerpetualCache为具体构件类,decorators包下的类为装饰类,这里没有抽象装饰类。
参考:
《码农翻身》刘欣
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
微服务架构设计基础之立方体模型
背景 对于现在的微服务架构的应用来说,对大量并发的及时响应是一项制胜能力。据用户行为分析平台统计,随行付的某一款APP产品每日请求就达到上千万次用户请求、加解密服务3000万次/日等等。这些微服务每时每刻在处理如此高强度的请求,对数据层的应对能力要求极高。如果我们把对速度的需求放在复杂的分布式数据架构背景下,是很难想象如何让应用应对如此巨大的数据访问量的。但很幸运,我们有方法做到。即立方体模型。 立方体模型 可扩展的分布式系统架构设计有一个朴素的理念,就是:通过加机器就可以解决容量和可用性的问题。 对于一个迅速增长的应用而言,容量和性能是首当其冲要面临的问题。但随着时间的向前推移、应用规模不断的快速增长,除了面对性能与容量的问题外,还需要解决功能与模块数量上增长带来的系统复杂性问题、业务变化带来的差异化服务问题等。而多数情况下应用设计之初出于诸多因素的考量,并没有充分考虑或在设计之初就将此类问题提上日程,导致系统的重构成为常态,从而影响业务交付能力。对此,「架构即未来」一书中提出了更加系统的可扩展模型,可扩展模型是一个富有启发性的方法,描述了微服务三个维度的扩展方法,可以通过它来了解微...
- 下一篇
【MySQL经典案例分析】 Waiting for table metadata lock
本文由云+社区发表 一、 问题是这样来的 2018年某个周末,接到连续数据库的告警,告警信息如下: 二、 苦逼的探索过程 1、总体的思路 看到too many connection的报错信息,基本上可以把问题定位在: (1)机器负载飙升,导致SQL执行效率下降,导致连接推积 (2)业务访问量突增(或者有SQL注入现象),导致连接数打满 (3)出现“死锁”或者锁竞争严重,导致大量SQL堆积 2、排查过程 (1)机器的各项性能指标都显示正常, 没有出现高负载现象,暂时先排除了这种原因 (2)查看监控信息,发现在连接数打满的时间点前并没有访问量突增的趋势,同时通过检查告警信息并没有发现有注入工单 (3)最后上到服务器上查看下SQL的执行情况 查看show full processlist; 大量的请求都是在“Waiting for table metadata lock”,可以分成三类请求: Select请求 Rename请求 Sleep请求 ②分析Waiting for table metadata lock 一般来说常见的“Waiting for table metadata...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Hadoop3单机部署,实现最简伪集群
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器