|
    
- 帖子
- 348
- 主题
- 7
- 精华
- 0
- 积分
- 560
- 疯狂金币
- 260
- 疯狂水晶
- 0
- 在线时间
- 126 小时
|
spring缓存框架之AOP渐进实现 [转]
案例描述
系统中常常存在这样的一些逻辑:某些业务方法的执行结果在一段时间内比较稳定,不太容易发生变化,这些执行的结果可以借助缓存框架,通过一些细粒度的缓存过期策略,存放于缓存中,再次执行这些业务方法时可以直接到缓存中命中,从而降低系统开销,提高效率。
解决方案之一:第一小步
首先,我们需要一套缓存体系,缓存的过期策略大致有如下几类:
(1)EternalPolicy:永不过期策略
(2)IdlePolicy:空闲过期策略
(3)LivePolicy:
(4)IdleAndLivePolicy:
于是我们抽取出的Cache接口会像下面这样:
Java代码- public interface Cache {
-
- Cache putInCacheWithEternalPolicy(String cacheName, Object key, Object value);
-
- Cache putInCacheWithIdlePolicy(String cacheName, Object key, Object value, Integer idleSeconds);
-
- Cache putInCacheWithLivePolicy(String cacheName, Object key, Object value, Integer liveSeconds);
-
- Cache putInCacheWithIdleAndLivePolicy(String cacheName, Object key, Object value, Integer idleSeconds, Integer liveSeconds);
-
- Object getFromCache(String cacheName, Object key);
-
- }
- [code]
- [code]public interface Cache {
- Cache putInCacheWithEternalPolicy(String cacheName, Object key, Object value);
- Cache putInCacheWithIdlePolicy(String cacheName, Object key, Object value, Integer idleSeconds);
- Cache putInCacheWithLivePolicy(String cacheName, Object key, Object value, Integer liveSeconds);
- Cache putInCacheWithIdleAndLivePolicy(String cacheName, Object key, Object value, Integer idleSeconds, Integer liveSeconds);
- Object getFromCache(String cacheName, Object key);
- }
复制代码 有了Cache层就可以开始我们的编码了:
Java代码- public Object invoke(Object parameter) {
- Object value = this.cache.getFromCache(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY)
- if(value != null){
- return value;
- }
-
- value = .....
- this.cache.putInCacheWithLivePolicy(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY,60);
- return value;
- } public Object invoke(Object parameter) {
- Object value = this.cache.getFromCache(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY)
- if(value != null){
- return value;
- }
-
- value = .....
- this.cache.putInCacheWithLivePolicy(DEFAULT_CACHE_NAME,BUSSINESS_CACHE_KEY,60);
- return value;
- }
复制代码 OK,问题解决,一个丑陋的缓存实现应运而生!先喝口水,回头重构一下代码,发现很多不爽的地方了吧:每一个缓存Key都需要特殊指定,缓存过期策略都硬编码在Java代码中,而且代码中到处充斥着读缓存,写缓存……
解决方案之二:拦截篇
不满足命运的我们又开始了探索之路:提供一个缓存拦截器,所有需要缓存的业务方法都用该拦截器拦截,于是乎拦截器的代码大致如下:
Java代码- public class MethodInvokeCacheInterceptor implements MethodInterceptor {
-
- protected final Log log = LogFactory.getLog(getClass());
-
- private Cache cache;
- private CachePolicySource policySource;
-
- @Override
- public final Object invoke(MethodInvocation methodInvocation) throws Throwable {
- Class<?> targetClass = AopUtils.getTargetClass(methodInvocation.getThis());
- Method method = AopUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
- CachePolicy policy = this.policySource.getPolicy(method);
- if (policy == null) {
- log.warn("CachePolicy of {class:[" + targetClass + "],method:[" + method + "] is not found");
- return methodInvocation.proceed();
- }
-
- Object cacheKey = createCacheKey(method, methodInvocation.getArguments());
- if (log.isDebugEnabled()) {
- log.debug("Cache key is[" + cacheKey + "]");
- }
-
- Object value = findFromCache(policy.getCacheName(), cacheKey);
- if (value != null) {
- if (log.isInfoEnabled()) {
- log.info("Load cache from [" + policy.getCacheName() + "] with key[" + cacheKey + "]");
- }
- } else {
- value = methodInvocation.proceed();
- putInCache(policy, cacheKey, value);
- }
- return value;
- }
-
- ............
-
- } public class MethodInvokeCacheInterceptor implements MethodInterceptor {
- protected final Log log = LogFactory.getLog(getClass());
- private Cache cache;
- private CachePolicySource policySource;
- @Override
- public final Object invoke(MethodInvocation methodInvocation) throws Throwable {
- Class<?> targetClass = AopUtils.getTargetClass(methodInvocation.getThis());
- Method method = AopUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
- CachePolicy policy = this.policySource.getPolicy(method);
- if (policy == null) {
- log.warn("CachePolicy of {class:[" + targetClass + "],method:[" + method + "] is not found");
- return methodInvocation.proceed();
- }
- Object cacheKey = createCacheKey(method, methodInvocation.getArguments());
- if (log.isDebugEnabled()) {
- log.debug("Cache key is[" + cacheKey + "]");
- }
- Object value = findFromCache(policy.getCacheName(), cacheKey);
- if (value != null) {
- if (log.isInfoEnabled()) {
- log.info("Load cache from [" + policy.getCacheName() + "] with key[" + cacheKey + "]");
- }
- } else {
- value = methodInvocation.proceed();
- putInCache(policy, cacheKey, value);
- }
- return value;
- }
- ............
- }
复制代码 其中CachePolicySource是一个缓存过期策略源,负责管理业务方法的过期策略。有了这个拦截器之后就可以借助
Spring的Aop能力提供业务层的缓存能力了,Spring配置文件如下:
Xml代码- <bean id="testServie" class="com.derby.dswitch.common.cache.TestServie" />
-
- <bean id="methodInvokeCacheTnterceptor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
- <property name="mappedNames">
- <list>
- <value>*hello*</value>
- </list>
- </property>
- <property name="advice">
- <bean class="com.derby.dswitch.common.cache.support.MethodInvokeCacheInterceptor">
- <property name="cache" ref="cache" />
- <property name="policySource">
- <bean class="com.derby.dswitch.common.cache.support.NameMatchCachePolicySource">
- <constructor-arg>
- <map>
- <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">
- <bean class="com.derby.dswitch.common.cache.support.CachePolicy">
- <property name="cacheName" value="test.cache.name" />
- <property name="liveSeconds" value="2" />
- </bean>
- </entry>
- </map>
- </constructor-arg>
- </bean>
- </property>
- </bean>
- </property>
- </bean>
-
- <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <property name="beanNames">
- <list>
- <value>testServie</value>
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>methodInvokeCacheTnterceptor</value>
- </list>
- </property>
- </bean> <bean id="testServie" class="com.derby.dswitch.common.cache.TestServie" />
-
- <bean id="methodInvokeCacheTnterceptor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
- <property name="mappedNames">
- <list>
- <value>*hello*</value>
- </list>
- </property>
- <property name="advice">
- <bean class="com.derby.dswitch.common.cache.support.MethodInvokeCacheInterceptor">
- <property name="cache" ref="cache" />
- <property name="policySource">
- <bean class="com.derby.dswitch.common.cache.support.NameMatchCachePolicySource">
- <constructor-arg>
- <map>
- <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">
- <bean class="com.derby.dswitch.common.cache.support.CachePolicy">
- <property name="cacheName" value="test.cache.name" />
- <property name="liveSeconds" value="2" />
- </bean>
- </entry>
- </map>
- </constructor-arg>
- </bean>
- </property>
- </bean>
- </property>
- </bean>
- <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
- <property name="beanNames">
- <list>
- <value>testServie</value>
- </list>
- </property>
- <property name="interceptorNames">
- <list>
- <value>methodInvokeCacheTnterceptor</value>
- </list>
- </property>
- </bean>
复制代码 仔细观察任然发现了不爽的地方,拦截器的Pointcut和缓存过期策略的配置存在重合的地方,解决这个问题的方法其实也很简单,提供一个CacheProxyFactoryBean就可以轻松搞定,实现代码也许是这样的:
Java代码- public class NameMatchMethodInvokeCacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean implements FactoryBean {
-
- ............
-
- @Override
- protected Object createMainInterceptor() {
- return new DefaultPointcutAdvisor(this.pointcut, this.interceptor);
- }
-
- @Required
- public void setCachePolicy(Map<String, CachePolicy> properties) {
- Assert.notNull(properties);
- for (Iterator<Entry<String, CachePolicy>> it = properties.entrySet().iterator(); it.hasNext();) {
- Entry<String, CachePolicy> entry = it.next();
- this.properties.put(NameMatchUtils.optimize(entry.getKey()), entry.getValue());
- }
- }
-
- @Override
- public void afterPropertiesSet() {
- this.interceptor.setPolicySource(this.createCachePolicySource(this.properties));
- this.pointcut = createPointcut(this.properties.keySet());
- super.afterPropertiesSet();
- }
- } public class NameMatchMethodInvokeCacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean implements FactoryBean {
- ............
- @Override
- protected Object createMainInterceptor() {
- return new DefaultPointcutAdvisor(this.pointcut, this.interceptor);
- }
- @Required
- public void setCachePolicy(Map<String, CachePolicy> properties) {
- Assert.notNull(properties);
- for (Iterator<Entry<String, CachePolicy>> it = properties.entrySet().iterator(); it.hasNext();) {
- Entry<String, CachePolicy> entry = it.next();
- this.properties.put(NameMatchUtils.optimize(entry.getKey()), entry.getValue());
- }
- }
- @Override
- public void afterPropertiesSet() {
- this.interceptor.setPolicySource(this.createCachePolicySource(this.properties));
- this.pointcut = createPointcut(this.properties.keySet());
- super.afterPropertiesSet();
- }
- }
复制代码 配置文件如下:
Xml代码- <bean id="methodInvokeCacheTnterceptor" class="com.derby.dswitch.common.cache.support.NameMatchMethodInvokeCacheProxyFactoryBean"
- autowire="no">
- <property name="target">
- <bean class="com.derby.dswitch.common.cache.TestServie" />
- </property>
- <property name="cache" ref="cache" />
- <property name="cachePolicy">
- <map>
- <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">
- <bean class="com.derby.dswitch.common.cache.support.CachePolicy">
- <property name="cacheName" value="test.cache.name" />
- <property name="liveSeconds" value="2" />
- </bean>
- </entry>
- </map>
- </property>
- </bean> <bean id="methodInvokeCacheTnterceptor" class="com.derby.dswitch.common.cache.support.NameMatchMethodInvokeCacheProxyFactoryBean"
- autowire="no">
- <property name="target">
- <bean class="com.derby.dswitch.common.cache.TestServie" />
- </property>
- <property name="cache" ref="cache" />
- <property name="cachePolicy">
- <map>
- <entry key="*com.derby.dswitch.common.cache.TestServie.hello(java.lang.String)*">
- <bean class="com.derby.dswitch.common.cache.support.CachePolicy">
- <property name="cacheName" value="test.cache.name" />
- <property name="liveSeconds" value="2" />
- </bean>
- </entry>
- </map>
- </property>
- </bean>
复制代码 至此,业务层的缓存能力在AOP的强大力量下,以一种比较优雅的方式得到了完美的解决。咖啡有点冷了,去加点热水……
解决方案之三:Annotation篇
一个优秀的程序员是不能满足于仅仅能用的基础上,还得充分考虑到易用性。拦截篇中提供的配置方式始终有点复杂的感觉,如果能提供Annotation配置的话,易用性就得到了极大的提高。
Java代码- @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- public @interface CacheEnable {
-
- String cacheName() default "default.cache.name";
-
- int liveSeconds() default 0;
-
- int idleSeconds() default 0;
-
- } @Documented
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- public @interface CacheEnable {
- String cacheName() default "default.cache.name";
- int liveSeconds() default 0;
- int idleSeconds() default 0;
- }
复制代码 为了使用该Annotation,需要用到Spring的标签扩展能力。提供一个类CacheAnnotationDrivenBeanDefinitionParser扩展Spring中的org.springframework.beans.factory.xml.BeanDefinitionParser,其代码如下
Java代码- public class CacheAnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
-
- private void registerMethodInvokeCacheInterceptor(Element element, ParserContext parserContext) {
- String policySourceBeanName = getBeanName(CacheAnnotationCachePolicySource.class);
- if (parserContext.getRegistry().containsBeanDefinition(policySourceBeanName)) {
- return;
- }
-
- AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
-
- Object elementSource = parserContext.extractSource(element);
-
- RootBeanDefinition policySourceDefinition = new RootBeanDefinition(CacheAnnotationCachePolicySource.class);
- policySourceDefinition.setSource(elementSource);
- policySourceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- parserContext.registerBeanComponent(new BeanComponentDefinition(policySourceDefinition, policySourceBeanName));
-
- AbstractBeanDefinition adviceDefinition = null;
- if (!element.hasAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME)) {
- adviceDefinition = new RootBeanDefinition(MethodInvokeCacheInterceptor.class);
- adviceDefinition.setSource(elementSource);
- adviceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- } else {
- adviceDefinition = (AbstractBeanDefinition) parserContext.getRegistry().getBeanDefinition(
- element.getAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME));
- }
- String cacheBeanName = getCacheBeanName(element);
- adviceDefinition.getPropertyValues().addPropertyValue("cache", new RuntimeBeanReference(cacheBeanName));
- adviceDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));
-
- RootBeanDefinition pointcutDefinition = new RootBeanDefinition(CacheAnnotationPointcut.class);
- pointcutDefinition.setSource(elementSource);
- pointcutDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- pointcutDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));
-
- RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultPointcutAdvisor.class);
- advisorDefinition.setSource(elementSource);
- advisorDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- advisorDefinition.getPropertyValues().addPropertyValue("advice", adviceDefinition);
- advisorDefinition.getPropertyValues().addPropertyValue("pointcut", pointcutDefinition);
-
- String advisorBeanName = getBeanName(MethodInvokeCacheInterceptor.class);
- parserContext.registerBeanComponent(new BeanComponentDefinition(advisorDefinition, advisorBeanName));
-
- if (log.isInfoEnabled()) {
- log.info("register internal advisor [" + advisorBeanName + "], advice ["
- + MethodInvokeCacheInterceptor.class.getName() + "] annotation [" + CacheEnable.class.getName() + "]");
- }
- }
-
- } public class CacheAnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
- private void registerMethodInvokeCacheInterceptor(Element element, ParserContext parserContext) {
- String policySourceBeanName = getBeanName(CacheAnnotationCachePolicySource.class);
- if (parserContext.getRegistry().containsBeanDefinition(policySourceBeanName)) {
- return;
- }
- AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
- Object elementSource = parserContext.extractSource(element);
- RootBeanDefinition policySourceDefinition = new RootBeanDefinition(CacheAnnotationCachePolicySource.class);
- policySourceDefinition.setSource(elementSource);
- policySourceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- parserContext.registerBeanComponent(new BeanComponentDefinition(policySourceDefinition, policySourceBeanName));
- AbstractBeanDefinition adviceDefinition = null;
- if (!element.hasAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME)) {
- adviceDefinition = new RootBeanDefinition(MethodInvokeCacheInterceptor.class);
- adviceDefinition.setSource(elementSource);
- adviceDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- } else {
- adviceDefinition = (AbstractBeanDefinition) parserContext.getRegistry().getBeanDefinition(
- element.getAttribute(CACHE_INTERCEPTOR_ATTRIBUTE_NAME));
- }
- String cacheBeanName = getCacheBeanName(element);
- adviceDefinition.getPropertyValues().addPropertyValue("cache", new RuntimeBeanReference(cacheBeanName));
- adviceDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));
- RootBeanDefinition pointcutDefinition = new RootBeanDefinition(CacheAnnotationPointcut.class);
- pointcutDefinition.setSource(elementSource);
- pointcutDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- pointcutDefinition.getPropertyValues().addPropertyValue("policySource", new RuntimeBeanReference(policySourceBeanName));
- RootBeanDefinition advisorDefinition = new RootBeanDefinition(DefaultPointcutAdvisor.class);
- advisorDefinition.setSource(elementSource);
- advisorDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- advisorDefinition.getPropertyValues().addPropertyValue("advice", adviceDefinition);
- advisorDefinition.getPropertyValues().addPropertyValue("pointcut", pointcutDefinition);
- String advisorBeanName = getBeanName(MethodInvokeCacheInterceptor.class);
- parserContext.registerBeanComponent(new BeanComponentDefinition(advisorDefinition, advisorBeanName));
- if (log.isInfoEnabled()) {
- log.info("register internal advisor [" + advisorBeanName + "], advice ["
- + MethodInvokeCacheInterceptor.class.getName() + "] annotation [" + CacheEnable.class.getName() + "]");
- }
- }
- }
复制代码 于是我们的业务层就可以通过如下配置得到缓存的能力:
Java代码- public class TestServie implements ITestServie {
-
- @Override
- @CacheEnable(cacheName = "test.annotation", liveSeconds = 2)
- public String hello(String name) {
- return "Hello,Passyt";
- }
-
- } public class TestServie implements ITestServie {
- @Override
- @CacheEnable(cacheName = "test.annotation", liveSeconds = 2)
- public String hello(String name) {
- return "Hello,Passyt";
- }
- }
复制代码 至此,本文所要表达的目的已经全部实现,现在业务层可以通过三种配置方式得到缓存的能力。至于缓存如何实现,可以参考附件中提供的源码,其中包括几个测试Sample。
后话
以上任然遗留几个问题
(1)如何提供一种动态更新缓存过期的机制,如在系统运行中动态修改某个业务的缓存过期策略
(2)Cache的实现方式和Ehcache绑定过紧,基于内存的缓存实现可以参考google-collections中的MapMaker,但是它不提供空闲过期策略,需要做适当的扩展;同样基于Oscache的实现也不空闲过期策略
[ 本帖最后由 crazyit 于 2010-7-21 19:57 编辑 ] |
-
-
cache-source.zip
(23.88 KB)
售价: 疯狂金币 2 [记录]
[购买]
-
1
评分人数
-
|