装饰器模式在MyBatis以及Spring源码中的应用

本文深入探讨了装饰器模式,通过实例解释了其在日志系统中的应用,展示了如何使用装饰器模式动态扩展日志功能。同时,分析了MyBatis源码中CachingExecutor如何利用装饰器模式实现缓存功能,并讨论了Spring框架中装饰器模式的使用。最后,对比了装饰器模式与代理模式的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

结构型模式                 ————顺口溜:适装桥组享代外

目录

1、装饰器模式

1.1 装饰器模式UML图

1.2 日常生活中看装饰器模式

1.3 使用场景

1.4 Java代码实现

2、装饰器模式在源码中的应用

2.1 MyBatis源码中装饰器模式

2.1.1 Executor

2.1.2 CachingExecutor (装饰器的具体实现对象)

2.1.3 Cache

2.1.4 具体使用

2.2 Spring源码中装饰器模式

2.2.1 Decorator例子

2.2.2 Wrapper例子

3、装饰器模式的优缺点

3.1 优点

3.2 缺点

3.3 使用场景

3.4 注意事项

4、装饰器模式与代理模式异同


1、装饰器模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

  • 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
  • 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
  • 何时使用:在不想增加很多子类的情况下扩展类。
  • 如何解决:将具体功能职责划分,同时继承装饰者模式。
  • 关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

1.1 装饰器模式UML图

1.2 日常生活中看装饰器模式

  1. 搭配不同服饰的系统(组装好似建造者模式,但建造者模式要求建造的过程必须是稳定的,而此处完全可以内裤外穿变超人,不属于建造者模式)——引自《大话设计模式》
  2. 孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
  3. 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

1.3 使用场景

  1. 动态的增加对象的功能;
  2. 不能以派生子类的方式来扩展功能;
  3. 动态增加功能,动态撤销
  4. 限制对象的执行条件;
  5. 参数控制和检查等;

1.4 Java代码实现

写这篇博客的初衷也是恰好在工作中使用到了这个模式,觉得非常好用。需求大致是这样:采用sls服务监控项目日志,以Json的格式解析,所以需要将项目中的日志封装成json格式再打印。现有的日志体系采用了log4j + slf4j框架搭建而成。调用起来是这样的:

private static final Logger logger = LoggerFactory.getLogger(Component.class);
logger.error(string);

这样打印出来的是毫无规范的一行行字符串。在考虑将其转换成json格式时,我采用了装饰器模式。目前有的是统一接口Logger和其具体实现类,我要加的就是一个装饰类和真正封装成Json格式的装饰产品类。具体实现代码如下:

/**
 * logger decorator for other extension 
 * this class have no specific implementation
 * just for a decorator definition
 * @author jzb
 *
 */
public class DecoratorLogger implements Logger {
    public Logger logger;    public DecoratorLogger(Logger logger) {        this.logger = logger;    }    
    @Override
    public void error(String str) {}

    @Override
    public void info(String str) {}
        
    //省略其他默认实现
}
/**
 * json logger for formatted output 
 * @author jzb
 *
 */
public class JsonLogger extends DecoratorLogger {
public JsonLogger(Logger logger) {
        
        super(logger);
    }
        
    @Override
    public void info(String msg) {

        JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.info(result.toString());
    }
    
    @Override
    public void error(String msg) {
        
        JSONObject result = composeBasicJsonResult();
        result.put("MESSAGE", msg);
        logger.error(result.toString());
    }
    
    public void error(Exception e) {

        JSONObject result = composeBasicJsonResult();
        result.put("EXCEPTION", e.getClass().getName());
        String exceptionStackTrace = ExceptionUtils.getStackTrace(e);    
        result.put("STACKTRACE", exceptionStackTrace);
        logger.error(result.toString());
    }
    
    public static class JsonLoggerFactory {
        
        @SuppressWarnings("rawtypes")
        public static JsonLogger getLogger(Class clazz) {

            Logger logger = LoggerFactory.getLogger(clazz);
            return new JsonLogger(logger);
        }
    }
    
    private JSONObject composeBasicJsonResult() {
        //拼装了一些运行时信息
    }
}

可以看到,在JsonLogger中,对于Logger的各种接口,我都用JsonObject对象进行一层封装。在打印的时候,最终还是调用原生接口logger.error(string),只是这个string参数已经被我们装饰过了。如果有额外的需求,我们也可以再写一个函数去实现。比如error(Exception e),只传入一个异常对象,这样在调用时就非常方便了。

另外,为了在新老交替的过程中尽量不改变太多的代码和使用方式。我又在JsonLogger中加入了一个内部的工厂类JsonLoggerFactory(这个类转移到DecoratorLogger中可能更好一些),他包含一个静态方法,用于提供对应的JsonLogger实例。最终在新的日志体系中,使用方式如下:

    private static final Logger logger = JsonLoggerFactory.getLogger(Component.class);
    logger.error(string);

他唯一与原先不同的地方,就是LoggerFactory -> JsonLoggerFactory,这样的实现,也会被更快更方便的被其他开发者接受和习惯。

2、装饰器模式在源码中的应用

2.1 MyBatis源码中装饰器模式

MyBatis中关于Cache和CachingExecutor接口的实现类也使用了装饰者设计模式。Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;CachingExecutor是一个Executor的装饰器,给一个Executor增加了缓存的功能。此时可以看做是对Executor类的一个增强,故使用装饰器模式是合适的。

2.1.1 Executor

首先我们看下Executor,打开MyBatis的源码org.apache.ibatis.session.Configuration

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    //如果开启了二级缓存则装饰原先的Executor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

这里写图片描述

2.1.2 CachingExecutor (装饰器的具体实现对象)

public class CachingExecutor implements Executor {
    //持有组件对象
  private Executor delegate;
  private TransactionalCacheManager tcm = new TransactionalCacheManager();
    //构造方法,传入组件对象
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
      //转发请求给组件对象,可以在转发前后执行一些附加动作
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
  //...
 }

当然,这个装饰器模式的使用与标准的有点差异,但是完成的功能性质相同。

2.1.3 Cache

在MyBatis中有一级和二级缓存。在BaseExecutor(SimpleExecutor\BatchExecutor的父类)中,存放着一级缓存,org.apache.ibatis.cache.impl.PerpetualCache 是默认缓存的实现。

这里写图片描述

这里写图片描述

而当我们初始化时,会对PerpetualCache进行包装
查看org.apache.ibatis.mapping.CacheBuilder中源码,我们开启二级缓存后会包装多层,比如加上LruCache来进行缓存的清除等。

  public Cache build() {
    setDefaultImplementations();
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    //加上一些装饰
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

MyBastis中的装饰器实现对象

这里写图片描述

包装完成之后呢,在查询等方法进行缓存操作的时候,就拥有了更强大的功能了。

2.1.4 具体使用

好了,说了这么多,我们看下它是如何使用装饰器模式的吧

这里写图片描述

Cache 组件对象的接口
PerpetualCache 具体组件,是我们需要装饰的对象
LruCache等 是具体装饰类,被装饰的对象

2.2 Spring源码中装饰器模式

Spring 中用到的装饰器模式在类名上有两种表现:一种是类名中含有 Wrapper,另一种是类名中含有Decorator

2.2.1 Decorator例子

在 Spring 中,TransactionAwareCacheDecorator 类相当于装饰器模式中的抽象装饰角色,主要用来处理事务缓存,代码如下。

public class TransactionAwareCacheDecorator implements Cache {
    private final Cache targetCache;
    /**
      * Create a new TransactionAwareCache for the given target Cache.
      * @param targetCache the target Cache to decorate
      */
    public TransactionAwareCacheDecorator(Cache targetCache) {
      Assert.notNull(targetCache, "Target Cache must not be null");
      this.targetCache = targetCache;
    }
    /**
      * Return the target Cache that this Cache should delegate to.
      */
    public Cache getTargetCache() {
      return this.targetCache;
    }
    ......
}

TransactionAwareCacheDecorator 就是对 Cache 的一个包装。

下面再来看一个 MVC 中的装饰器模式:HttpHeadResponseDecorator 类,相当于装饰器模式中的具体装饰角色。

public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator{
    public HttpHeadResponseDecorator(ServerHttpResponse delegate){
        super(delegate);
    }
    ...
}

2.2.2 Wrapper例子

Spring Session就是其中之一,Spring Session通过SessionRepositoryRequestWrapper继承ServletRequestWrapper,扩展了Request,并在SessionRepositoryFilter通过调用过滤链filterChain.doFilter(strategyRequest, strategyResponse);将装饰的Request传入下一流程,具体请阅读以下类图的实现:

SessionRepositoryRequestWrapper覆盖了以下方法:

@Override
public boolean isRequestedSessionIdValid();

@Override
public HttpSessionWrapper getSession(boolean create);

@Override
public ServletContext getServletContext();

@Override
public HttpSessionWrapper getSession();

@Override
public String getRequestedSessionId();

3、装饰器模式的优缺点

3.1 优点

  • 装饰类和被装饰类可以独立发展,而不会相互耦合是继承的一个替代模式,换句话说,Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。
  • 装饰器模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component(因为Decorator本身就是继承自Component的),实现的还是is-a的关系。
  • 装饰模式可以动态地扩展一个实现类的功能,比如在I/O系统中,我们直接给BufferedInputStream的构造器直接传一个InputStream就可以轻松构件一个带缓冲的输入流,如果需要扩展,我们继续“装饰”即可。

3.2 缺点

多层的装饰是比较复杂的。

为什么会复杂?你想想看,就像剥洋葱一样,你剥到最后才发现是最里层的装饰出现了问题,可以想象一下工作量。这点从我使用Java I/O的类库就深有感受,我只需要单一结果的流,结果却往往需要创建多个对象,一层套一层,对于初学者来说容易让人迷惑。

3.3 使用场景

  1. 扩展一个类的功能。
  2. 动态增加功能,动态撤销。

3.4 注意事项

可代替继承。

4、装饰器模式与代理模式异同

  • 代理模式和装饰模式看代码差不多,实际上二者的着重点不同
  • 代理模式偏向于一个控制器,就是把被代理对象的控制权交给了代理对象,由代理对象决定被代理对象是否执行,如何执行
  • 装饰模式偏向于一个和花瓶,只能在被装饰对象前后加入一点东西,并不会去阻止执行的

其实所谓装饰器,本质上是对现有类对象的包裹,得到一个加强版的对象。

 

参考文章:

https://www.cnblogs.com/jzb-blog/p/6717349.html

https://www.runoob.com/design-pattern/decorator-pattern.html

https://blog.youkuaiyun.com/qq_18860653/article/details/80594778

http://c.biancheng.net/view/8464.html

https://www.cnblogs.com/nick-huang/p/7009511.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值