通用缓存框架,spring缓存模块原理分析篇

  • Post author:
  • Post category:其他



在设计自己的缓存框架之前,有必要了解一下


spring





cache


模块。在


spring3.1


及以后的版本中,提供了基于注解的缓存支持,但


spring


并没有对缓存进行具体实现(除了提供一个简单的基于


Map


的实现之外)。本框架就是在此基础上进行扩展。




1





spring


通过注解操作缓存的使用方法与示例:




因为篇幅有限,网上有很多现成的使用例子,这里不介绍


spring


缓存具体使用方法。




2





spring


通过注解操作缓存的原理:




spring


能通过注解操作缓存,是因为它采用天生就具有的强大的


AOP


机制,拦截业务方法。总体流程是:当一个方法上配置了


Cacheable


之类的注解后,这个方法被调用时,就会被一个叫


CacheInterceptor


的拦截器拦截,进入该类的


invoke()


方法中,如果当前


context


已经初始化完成,该方法紧接着会调用


execute()





execute()


方法中会读取原来被调用业务方法上的注解信息,通过这些信息进行相应的缓存操作,再跟据操作的结果决定是否调用原方法中的业务逻辑。这就是


spring


通过注解操作缓存的总体流程。



CacheInterceptor


是在


srping


上下文初始化的时候,通过配置文件中的


<cache:annotation-driven/>


标签注册到


context


中的。此标签的


cache


名称空间对应的处理类的是


org.springframework.cache.config. CacheNamespaceHandler


,其中对于


cache:annotation-driven


标签的解析类是


org.springframework.cache.config.AnnotationDrivenCacheBeanDefinitionParser(





CacheNamespaceHandler


类的代码中可以找到


)


,在这个类的


parse()


解析方法中跟据


annotation-driven


标签中的


model


属性决定是通过代理的方式实现


aop


还是通过


aspectj


的方式进行切面拦截


(


默认采用


proxy


代理方式


)


。对于代理方式,会调用


registerCacheAdvisor()


注册缓存


Advisor


。在这个方法中会注入一个


CacheInterceptor


类型的拦截器。这就实现了对业务方法的切面拦截。






registerCacheAdvisor()


方法中,还会调用一个叫


parseCacheResolution()


方法来注入一个缓存解析器


CacheResolver


,如果


<cache:annotation-driven />


标签中有


cache-resolver


的配置,就跟据配置注入一个


CacheResolver


,否则,就默认注入一个


SimpleCacheResolver


类型的实例。每个


CacheResolver


中包装了一个


CacheManager


,这个


CacheManager


可通过


<cache:annotation-driven/>


标签中


cacheManager


之类的配置进行指定,如果没有指定,会自动注入一个叫


cacheManager





bean






CacheInterceptor


在执行


execute()


的过程中会调用事先注入的


CacheResolver


实例的


resolveCaches()


方法解析业务方法中需要操作的缓存


Cache


列表(


resolveCaches()


方法内部实现是通过调用此


CacheResolver


实例中的


cacheManager


属性的


getCache()


方法获取


Cache


)。获取到需要操作的


Cache


列表后,遍历这个列表,然后都过调用


doGet(Cache cache)





doPut(Cachecache)


方法进行缓存操作。


doGet()





doPut()


方法在


CacheInterceptor


类的父类


AbstractCacheInvoker


中定义


(


注意这里的


Cache


列表,是


spring


包装了特定厂商缓存后的


Cache


对像,是


org.springframework.cache.Cache


类型的实例


)






总体上说,


CacheInterceptor





execute()


中对缓存的操作就是通过事先注一个


CacheResolver





CacheManager


实例,然后通过调用这个


CacheResolver


实例的


resolveCaches()


获得需要操作的


Cache


列表,再遍历列表,将每个


Cache


实例作为参数传入


doGet()





doPut()


来实现缓存读取。当然,还需要一些


Key


之类的参数,这个是由


keyGenerator


自动生成的。对于


keyGenerator


,这里不再介绍。看看源码就很容易理清思路。




3





spring


缓存模块的类简介:




spring


缓存模块对可用的


Cache


采用适配器模式进行了统一的封装。具体代码在


spring-context-xxx.jar


包中的


org.springframework.cache.Cache


接口,此接口声明一些储如


get(Object key)





put(Objectkey,Object value)


等缓存操作的统一


api


。在这个接口中,还声明了一个叫


getNativeCache()


的方法,返回它适配的具体的缓存实现


(


比如在集成


ehcache


时,这个接口实现类的实例调用


getNativeCache()


时会返回


net.sf.ehcache.Cache


类型的实例


)


。每一个


Cache


实例都通过名称加以区分,所以在


Cache


接口中,还声明了一个


getName()


返回此实例的名称。


spring


提供一个叫


ConcurrentMapCache


的基于


Map





Cache


实现类,作为它内置的本地缓存实现方案。



Cache


相关的类图如下:









:springcache


类图




所有被包装的


Cache


,都由


CacheManager


实例进行统一管理


(


在上文的原理分析中可以看到,在


<cache:annotation-driven />


标签的解析过程中会自动注入一个


CacheManager


实例


)


,他提供一个叫


getCache(String name)


的方法,跟据名称获得一个被包装的


Cache









Srping


的缓存模块中,


spring-context-xxx.jar


包中自带一个叫


ConcurrentMapCacheManagr


的简单实现类,它可以管理上文的提到的


ConcurrentMapCache


类。开发人员如果配置了此管理器,也就拥有了本地缓存的能力。另外,为了让应用支持同时存在多个


CacheManager





spring


提供了一个


CompositeCacheManager


的实现类,以组合设计模式的方式统一管理多个


CacheManager


实例。



CacheManager


部分类图如下:






上图中,


CacheManager


接口中只有两个方法


getCache(String)





getCacheNames()


,显然,这两个方法的作用就是跟据名称获得


Cache


实例以及获得所有被管理的缓存的名称列表。



上图中


CompositeCacheManager





ConcurrentMapCacheManager


类的作用在前文已经介绍过了。这里简单再说一下具体实现的方式:


CompositeCacheManager


中维护一个


CacheManager


列表,用户可以通过配置,把多个


CacheManager


配置到这个列表中,使得应用可以同时管理多个缓存管理器。这个类对于


getCache(String)


方法实现是通过遍历这个列表,匹配出


name


相同的


Cache


实例并返回。这个类还可以通过配置指定一个


boolean





fallbackToNoOpCache


标志属性,它的作用就是,当通过


getCache(string)


获取不到


Cache


实例时,是否不进行任何缓存操作。在默认情况或者


fallbackToNoOpCache


值为


false


时,在通过


getCache(string)


获取不到


Cache


实例时,业务层上可能会抛出运行时异常(比如提示“找不到


XXX


名称的


Cache


”)。但如果为


true


时,这时候不进行任何缓存操作也不抛异常,这种场景主要用于在不具备缓存条件的时候,在不改代码的情况下,禁用缓存。


spring


对于这种机制的实现,是通过上图中没有画出的两个特殊的类来实现的


: NoOpCacheManager





NoOpCache


类。这两个类分别是


CacheManager





Cache


类的子类,表示不进行任何缓存操作。






ConcurrentMapCacheManager


内置的缓存管理器中,可以通过配置指定一个


boolean


类型的


allowNullValues


属性,用于指定缓存中能否保存


null


值。因为该管理器是用于


spring


通过


Map


实现的内置缓存的管理器实现。在对应的


Cache


实现类


ConcurrentMapCache


中可以看到,它是通过


ConcurrentHashMap


保存所有建值对数据的。然而


ConcurrentHashMap


并不支持保存


null


值,直接在


ConcurrentHashMap





put


空值会抛空指针异常。然而,往缓存中保存空值有时候确实也是有必要的。比如,在从数据库查询某项数据时,因数据不存在,返回了


null


。这时候如果不把这个


null


值保存到缓存中去,那么下次再作查询时,缓存就无法命中,从而导致重复查询数据库,这就是所谓的缓存穿透。为了防止这种情况,这就要对


null


值做一个包装,把它包装成一个非


null


的而且在业务上认为是无效的对像保存到缓存里面。


ConcurrentMapCacheManager


中的


allowNullValues


就是用于指定能否缓存


null


,如果此值为


true


,将把自动把


null


包装成无效对像缓存起来,如果为


false


,那么需要开发人员自行从业务层上保证不往缓存中保存


null


数据。



在上面类图中,最重要的是类就是


AbstractCacheManager


抽象类了,它只对


CacheManager


提供了一个简单实现,并开放了一些比如


loadCaches()


之类的抽象方法,对于这个类的具体实现,由需要集成的具体的缓存厂商来实现。



AbstractCacheManager


类除了实现


CacheManager


接口之外,还实现了


srping


框架的


InitializingBean


接口,这使得此类型的


bean


在被


spring


初始化的时候,会自动调用


afterPropertiesSet()


方法,这个方法会调用此类的


initializeCaches()


的方法进行初始化。它的具体逻辑是通过用调用抽象方法


loadCaches()


获取它能管理的所有


Cache


实列列表,并遍历它,把它都添加到此实例的


cacheMap


属性集合中,同时把所有的


name


都统一加入到


cacheNames


集合中,以便方法


getCacheNames()


可以返回所有


cache


的名称集合。



抽象方法


loadCaches()


的作用是从具体的缓存实现中加载所有它能管理的


Cache


(比如调用


EhCache


相关的


api


加载他所有的


Cache


)。



在这个类中,最重要的方法就是


getCache(String)


,表示通过缓存名称获取缓存实例。它的实现逻辑是,先从


cacheMap


集合以


name


作为


key


查找


cache


,如果找不到,就调用


getMissingCache()


方法获取。这个


getMissingCache()


意图为


:


返回


loadCaches()


原来没有加载到的


Cache(


这里有一次重新加载的机会


)


。当然,


AbstractCacheManager


这个类并没有对这个方法做特别的实现,只是简单返回了


null


,具体的实现类可以覆盖这个方法。



这个类中还有其它的诸如


addCache()


之类的方法,就不介绍了。下面看看


spring


自带的


ehcache


实现。




4





spring cache


内置的


ehcache


支持方案:




springcache


模块通过提供


ehcache


相关的几个实现类对


ehcache


进行支持,采用的是适配器设计模式,相关类在


srping-cotext-suppoert-xxx.jar


包中如下类图:





图中可看出,最重要的两个类就是


EhCacheCache





EhCacheCacheManager


,分别是


Cache





AbstractCacheManager


的子类实现类。这两个类中,对于父类抽象的方法的实现,都是委托它类部的


Ehcache





net.sf.ehcache.CacheManager


通过调用


ehcache


相关


api


来完成。


然而,因为


ehcache


的配置参数比较多,为了方便开发人员简洁的配置


EhCacheCache





EhCacheCacheManager


实例。


spring


提供了两个对应的工厂类


:EhCacheFactoryBean





EhCacheManagerFactoryBean


。开发人员只要在配置文件中配置这两个类型的


bean


,就可以很方便的完成与


ehcache


的集成







4


、集成


memcache


方案提示:


spring


并没有提供对


memcache


的直接支持,需要我们自己实现,通过以上分析,可以想象,集成


memcache


的主要思路就是实现自己的


CacheManager





Cache


类。限于篇幅,在后面的文章中再给出具体的实现方案。





目前


JAD-CACHE


项目已在开源中国码云平台上开源,地址:



https://git.oschina.net/457049726/jad-cache


想了解更多信息的同学们可以扫以下二维码关注我的微信公众号:





版权声明:本文为w4hechuan2009原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。