概述
在设计自己的缓存框架之前,有必要了解一下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
想了解更多信息的同学们可以扫以下二维码关注我的微信公众号:
最后
以上就是酷炫大象为你收集整理的通用缓存框架,spring缓存模块原理分析篇的全部内容,希望文章能够帮你解决通用缓存框架,spring缓存模块原理分析篇所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复