aop cache再讨论

本文介绍了AOPCache的基本用法及其存在的问题,并提出了一种改进方案,通过自定义注解实现对方法缓存的精确控制,同时支持设置缓存过期时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开门见山,一刀见血,让我们说说烦人的aop cache.

aop cache解释使用aop技术的cache,可以cache被代理对象的方法返回结果,还可以通过方法的参数值来控制缓存的粒度,看上去很美,用的人估计也颇多,好东西啊,面试的时候经常有人告诉我"我用过aop cache",看来是居家必备啊.不过居家必备的东西也得升个级什么滴啊,就想汽车一样,每年拉一次皮,照卖,还自夸是新一袋.aop cache要升级得先看看它烦人得地方.看看它烦人得地方先得知道它得用法,那么就先简单介绍一下它得用法:

常见步骤,2步
1,建立一个拦截器类,环绕增强或者后增强都可以,代码如下:

 /** 
  * @author ahuaxuan(aaron zhang) 代码原主是一个老外,不是我 
  * @since 2008-5-13 
  * @version $Id: MethodCacheInterceptor.java 814 2008-05-13 06:52:54Z aaron $ 
  */  
 @Component("methodCacheInterceptor")  
 @GlobalAutowired//这个是俺写的globalautowired,大家可以忽略  
 public class MethodCacheInterceptor implements MethodInterceptor {  
   
     private Cache methodCache;  
   
     public void setMethodCache(Cache methodCache) {  
         this.methodCache = methodCache;  
     }  
   
     public Object invoke(MethodInvocation invocation) throws Throwable {  
         String targetName = invocation.getThis().getClass().getName();  
         String methodName = invocation.getMethod().getName();  
         Object[] arguments = invocation.getArguments();  
         Object result;  
   
         String cacheKey = getCacheKey(targetName, methodName, arguments);  
         Element element = methodCache.get(cacheKey);  
         if (element == null) {  
             result = invocation.proceed();  
   
             element = new Element(cacheKey, (Serializable) result);  
             methodCache.put(element);  
         }  
         return element.getValue();  
     }  
   
     private String getCacheKey(String targetName, String methodName,  
             Object[] arguments) {  
         StringBuffer sb = new StringBuffer();  
         sb.append(targetName).append(".").append(methodName);  
         if ((arguments != null) && (arguments.length != 0)) {  
             for (int i = 0; i < arguments.length; i++) {  
                 sb.append(".").append(arguments[i]);  
             }  
         }  
   
         return sb.toString();  
     }  
   
 }

 这段代码很简单,就是缓存某个方法的返回结果,使用的缓存组件是ehcache,ehcache的比较详细的用法ahuaxuan在http://www.iteye.com/topic/128458 这篇文章中已经有了说明.

 <!-- method cache auto proxy, add by ahuaxuan -->  
      <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
          <property name="beanNames">  
               <list>  
                    <value>aaComponent</value>  
                    <value>bbComponent</value>  
               </list>  
          </property>  
          <property name="interceptorNames">  
               <list>  
                    <value>methodCacheInterceptor</value>  
               </list>  
          </property>  
      </bean>

 Over,最简单的aop cache.使用了该aop cache之后,可以缓存方法返回结果于无形,又可以根据方法参数来控制缓存粒度, 实乃居家旅行,杀人越货的必备良药

那么接下来看看这个用法有没有什么问题,相信熟悉一点的童子一眼就看出来了:”糟了,aaComponent和bbComponent所有的方法都被拦截了”.这个代码着实让我焦虑,我很焦虑.

Ok,我改,我改正则表达式还不行吗,我可以通过正则表达式让某些特定方法名的方法才被拦截处理.好啊,正统的spring用法,于是 advice变成了advisor,增强变成了增强器, 但是我怎么看着就这么扭呢,难道我要缓存一个方法的结果还非得把这个方法的名字按照某个固定的格式来取, 再着,两个get方法,一个getxxx(),一个getyyy,两个之中一个需要缓存,另外一个不需要缓存(靠,真是变态),怎么办呢?正则的方式让我 很烦躁,非常烦躁.

第一种方法让我焦虑,而第二种方法让我烦躁,我应该去寻找解决焦虑和烦躁的方案.

写代码需要有灵感,也需要有很强的分析能力,我们来看看我的问题是什么:
问题重新描述:不能精确的控制某个对象的某个方法需要被缓存.
思考:如何固定这个方法的标示-------------------
hardcode方法名到methodinterceptor中
hardcode缓存标示到方法上(如果该类所有方法都需要被缓存,那么hardcode缓存标示到类上)

我选第二种.那么看看实现步骤:
1.annotation类,两个:

 @Target(ElementType.METHOD)  
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface MethodCache {  
   
 }

 还有一个:

 @Target(ElementType.TYPE)  
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface ObjectCache {  
   
 }

 看上去是多么无聊的两个annotation.

2修改methodinterceptor,加上判断逻辑
如果被代理的类加了ObjectCache,那么拦截这个对象所有的方法,如果没有类上没有加ObjectCache,那么判断method上有没有加methodcache,如果加了,拦截该方法,如果没有加,直接调用目标类的方法.

于是代码变成:

 public Object invoke(MethodInvocation invocation) throws Throwable {  
           
         String targetName = invocation.getThis().getClass().getInterfaces()[0].getName();  
         String methodName = invocation.getMethod().getName();  
         Object[] arguments = invocation.getArguments();  
           
         if (invocation.getThis().getClass().isAnnotationPresent(ObjectCache.class)) {  
             return getResult(targetName, methodName, arguments, invocation);  
         } else {  
             if (invocation.getMethod().isAnnotationPresent(MethodCache.class)) {  
                 return getResult(targetName, methodName, arguments, invocation);  
             } else {  
                 return invocation.proceed();  
             }  
         }  
     }  
   
 private Object getResult(String targetName, String methodName, Object[] arguments, MethodInvocation invocation) throws Throwable {  
         Object result;  
           
         String cacheKey = getCacheKey(targetName, methodName, arguments);  
         Element element = methodCache.get(cacheKey);  
         if (element == null) {  
             result = invocation.proceed();  
   
             element = new Element(cacheKey, (Serializable) result);  
             methodCache.put(element);  
         }  
           
         return element.getValue();  
     }

 Ok,试试把,现在我要拦截aaservice上所有的方法,那么我的代码如下:

 @ObjectCache  
 public class AaService  implement xxxxxx{  
   
 }

 如果我要拦截bbservice上的b1方法,代码如下:

 public class BbService implement xxxxxx{  
   
     @MethodCache  
     public void bb() {  
           
     }  
 }

 好了,目的达到了,我们可以任意的指定需要要拦截某个类的全部,或者部分方法了. 可是心中好像还是很闷的慌,我很慌张,非常慌张.
有人问了:都到这个份上了还慌啥张啊.
答:它tmd什么时候过期啊.我在ehcache.xml配置的可是统一的过期时间啊.ok,想到了,改,于是俺们的annotation就长成下面这个样子了:

 @Target(ElementType.METHOD)  
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface MethodCache {  
     int expire() default 0;  
 }

 和

 @Target(ElementType.TYPE)  
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface ObjectCache {  
     int expire() default 0;  
 }

 再看看我们的进化过的methodInterceptor吧,大家可以详细比较一下下面这段和上面两端代码的异同之处

 public Object invoke(MethodInvocation invocation) throws Throwable {  
           
         String targetName = invocation.getThis().getClass().getInterfaces()[0].getName();  
         String methodName = invocation.getMethod().getName();  
         Object[] arguments = invocation.getArguments();  
         Class[] cs = new Class[arguments.length];  
         for (int k = 0; k < arguments.length; k++) {  
             cs[k] = arguments[k].getClass();  
         }  
           
         if (invocation.getThis().getClass().getCanonicalName().contains("$Proxy")) {  
             if (logger.isWarnEnabled()) {  
                 logger.warn("----- The object has been proxyed and method " +  
                         "cache interceptor can't get the target, " +  
                         "so the method result can't be cached which name is ------" + methodName);  
             }  
               
             return invocation.proceed();  
         } else {  
             if (invocation.getThis().getClass().isAnnotationPresent(ObjectCache.class)) {  
                 ObjectCache oc = invocation.getThis().getClass().getAnnotation(ObjectCache.class);  
                 return getResult(targetName, methodName, arguments, invocation, oc.expire());  
             } else {  
                   
                 Method[] mss = invocation.getThis().getClass().getMethods();  
                 Method ms = null;  
                 for (Method m : mss) {  
                     if (m.getName().equals(methodName)) {  
                         boolean argMatch = true;  
                         Class[] tmpCs = m.getParameterTypes();  
                         if (tmpCs.length != cs.length) {  
                             argMatch = false;  
                             continue;  
                         }  
                         for (int k = 0; k < cs.length; k++) {  
                             if (!cs[k].equals(tmpCs[k])) {  
                                 argMatch = false;  
                                 break;  
                             }  
                         }  
                           
                         if (argMatch) {  
                             ms = m;  
                             break;  
                         }  
                     }  
                 }  
                   
                 if (ms != null && ms.isAnnotationPresent(MethodCache.class)) {  
                     MethodCache mc = ms.getAnnotation(MethodCache.class);  
                     return getResult(targetName, methodName, arguments, invocation, mc.expire());  
                 } else {  
                     return invocation.proceed();  
                 }  
             }  
         }  
     }  
       
     private Object getResult(String targetName, String methodName, Object[] arguments,  
             MethodInvocation invocation, int expire) throws Throwable {  
         Object result;  
           
         String cacheKey = getCacheKey(targetName, methodName, arguments);  
         Element element = methodCache.get(cacheKey);  
         if (element == null) {  
             synchronized (this) {  
                 element = methodCache.get(cacheKey);  
                 if (element == null) {  
                     result = invocation.proceed();  
       
                     element = new Element(cacheKey, (Serializable) result);  
                       
                     //annotation没有设expire值则使用ehcache.xml中自定义值  
                     if (expire > 0) {  
                         element.setTimeToIdle(expire);  
                         element.setTimeToLive(expire);  
                     }  
                     methodCache.put(element);  
                 }  
             }  
         }  
           
         return element.getValue();  
     }

 童子们可以看到invoke方法加了一些判断(比如说类名中是否含有$Proxy),主要是防止越来越多的代理层次,如果被 methodcacheinterceptor拦截到的类是一个代理类,那么ahuaxuan暂时还没有找到可以得到该代理类的目标类的方法(望知情者告 之,不甚感激).

好了,好像可以告一段落了,因为现在既可以指定缓存某个类所有方法的返回结果,也可以只缓存某个类的某些方法的结果,而且还可以指定某个方法的结果被缓存多长的时间.嗯.
有童子说了:”等等,还有一个需求,我一个类中只有一个方法不需要缓存结果,其他都要缓存结果,怎么办?”
答:别烦了好吗,你就不能自己写一个@MethodNoCache吗,和@ObjectCache联合使用不就解决问题了吗.

文章最后,附上ahuaxuan的源代码,让各位见笑了.

[转自 :http://www.iteye.com/topic/263895 ]

Norther评论:

写的很好,我以前也有类似的实现,目的都一样,但是还有改进的余地,我是加一个spring tag,<method-cache:annotation-driven />,类似<tx:annotation-driven /> 的原理,不用配那个拦截器,在那个tag的BeanDefinitionParser里,注册一个Advisor和你那个拦截器,那个Advisor可以 cut带你那两个annotation的方法或者类,这样不是更爽。 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值