在设计本身的缓存框架以前,有必要了解一下spring的cache模块。在spring3.1及之后的版本中,提供了基于注解的缓存支持,但spring并无对缓存进行具体实现(除了提供一个简单的基于Map的实现以外)。本框架就是在此基础上进行扩展。spring
1、spring经过注解操做缓存的使用方法与示例:数据库
由于篇幅有限,网上有不少现成的使用例子,这里不介绍spring缓存具体使用方法。设计模式
2、spring经过注解操做缓存的原理:api
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(Cache cache)方法进行缓存操做。doGet()或doPut()方法在CacheInterceptor类的父类AbstractCacheInvoker中定义(注意这里的Cache列表,是spring包装了特定厂商缓存后的Cache对像,是org.springframework.cache.Cache类型的实例)。spa
整体上说,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(Object key,Object value)等缓存操做的统一api。在这个接口中,还声明了一个叫getNativeCache()的方法,返回它适配的具体的缓存实现(好比在集成ehcache时,这个接口实现类的实例调用getNativeCache()时会返回net.sf.ehcache.Cache类型的实例)。每个Cache实例都经过名称加以区分,因此在Cache接口中,还声明了一个getName()返回此实例的名称。spring提供一个叫ConcurrentMapCache的基于Map的Cache实现类,做为它内置的本地缓存实现方案。
Cache相关的类图以下:
全部被包装的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支持方案:
spring cache模块经过提供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类。限于篇幅,在后面的文章中再给出具体的实现方案。
想了解更多信息的同窗们能够扫如下二维码关注个人微信公众号: