aop cache再讨论

  1. 云栖社区>
  2. 博客>
  3. 正文

aop cache再讨论

shy丶gril 2016-03-28 10:50:55 浏览1287
展开阅读全文
/**
*作者:张荣华
*日期:2008-11-07
**/

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

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

常见步骤,2步
1,建立一个拦截器类,环绕增强或者后增强都可以,代码如下:
Java代码 复制代码 收藏代码
  1. /**
  2. * @author ahuaxuan(aaron zhang) 代码原主是一个老外,不是我
  3. * @since 2008-5-13
  4. * @version $Id: MethodCacheInterceptor.java 814 2008-05-13 06:52:54Z aaron $
  5. */
  6. @Component("methodCacheInterceptor")
  7. @GlobalAutowired//这个是俺写的globalautowired,大家可以忽略
  8. public class MethodCacheInterceptor implements MethodInterceptor {
  9. private Cache methodCache;
  10. public void setMethodCache(Cache methodCache) {
  11. this.methodCache = methodCache;
  12. }
  13. public Object invoke(MethodInvocation invocation) throws Throwable {
  14. String targetName = invocation.getThis().getClass().getName();
  15. String methodName = invocation.getMethod().getName();
  16. Object[] arguments = invocation.getArguments();
  17. Object result;
  18. String cacheKey = getCacheKey(targetName, methodName, arguments);
  19. Element element = methodCache.get(cacheKey);
  20. if (element == null) {
  21. result = invocation.proceed();
  22. element = new Element(cacheKey, (Serializable) result);
  23. methodCache.put(element);
  24. }
  25. return element.getValue();
  26. }
  27. private String getCacheKey(String targetName, String methodName,
  28. Object[] arguments) {
  29. StringBuffer sb = new StringBuffer();
  30. sb.append(targetName).append(".").append(methodName);
  31. if ((arguments != null) && (arguments.length != 0)) {
  32. for (int i = 0; i < arguments.length; i++) {
  33. sb.append(".").append(arguments[i]);
  34. }
  35. }
  36. return sb.toString();
  37. }
  38. }
/**
 * @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这篇文章中已经有了说明.

而配置自动代理:
Java代码 复制代码 收藏代码
  1. <!-- method cache auto proxy, add by ahuaxuan -->
  2. <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
  3. <property name="beanNames">
  4. <list>
  5. <value>aaComponent</value>
  6. <value>bbComponent</value>
  7. </list>
  8. </property>
  9. <property name="interceptorNames">
  10. <list>
  11. <value>methodCacheInterceptor</value>
  12. </list>
  13. </property>
  14. </bean>
<!-- 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类,两个:
Java代码 复制代码 收藏代码
  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface MethodCache {
  4. }
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodCache {

}
还有一个:
Java代码 复制代码 收藏代码
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface ObjectCache {
  4. }
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ObjectCache {

}

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

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

于是代码变成:
Java代码 复制代码 收藏代码
  1. public Object invoke(MethodInvocation invocation) throws Throwable {
  2. String targetName = invocation.getThis().getClass().getInterfaces()[0].getName();
  3. String methodName = invocation.getMethod().getName();
  4. Object[] arguments = invocation.getArguments();
  5. if (invocation.getThis().getClass().isAnnotationPresent(ObjectCache.class)) {
  6. return getResult(targetName, methodName, arguments, invocation);
  7. } else {
  8. if (invocation.getMethod().isAnnotationPresent(MethodCache.class)) {
  9. return getResult(targetName, methodName, arguments, invocation);
  10. } else {
  11. return invocation.proceed();
  12. }
  13. }
  14. }
  15. private Object getResult(String targetName, String methodName, Object[] arguments, MethodInvocation invocation) throws Throwable {
  16. Object result;
  17. String cacheKey = getCacheKey(targetName, methodName, arguments);
  18. Element element = methodCache.get(cacheKey);
  19. if (element == null) {
  20. result = invocation.proceed();
  21. element = new Element(cacheKey, (Serializable) result);
  22. methodCache.put(element);
  23. }
  24. return element.getValue();
  25. }
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上所有的方法,那么我的代码如下:
Java代码 复制代码 收藏代码
  1. @ObjectCache
  2. public class AaService implement xxxxxx{
  3. }
@ObjectCache
public class AaService  implement xxxxxx{

}


如果我要拦截bbservice上的b1方法,代码如下:
Java代码 复制代码 收藏代码
  1. public class BbService implement xxxxxx{
  2. @MethodCache
  3. public void bb() {
  4. }
  5. }
public class BbService implement xxxxxx{

	@MethodCache
	public void bb() {
		
	}
}


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

Java代码 复制代码 收藏代码
  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface MethodCache {
  4. int expire() default 0;
  5. }
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodCache {
	int expire() default 0;
}



Java代码 复制代码 收藏代码
  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface ObjectCache {
  4. int expire() default 0;
  5. }
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ObjectCache {
	int expire() default 0;
}


再看看我们的进化过的methodInterceptor吧,大家可以详细比较一下下面这段和上面两端代码的异同之处
Java代码 复制代码 收藏代码
  1. public Object invoke(MethodInvocation invocation) throws Throwable {
  2. String targetName = invocation.getThis().getClass().getInterfaces()[0].getName();
  3. String methodName = invocation.getMethod().getName();
  4. Object[] arguments = invocation.getArguments();
  5. Class[] cs = new Class[arguments.length];
  6. for (int k = 0; k < arguments.length; k++) {
  7. cs[k] = arguments[k].getClass();
  8. }
  9. if (invocation.getThis().getClass().getCanonicalName().contains("$Proxy")) {
  10. if (logger.isWarnEnabled()) {
  11. logger.warn("----- The object has been proxyed and method " +
  12. "cache interceptor can't get the target, " +
  13. "so the method result can't be cached which name is ------" + methodName);
  14. }
  15. return invocation.proceed();
  16. } else {
  17. if (invocation.getThis().getClass().isAnnotationPresent(ObjectCache.class)) {
  18. ObjectCache oc = invocation.getThis().getClass().getAnnotation(ObjectCache.class);
  19. return getResult(targetName, methodName, arguments, invocation, oc.expire());
  20. } else {
  21. Method[] mss = invocation.getThis().getClass().getMethods();
  22. Method ms = null;
  23. for (Method m : mss) {
  24. if (m.getName().equals(methodName)) {
  25. boolean argMatch = true;
  26. Class[] tmpCs = m.getParameterTypes();
  27. if (tmpCs.length != cs.length) {
  28. argMatch = false;
  29. continue;
  30. }
  31. for (int k = 0; k < cs.length; k++) {
  32. if (!cs[k].equals(tmpCs[k])) {
  33. argMatch = false;
  34. break;
  35. }
  36. }
  37. if (argMatch) {
  38. ms = m;
  39. break;
  40. }
  41. }
  42. }
  43. if (ms != null && ms.isAnnotationPresent(MethodCache.class)) {
  44. MethodCache mc = ms.getAnnotation(MethodCache.class);
  45. return getResult(targetName, methodName, arguments, invocation, mc.expire());
  46. } else {
  47. return invocation.proceed();
  48. }
  49. }
  50. }
  51. }
  52. private Object getResult(String targetName, String methodName, Object[] arguments,
  53. MethodInvocation invocation, int expire) throws Throwable {
  54. Object result;
  55. String cacheKey = getCacheKey(targetName, methodName, arguments);
  56. Element element = methodCache.get(cacheKey);
  57. if (element == null) {
  58. synchronized (this) {
  59. element = methodCache.get(cacheKey);
  60. if (element == null) {
  61. result = invocation.proceed();
  62. element = new Element(cacheKey, (Serializable) result);
  63. //annotation没有设expire值则使用ehcache.xml中自定义值
  64. if (expire > 0) {
  65. element.setTimeToIdle(expire);
  66. element.setTimeToLive(expire);
  67. }
  68. methodCache.put(element);
  69. }
  70. }
  71. }
  72. return element.getValue();
  73. }
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联合使用不就解决问题了吗.

网友评论

登录后评论
0/500
评论