返回列表 发帖

spring缓存框架之AOP渐进实现 [转]

案例描述

系统中常常存在这样的一些逻辑:某些业务方法的执行结果在一段时间内比较稳定,不太容易发生变化,这些执行的结果可以借助缓存框架,通过一些细粒度的缓存过期策略,存放于缓存中,再次执行这些业务方法时可以直接到缓存中命中,从而降低系统开销,提高效率

解决方案之一:第一小步

首先,我们需要一套缓存体系,缓存的过期策略大致有如下几类:

(1)EternalPolicy:永不过期策略

(2)IdlePolicy:空闲过期策略

(3)LivePolicy:

(4)IdleAndLivePolicy:

于是我们抽取出的Cache接口会像下面这样:
Java代码
  1. public interface Cache {   
  2.   
  3.     Cache putInCacheWithEternalPolicy(String cacheName, Object key, Object value);   
  4.   
  5.     Cache putInCacheWithIdlePolicy(String cacheName, Object key, Object value, Integer idleSeconds);   
  6.   
  7.     Cache putInCacheWithLivePolicy(String cacheName, Object key, Object value, Integer liveSeconds);   
  8.   
  9.     Cache putInCacheWithIdleAndLivePolicy(String cacheName, Object key, Object value, Integer idleSeconds, Integer liveSeconds);   
  10.   
  11.     Object getFromCache(String cacheName, Object key);   
  12.   
  13. }  
  14. [code]
  15. [code]public interface Cache {

  16.         Cache putInCacheWithEternalPolicy(String cacheName, Object key, Object value);

  17.         Cache putInCacheWithIdlePolicy(String cacheName, Object key, Object value, Integer idleSeconds);

  18.         Cache putInCacheWithLivePolicy(String cacheName, Object key, Object value, Integer liveSeconds);

  19.         Cache putInCacheWithIdleAndLivePolicy(String cacheName, Object key, Object value, Integer idleSeconds, Integer liveSeconds);

  20.         Object getFromCache(String cacheName, Object key);

  21. }
复制代码
有了Cache层就可以开始我们的编码了:

Java代码
  1. public Object invoke(Object parameter) {   
  2.     Object value = this.cache.getFromCache(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY)   
  3.     if(value != null){   
  4.         return value;   
  5.     }   
  6.       
  7.     value = .....   
  8.     this.cache.putInCacheWithLivePolicy(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY,60);   
  9.     return value;   
  10. }  public Object invoke(Object parameter) {
  11.         Object value = this.cache.getFromCache(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY)
  12.         if(value != null){
  13.                 return value;
  14.         }
  15.         
  16.         value = .....
  17.         this.cache.putInCacheWithLivePolicy(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY,60);
  18.         return value;
  19. }
复制代码
OK,问题解决,一个丑陋的缓存实现应运而生!先喝口水,回头重构一下代码,发现很多不爽的地方了吧:每一个缓存Key都需要特殊指定,缓存过期策略都硬编码在Java代码中,而且代码中到处充斥着读缓存,写缓存……

解决方案之二:拦截篇

不满足命运的我们又开始了探索之路:提供一个缓存拦截器,所有需要缓存的业务方法都用该拦截器拦截,于是乎拦截器的代码大致如下:

Java代码
  1. public class MethodInvokeCacheInterceptor implements MethodInterceptor {   
  2.   
  3.     protected final Log log = LogFactory.getLog(getClass());   
  4.   
  5.     private Cache cache;   
  6.     private CachePolicySource policySource;   
  7.   
  8.     @Override  
  9.     public final Object invoke(MethodInvocation methodInvocation) throws Throwable {   
  10.         Class<?> targetClass = AopUtils.getTargetClass(methodInvocation.getThis());   
  11.         Method method = AopUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);   
  12.         CachePolicy policy = this.policySource.getPolicy(method);   
  13.         if (policy == null) {   
  14.             log.warn("CachePolicy of {class:[" + targetClass + "],method:[" + method + "] is not found");   
  15.             return methodInvocation.proceed();   
  16.         }   
  17.   
  18.         Object cacheKey = createCacheKey(method, methodInvocation.getArguments());   
  19.         if (log.isDebugEnabled()) {   
  20.             log.debug("Cache key is[" + cacheKey + "]");   
  21.         }   
  22.   
  23.         Object value = findFromCache(policy.getCacheName(), cacheKey);   
  24.         if (value != null) {   
  25.             if (log.isInfoEnabled()) {   
  26.                 log.info("Load cache from [" + policy.getCacheName() + "] with key[" + cacheKey + "]");   
  27.             }   
  28.         } else {   
  29.             value = methodInvocation.proceed();   
  30.             putInCache(policy, cacheKey, value);   
  31.         }   
  32.         return value;   
  33.     }   
  34.   
  35.     ............   
  36.   
  37. }  public class MethodInvokeCacheInterceptor implements MethodInterceptor {

  38.         protected final Log log = LogFactory.getLog(getClass());

  39.         private Cache cache;
  40.         private CachePolicySource policySource;

  41.         @Override
  42.         public final Object invoke(MethodInvocation methodInvocation) throws Throwable {
  43.                 Class<?> targetClass = AopUtils.getTargetClass(methodInvocation.getThis());
  44.                 Method method = AopUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
  45.                 CachePolicy policy = this.policySource.getPolicy(method);
  46.                 if (policy == null) {
  47.                         log.warn("CachePolicy of {class:[" + targetClass + "],method:[" + method + "] is not found");
  48.                         return methodInvocation.proceed();
  49.                 }

  50.                 Object cacheKey = createCacheKey(method, methodInvocation.getArguments());
  51.                 if (log.isDebugEnabled()) {
  52.                         log.debug("Cache key is[" + cacheKey + "]");
  53.                 }

  54.                 Object value = findFromCache(policy.getCacheName(), cacheKey);
  55.                 if (value != null) {
  56.                         if (log.isInfoEnabled()) {
  57.                                 log.info("Load cache from [" + policy.getCacheName() + "] with key[" + cacheKey + "]");
  58.                         }
  59.                 } else {
  60.                         value = methodInvocation.proceed();
  61.                         putInCache(policy, cacheKey, value);
  62.                 }
  63.                 return value;
  64.         }

  65.         ............

  66. }
复制代码
其中CachePolicySource是一个缓存过期策略源,负责管理业务方法的过期策略。有了这个拦截器之后就可以借助
Spring的Aop能力提供业务层的缓存能力了,Spring配置文件如下:

Xml代码
  1. <bean id="testServie" class="com.derby.dswitch.common.cache.TestServie" />  
  2.       
  3. <bean id="methodInvokeCacheTnterceptor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">  
  4.     <property name="mappedNames">  
  5.         <list>  
  6.             <value>*hello*</value>  
  7.         </list>  
  8.     </property>  
  9.     <property name="advice">  
  10.         <bean class="com.derby.dswitch.common.cache.support.MethodInvokeCacheInterceptor">  
  11.             <property name="cache" ref="cache" />  
  12.             <property name="policySource">  
  13.                 <bean class="com.derby.dswitch.common.cache.support.NameMatchCachePolicySource">  
  14.                     <constructor-arg>  
  15.                         <map>  
  16.                             <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">  
  17.                                 <bean class="com.derby.dswitch.common.cache.support.CachePolicy">  
  18.                                     <property name="cacheName" value="test.cache.name" />  
  19.                                     <property name="liveSeconds" value="2" />  
  20.                                 </bean>  
  21.                             </entry>  
  22.                         </map>  
  23.                     </constructor-arg>  
  24.                 </bean>  
  25.             </property>  
  26.         </bean>  
  27.     </property>  
  28. </bean>  
  29.   
  30. <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
  31.     <property name="beanNames">  
  32.         <list>  
  33.             <value>testServie</value>  
  34.         </list>  
  35.     </property>  
  36.     <property name="interceptorNames">  
  37.         <list>  
  38.             <value>methodInvokeCacheTnterceptor</value>  
  39.         </list>  
  40.     </property>  
  41. </bean>  <bean id="testServie" class="com.derby.dswitch.common.cache.TestServie" />
  42.         
  43. <bean id="methodInvokeCacheTnterceptor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
  44.         <property name="mappedNames">
  45.                 <list>
  46.                         <value>*hello*</value>
  47.                 </list>
  48.         </property>
  49.         <property name="advice">
  50.                 <bean class="com.derby.dswitch.common.cache.support.MethodInvokeCacheInterceptor">
  51.                         <property name="cache" ref="cache" />
  52.                         <property name="policySource">
  53.                                 <bean class="com.derby.dswitch.common.cache.support.NameMatchCachePolicySource">
  54.                                         <constructor-arg>
  55.                                                 <map>
  56.                                                         <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">
  57.                                                                 <bean class="com.derby.dswitch.common.cache.support.CachePolicy">
  58.                                                                         <property name="cacheName" value="test.cache.name" />
  59.                                                                         <property name="liveSeconds" value="2" />
  60.                                                                 </bean>
  61.                                                         </entry>
  62.                                                 </map>
  63.                                         </constructor-arg>
  64.                                 </bean>
  65.                         </property>
  66.                 </bean>
  67.         </property>
  68. </bean>

  69. <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  70.         <property name="beanNames">
  71.                 <list>
  72.                         <value>testServie</value>
  73.                 </list>
  74.         </property>
  75.         <property name="interceptorNames">
  76.                 <list>
  77.                         <value>methodInvokeCacheTnterceptor</value>
  78.                 </list>
  79.         </property>
  80. </bean>
复制代码
仔细观察任然发现了不爽的地方,拦截器的Pointcut和缓存过期策略的配置存在重合的地方,解决这个问题的方法其实也很简单,提供一个CacheProxyFactoryBean就可以轻松搞定,实现代码也许是这样的:

Java代码
  1. public class NameMatchMethodInvokeCacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean implements FactoryBean {   
  2.   
  3.     ............   
  4.   
  5.     @Override  
  6.     protected Object createMainInterceptor() {   
  7.         return new DefaultPointcutAdvisor(this.pointcut, this.interceptor);   
  8.     }   
  9.   
  10.     @Required  
  11.     public void setCachePolicy(Map<String, CachePolicy> properties) {   
  12.         Assert.notNull(properties);   
  13.         for (Iterator<Entry<String, CachePolicy>> it = properties.entrySet().iterator(); it.hasNext();) {   
  14.             Entry<String, CachePolicy> entry = it.next();   
  15.             this.properties.put(NameMatchUtils.optimize(entry.getKey()), entry.getValue());   
  16.         }   
  17.     }   
  18.   
  19.     @Override  
  20.     public void afterPropertiesSet() {   
  21.         this.interceptor.setPolicySource(this.createCachePolicySource(this.properties));   
  22.         this.pointcut = createPointcut(this.properties.keySet());   
  23.         super.afterPropertiesSet();   
  24.     }   
  25. }  public class NameMatchMethodInvokeCacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean implements FactoryBean {

  26.         ............

  27.         @Override
  28.         protected Object createMainInterceptor() {
  29.                 return new DefaultPointcutAdvisor(this.pointcut, this.interceptor);
  30.         }

  31.         @Required
  32.         public void setCachePolicy(Map<String, CachePolicy> properties) {
  33.                 Assert.notNull(properties);
  34.                 for (Iterator<Entry<String, CachePolicy>> it = properties.entrySet().iterator(); it.hasNext();) {
  35.                         Entry<String, CachePolicy> entry = it.next();
  36.                         this.properties.put(NameMatchUtils.optimize(entry.getKey()), entry.getValue());
  37.                 }
  38.         }

  39.         @Override
  40.         public void afterPropertiesSet() {
  41.                 this.interceptor.setPolicySource(this.createCachePolicySource(this.properties));
  42.                 this.pointcut = createPointcut(this.properties.keySet());
  43.                 super.afterPropertiesSet();
  44.         }
  45. }
复制代码
配置文件如下:

Xml代码
  1. <bean id="methodInvokeCacheTnterceptor" class="com.derby.dswitch.common.cache.support.NameMatchMethodInvokeCacheProxyFactoryBean"  
  2.     autowire="no">  
  3.     <property name="target">  
  4.         <bean class="com.derby.dswitch.common.cache.TestServie" />  
  5.     </property>  
  6.     <property name="cache" ref="cache" />  
  7.     <property name="cachePolicy">  
  8.         <map>  
  9.             <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">  
  10.                 <bean class="com.derby.dswitch.common.cache.support.CachePolicy">  
  11.                     <property name="cacheName" value="test.cache.name" />  
  12.                     <property name="liveSeconds" value="2" />  
  13.                 </bean>  
  14.             </entry>  
  15.         </map>  
  16.     </property>  
  17. </bean>  <bean id="methodInvokeCacheTnterceptor" class="com.derby.dswitch.common.cache.support.NameMatchMethodInvokeCacheProxyFactoryBean"
  18.         autowire="no">
  19.         <property name="target">
  20.                 <bean class="com.derby.dswitch.common.cache.TestServie" />
  21.         </property>
  22.         <property name="cache" ref="cache" />
  23.         <property name="cachePolicy">
  24.                 <map>
  25.                         <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">
  26.                                 <bean class="com.derby.dswitch.common.cache.support.CachePolicy">
  27.                                         <property name="cacheName" value="test.cache.name" />
  28.                                         <property name="liveSeconds" value="2" />
  29.                                 </bean>
  30.                         </entry>
  31.                 </map>
  32.         </property>
  33. </bean>
复制代码
至此,业务层的缓存能力在AOP的强大力量下,以一种比较优雅的方式得到了完美的解决。咖啡有点冷了,去加点热水……

解决方案之三:Annotation篇

一个优秀的程序员是不能满足于仅仅能用的基础上,还得充分考虑到易用性。拦截篇中提供的配置方式始终有点复杂的感觉,如果能提供Annotation配置的话,易用性就得到了极大的提高。

Java代码
  1. @Documented  
  2. @Retention(RetentionPolicy.RUNTIME)   
  3. @Target(ElementType.METHOD)   
  4. public @interface CacheEnable {   
  5.   
  6.     String cacheName() default "default.cache.name";   
  7.   
  8.     int liveSeconds() default 0;   
  9.   
  10.     int idleSeconds() default 0;   
  11.   
  12. }  @Documented
  13. @Retention(RetentionPolicy.RUNTIME)
  14. @Target(ElementType.METHOD)
  15. public @interface CacheEnable {

  16.         String cacheName() default "default.cache.name";

  17.         int liveSeconds() default 0;

  18.         int idleSeconds() default 0;

  19. }
复制代码
为了使用该Annotation,需要用到Spring的标签扩展能力。提供一个类CacheAnnotationDrivenBeanDefinitionParser扩展Spring中的org.springframework.beans.factory.xml.BeanDefinitionParser,其代码如下

Java代码
  1. public class CacheAnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {   
  2.   
  3.     private void registerMethodInvokeCacheInterceptor(Element element, ParserContext parserContext) {   
  4.         String policySourceBeanName = getBeanName(CacheAnnotationCachePolicySource.class);   
  5.         if (parserContext.getRegistry().containsBeanDefinition(policySourceBeanName)) {   
  6.             return;   
  7.         }   
  8.   
  9.         AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);   
  10.   
  11.         Object elementSource = parserContext.extractSource(element);   
  12.   
  13.         RootBeanDefinition policySourceDefinition = new RootBeanDefinition(CacheAnnotationCachePolicySource.class);   
  14.         policySourceDefinition.setSource(elementSource);   
  15.         policySourceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);   
  16.         parserContext.registerBeanComponent(new BeanComponentDefinition(policySourceDefinition, policySourceBeanName));   
  17.   
  18.         AbstractBeanDefinition adviceDefinition = null;   
  19.         if (!element.hasAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME)) {   
  20.             adviceDefinition = new RootBeanDefinition(MethodInvokeCacheInterceptor.class);   
  21.             adviceDefinition.setSource(elementSource);   
  22.             adviceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);   
  23.         } else {   
  24.             adviceDefinition = (AbstractBeanDefinition) parserContext.getRegistry().getBeanDefinition(   
  25.                     element.getAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME));   
  26.         }   
  27.         String cacheBeanName = getCacheBeanName(element);   
  28.         adviceDefinition.getPropertyValues().addPropertyValue("cache", new RuntimeBeanReference(cacheBeanName));   
  29.         adviceDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));   
  30.   
  31.         RootBeanDefinition pointcutDefinition = new RootBeanDefinition(CacheAnnotationPointcut.class);   
  32.         pointcutDefinition.setSource(elementSource);   
  33.         pointcutDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);   
  34.         pointcutDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));   
  35.   
  36.         RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultPointcutAdvisor.class);   
  37.         advisorDefinition.setSource(elementSource);   
  38.         advisorDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);   
  39.         advisorDefinition.getPropertyValues().addPropertyValue("advice", adviceDefinition);   
  40.         advisorDefinition.getPropertyValues().addPropertyValue("pointcut", pointcutDefinition);   
  41.   
  42.         String advisorBeanName = getBeanName(MethodInvokeCacheInterceptor.class);   
  43.         parserContext.registerBeanComponent(new BeanComponentDefinition(advisorDefinition, advisorBeanName));   
  44.   
  45.         if (log.isInfoEnabled()) {   
  46.             log.info("register internal advisor [" + advisorBeanName + "], advice ["  
  47.                     + MethodInvokeCacheInterceptor.class.getName() + "] annotation [" + CacheEnable.class.getName() + "]");   
  48.         }   
  49.     }   
  50.   
  51. }  public class CacheAnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

  52.         private void registerMethodInvokeCacheInterceptor(Element element, ParserContext parserContext) {
  53.                 String policySourceBeanName = getBeanName(CacheAnnotationCachePolicySource.class);
  54.                 if (parserContext.getRegistry().containsBeanDefinition(policySourceBeanName)) {
  55.                         return;
  56.                 }

  57.                 AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);

  58.                 Object elementSource = parserContext.extractSource(element);

  59.                 RootBeanDefinition policySourceDefinition = new RootBeanDefinition(CacheAnnotationCachePolicySource.class);
  60.                 policySourceDefinition.setSource(elementSource);
  61.                 policySourceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  62.                 parserContext.registerBeanComponent(new BeanComponentDefinition(policySourceDefinition, policySourceBeanName));

  63.                 AbstractBeanDefinition adviceDefinition = null;
  64.                 if (!element.hasAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME)) {
  65.                         adviceDefinition = new RootBeanDefinition(MethodInvokeCacheInterceptor.class);
  66.                         adviceDefinition.setSource(elementSource);
  67.                         adviceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  68.                 } else {
  69.                         adviceDefinition = (AbstractBeanDefinition) parserContext.getRegistry().getBeanDefinition(
  70.                                         element.getAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME));
  71.                 }
  72.                 String cacheBeanName = getCacheBeanName(element);
  73.                 adviceDefinition.getPropertyValues().addPropertyValue("cache", new RuntimeBeanReference(cacheBeanName));
  74.                 adviceDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));

  75.                 RootBeanDefinition pointcutDefinition = new RootBeanDefinition(CacheAnnotationPointcut.class);
  76.                 pointcutDefinition.setSource(elementSource);
  77.                 pointcutDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  78.                 pointcutDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));

  79.                 RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultPointcutAdvisor.class);
  80.                 advisorDefinition.setSource(elementSource);
  81.                 advisorDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  82.                 advisorDefinition.getPropertyValues().addPropertyValue("advice", adviceDefinition);
  83.                 advisorDefinition.getPropertyValues().addPropertyValue("pointcut", pointcutDefinition);

  84.                 String advisorBeanName = getBeanName(MethodInvokeCacheInterceptor.class);
  85.                 parserContext.registerBeanComponent(new BeanComponentDefinition(advisorDefinition, advisorBeanName));

  86.                 if (log.isInfoEnabled()) {
  87.                         log.info("register internal advisor [" + advisorBeanName + "], advice ["
  88.                                         + MethodInvokeCacheInterceptor.class.getName() + "] annotation [" + CacheEnable.class.getName() + "]");
  89.                 }
  90.         }

  91. }
复制代码
于是我们的业务层就可以通过如下配置得到缓存的能力:

Java代码
  1. public class TestServie implements ITestServie {   
  2.   
  3.     @Override  
  4.     @CacheEnable(cacheName = "test.annotation", liveSeconds = 2)   
  5.     public String hello(String name) {   
  6.         return "Hello,Passyt";   
  7.     }   
  8.   
  9. }  public class TestServie implements ITestServie {

  10.         @Override
  11.         @CacheEnable(cacheName = "test.annotation", liveSeconds = 2)
  12.         public String hello(String name) {
  13.                 return "Hello,Passyt";
  14.         }

  15. }
复制代码
至此,本文所要表达的目的已经全部实现,现在业务层可以通过三种配置方式得到缓存的能力。至于缓存如何实现,可以参考附件中提供的源码,其中包括几个测试Sample。

后话

以上任然遗留几个问题

(1)如何提供一种动态更新缓存过期的机制,如在系统运行中动态修改某个业务的缓存过期策略

(2)Cache的实现方式和Ehcache绑定过紧,基于内存的缓存实现可以参考google-collections中的MapMaker,但是它不提供空闲过期策略,需要做适当的扩展;同样基于Oscache的实现也不空闲过期策略

[ 本帖最后由 crazyit 于 2010-7-21 19:57 编辑 ]

cache-source.zip (23.88 KB)

售价: 疯狂金币 2  [记录]  [购买]

1

评分人数

  • heyitang

成功的人不是赢在起点,而是赢在转折点!

万里独行多陌路,一诗好赏便知音。

TOP

原帖由 leeyohn 于 2010-7-21 18:30 发表
不错。顶
不过这图挂掉了

哦,其中的图片没是没用的。。只是一个标志..刚才已经处理好
成功的人不是赢在起点,而是赢在转折点!

TOP

倚楼听风雨,笑看江湖路。。。

TOP

返回列表