论坛首页 Java企业应用论坛

aop cache再讨论

浏览 21683 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-12-18  
ispring 写道
为什么不把两个Annotation合并在一起写呢,比如:
@Target(ElementType.METHOD | ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface MethodCache {  
 
}


大哥,你有没看代码,一个拦截整个类,一个拦截指定方法....
0 请登录后投票
   发表时间:2008-12-18  
AOP CACHE  命中率会很低吧...每次调用参数不同的话,,就没什么用啦...

代理类的目标类的方法:
每一个代理出来的类.命名都是有规则的..
timeout.Test$$EnhancerByCGLIB$$ab0d57f7
这是CGLIB代理出来的话...$$符号前面是完整类路径....


代理类的目标类的方法,不知道你这句话是不是上面的意思
0 请登录后投票
   发表时间:2008-12-22  
ahuaxuan 写道
/**
*作者:张荣华
*日期:2008-11-07
**/

开门见山,一刀见血,让我们说说烦人的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的源代码,让各位见笑了.

0 请登录后投票
   发表时间:2008-12-22  
kabbesy 写道

更好的解决方案也是有的,就是固定Key+EL表达式结合param的动态key。Cache本身也应该居于namespace的概念。不要总想着什么根据method+param自动生成key,缓存最重要的就是key。一旦你把key隐藏了,也就意味着flush的时候无法定位到具体key了
bless


能详细讲讲你的动态key吗? 什么叫 定Key+EL表达式结合param的动态key

0 请登录后投票
   发表时间:2008-12-23  
写的很好,谢谢楼主。
0 请登录后投票
   发表时间:2009-05-30  
楼主你好,我现在系统中用的cache方式,和你最初的版本有点像,也是用类名+方法名+参数作为参数。
如果方法的参数是对象比如数组或者VO对象,这个时候我们是不应该cache的,因为他们每次调用的key都是不一样的,这样就有个问题,比如我数组的内容都一样,但是产生的key不一样,所以每次都cache,但cache本省又不被识别。造成无用的cache。不知这个问题楼主如何解决?
0 请登录后投票
   发表时间:2009-05-31  
camelh 写道

楼主你好,我现在系统中用的cache方式,和你最初的版本有点像,也是用类名+方法名+参数作为参数。
如果方法的参数是对象比如数组或者VO对象,这个时候我们是不应该cache的,因为他们每次调用的key都是不一样的,这样就有个问题,比如我数组的内容都一样,但是产生的key不一样,所以每次都cache,但cache本省又不被识别。造成无用的cache。不知这个问题楼主如何解决?

遇到这种问题我们可从cache的实现原来来寻求解决方案,比如说,cache本身可以看作是一个linkedhashmap,那么如何使内容相同的数组或者VO对象在map里被认为是同一个对象呢,那么就可以想到hashcode方法和equals方法等等,那么沿着这条路下去,就可以找到解决方案了,当然如果是数组的话,确实比较麻烦,得另寻出路,比如封装等,
0 请登录后投票
   发表时间:2009-05-31  
ahuaxuan 写道
camelh 写道

楼主你好,我现在系统中用的cache方式,和你最初的版本有点像,也是用类名+方法名+参数作为参数。
如果方法的参数是对象比如数组或者VO对象,这个时候我们是不应该cache的,因为他们每次调用的key都是不一样的,这样就有个问题,比如我数组的内容都一样,但是产生的key不一样,所以每次都cache,但cache本省又不被识别。造成无用的cache。不知这个问题楼主如何解决?

遇到这种问题我们可从cache的实现原来来寻求解决方案,比如说,cache本身可以看作是一个linkedhashmap,那么如何使内容相同的数组或者VO对象在map里被认为是同一个对象呢,那么就可以想到hashcode方法和equals方法等等,那么沿着这条路下去,就可以找到解决方案了,当然如果是数组的话,确实比较麻烦,得另寻出路,比如封装等,

对于数组,你说的封装时是什么呢?我们在做cache的时候,是不能修改原来的逻辑和方法的,只能在AOP方法中做处理。为了避免这种不必要的cache,我能不能通过配置annotation来实现呢?
1 请登录后投票
   发表时间:2009-06-01  
camelh 写道

为了避免这种不必要的cache,我能不能通过配置annotation来实现呢?
可以的,可以定义哪些方法需要被缓存,methodcache,或者你再改一下,该成哪些方法不需要缓存,原理清楚了,要怎么改其实问题都不大
0 请登录后投票
   发表时间:2009-06-02  
ahuaxuan 写道
camelh 写道

为了避免这种不必要的cache,我能不能通过配置annotation来实现呢?
可以的,可以定义哪些方法需要被缓存,methodcache,或者你再改一下,该成哪些方法不需要缓存,原理清楚了,要怎么改其实问题都不大

恩,我就是模仿你那个annotation写不需要cache. 测试后确实有效,多谢.
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics