Análisis de código fuente de Myabtis análisis de módulo de cuatro cachés, el uso del modo de decoración

​¡Crear un hábito de escritura juntos! Este es el noveno día de mi participación en el "Nuevo plan diario de los Nuggets·Desafío de actualización de abril", [Haga clic para ver los detalles del evento]

1. Análisis del módulo de caché de Mybatis

El módulo de caché de mybatis tiene las siguientes características:

  1. La implementación de la caché MyBatis se basa en Map, y la lectura y escritura de datos de la caché es la función básica central del módulo de caché;
  2. Además de las funciones principales, hay muchas funciones adicionales adicionales, como: prevención de averías de caché, adición de estrategias de borrado de caché (fifo, lru), funciones de serialización, capacidades de registro, capacidades de borrado de tiempo, etc.;
  3. Se puede agregar funcionalidad adicional a la funcionalidad básica básica en cualquier combinación;

Entonces, ¿cómo podemos agregar con gracia capacidades adicionales a la funcionalidad principal?

Algunos estudiantes pueden saber que el método de herencia se usa para extender funciones adicionales. El método de herencia es estático y el usuario no puede controlar el método y el tiempo de agregar comportamiento. Además, hay muchas combinaciones de funciones nuevas y el uso de la herencia puede dar lugar a la existencia de un gran número de subclases;

Basado en las capacidades centrales de almacenamiento en caché de Map, la capacidad de mejorar elegantemente el bloqueo, las estrategias de limpieza, la serialización, el registro, etc., en cualquier combinación, es el mayor problema en la implementación del módulo de caché de Mybatis. proxy dinámico o herencia Los métodos tienen los siguientes problemas: estos métodos son estáticos y el usuario no puede controlar cómo y cuándo agregar comportamiento; además, existen múltiples combinaciones de funciones nuevas y el uso de la herencia puede conducir a la existencia de un gran número de subclases. En resumen, el módulo de caché MyBtis usa el patrón decorador para implementar el módulo de caché; 

En segundo lugar, el modo decorador. 

1. Introducción al patrón decorador

El patrón Decorator es una técnica que se utiliza en lugar de la herencia para extender la nueva funcionalidad de un objeto sin agregar subclases a través de la herencia. Utilice la relación de asociación de objetos en lugar de la herencia, que es más flexible y evita la rápida expansión del sistema de tipos. El diagrama de clases UML del decorador es el siguiente 
:

 Los componentes tienen los siguientes significados:

  • Componente : la interfaz del componente define el comportamiento implementado por todas las clases de componentes y decoradores; 
  • 组件实现类(ConcreteComponent) :实现 Component 接口,组件实现类就是被装饰器 装饰的原始对象,新功能或者附加功能都是通过装饰器添加到该类的对象上的
  • 装饰器抽象类(Decorator) :实现 Component 接口的抽象类,在其中封装了一个 Component 对象,也就是被装饰的对象;
  • 具体装饰器类(ConcreteDecorator) :该实现类要向被装饰的对象添加某些功能; 

 我们很多人都玩过游戏,以DNF里的职业剑魂为例,装饰器模式图示如下: 

2、装饰器模式优点

装饰器相对于继承,装饰器模式灵活性更强,扩展性更强:  

  • 灵活性:装饰器模式将功能切分成一个个独立的装饰器,在运行期可以根据需要动态的 添加功能,甚至对添加的新功能进行自由的组合;
  • 扩展性:当有新功能要添加的时候,只需要添加新的装饰器实现类,然后通过组合方式 添加这个新装饰器,无需修改已有代码,符合开闭原则;

3、装饰器模式使用举例

  1. IO 中输入流和输出流的设计 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("c://a.txt"))); 
  2. 对网络爬虫的自定义增强,可增强的功能包括:多线程能力、缓存、自动生成报表、黑 白名单、random 触发等 

三、装饰器在缓存模块的使用 

MyBatis 缓存模块是一个经典的使用装饰器实现的模块,类图如下:

  • Cache:Cache 接口是缓存模块的核 心接口,定义了缓存的基本操作; 
  • PerpetualCache:在缓存模块中扮演 ConcreteComponent 角色,使用 HashMap 来实现 cache 的相关操作; 
  • BlockingCache:阻塞版本的缓存装 饰器,保证只有一个线程到数据库去查 找指定的 key 对应的数据;BlockingCache 是阻塞版本的缓存装饰器,这个装饰器通过 ConcurrentHashMap 对锁的粒度 进行了控制,提高加锁后系统代码运行的效率(注:缓存雪崩的问题可以使用细粒度锁的方 式提升锁性能) 

源码分析:


/**
 * Simple blocking decorator 
 * 
 * Simple and inefficient version of EhCache's BlockingCache decorator.
 * It sets a lock over a cache key when the element is not found in cache.
 * This way, other threads will wait until this element is filled instead of hitting the database.
 * 
 * 阻塞版本的缓存装饰器,保证只有一个线程到数据库去查找指定的key对应的数据
 *
 *
 */
public class BlockingCache implements Cache {

  //阻塞的超时时长
  private long timeout;
  //被装饰的底层对象,一般是PerpetualCache
  private final Cache delegate;
  //锁对象集,粒度到key值
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      releaseLock(key);
    }
  }

  @Override
  public Object getObject(Object key) {
    acquireLock(key);//根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
    Object value = delegate.getObject(key);
    if (value != null) {//获取数据成功的,要释放锁
      releaseLock(key);
    }        
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    releaseLock(key);
    return null;
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
  
  private ReentrantLock getLockForKey(Object key) {
    ReentrantLock lock = new ReentrantLock();//创建锁
    ReentrantLock previous = locks.putIfAbsent(key, lock);//把新锁添加到locks集合中,如果添加成功使用新锁,如果添加失败则使用locks集合中的锁
    return previous == null ? lock : previous;
  }
  
//根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
  private void acquireLock(Object key) {
	//获得锁对象
    Lock lock = getLockForKey(key);
    if (timeout > 0) {//使用带超时时间的锁
      try {
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {//如果超时抛出异常
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());  
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    } else {//使用不带超时时间的锁
      lock.lock();
    }
  }
  
  private void releaseLock(Object key) {
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }  
}
复制代码

除了 BlockingCache 之外,缓存模块还有其他的装饰器如: 

  1. LoggingCache:日志能力的缓存; 
  2. ScheduledCache:定时清空的缓存; 
  3. BlockingCache:阻塞式缓存; 
  4. SerializedCache:序列化能力的缓存; 
  5. SynchronizedCache:进行同步控制的缓存; 

那么问题来了,我们知道HashMap是线程不安全的,那么Mybatis 的缓存功能使用 HashMap 实现会不会出现并发安全的问题呢? 

MyBatis 的缓存分为一级缓存、二级缓存。二级缓存是多个会话共享的缓存,确实会出 现并发安全的问题,因此 MyBatis 在初始化二级缓存时,会给二级缓存默认加上 SynchronizedCache 装饰器的增强,在对共享数据 HashMap 操作时进行同步控制,所以二级 缓存不会出现并发安全问题;而一级缓存是会话独享的,不会出现多个线程同时操作缓存数 据的场景,因此一级缓存也不会出现并发安全的问题; 

四、缓存的唯一标识 CacheKey 

MyBatis 中涉及到动态 SQL 的原因,缓存项的 key 不能仅仅通过一个 String 来表示,所以通 过 CacheKey 来封装缓存的 Key 值,CacheKey 可以封装多个影响缓存项的因素;
判断两个 CacheKey是否相同关键是比较两个对象的hash值是否一致;构成CacheKey对象的要素包括:

  1. mappedStatment 的 id 
  2. 指定查询结果集的范围(分页信息) 
  3. 查询所使用的 SQL 语句 
  4. 用户传递给 SQL 语句的实际参数值

CacheKey 中 update 方法和 equals 方法是进行比较时非常重要的两个方法:

  • update 方法:用于添加构成 CacheKey 对象的要素,每添加一个元素会对 hashcode、checksum、count 以及 updateList 进行更新;
  • equals 方法:用于比较两个元素是否相等。首先比较 hashcode、checksum、count 是否 相等,如果这三个值相等,会循环比较 updateList 中每个元素的 hashCode 是否一致;

 按照这种方式判断两个对象是否相等,一方面能很严格的判断是否一致避免出现误判, 另外一方面能提高比较的效率; 

Supongo que te gusta

Origin juejin.im/post/7084886691940892679
Recomendado
Clasificación