`
ahuaxuan
  • 浏览: 639669 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

aop cache再讨论

阅读更多
/**
*作者:张荣华
*日期: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的源代码,让各位见笑了.
分享到:
评论
23 楼 everlasting_188 2008-11-14  
个人观点:只不过是一种解决方法吧,其实大的系统要优化核心的频率最大的指令,全部都cache的概念听起来不错,但是不太好用,容易导致oom。我们现在的项目使用的是memorycache,不影响jvm的稳定性,出现问题容易定位。一种方法对一类问题是比较适合的,企业架构和互联网架构,电信架构是不一样的,关键看需求。
22 楼 zhongnanhai.8 2008-11-12  
软件开发中,没有最好,只有最合理... 谢谢
21 楼 sorehead 2008-11-12  
学习学习!
20 楼 yangdefeng95802 2008-11-12  
还不如用JCS!
19 楼 shenrd666888 2008-11-12  
好文章, spring确实强大
18 楼 kjj 2008-11-11  
不过用那么多annotations 我看着木木的,还是纯代码看着干净啊
17 楼 kjj 2008-11-11  
可能是spring的cache aop 我没看懂,觉得嘿嘿的,我还是自己用aspectj 写 增强,感觉到踏实啊
16 楼 ahuaxuan 2008-11-10  
发现代码有点小问题,
invocation.getThis().getClass().getMethod(methodName, classtypes)
这个方法在调用的时候,如果遇到参数是某个接口的具体类型,而方法定义的时候定义的是接口,这样,getMethod就会抛找不到方法的异常.

所以代码换成这样了

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();
				}
15 楼 bulargy 2008-11-10  
不错不错,我们目前的aop cache正好可以修改修改~~~
14 楼 maming2000 2008-11-09  
针对mycybyb所讲的数据库被修改的情况缓存应该失效,这是因为具体逻辑只有自己的领域问题才知道,一个通用的缓存是解决不了的。通用的话,要么就在更改动作发生时缓存全体失效以保证正确性,因为通用缓存不知道它到底还关联了其它什么对象,要么就不用缓存。
只有对业务进行深入了解,然后自己手工操控缓存,拿spring注入一个ehcache实例,其它的什么时间存什么时间失效都要自己控制,这个拿着aop的话应该不是问题,问题是很多人都想要个省事的通用缓存。
13 楼 sorphi 2008-11-08  
俺只就annotation来提的建议:

@Cache(key="user_{1}_{2}",group="user")
public User findUser(param1,param2);



@Flush(group="user")
public void updateUser();
12 楼 liucl_tiger 2008-11-08  
我是一个初学者,可不可以多加一点注释呢?最好是一行一个注释!
11 楼 Joo 2008-11-07  
看了你之前的有关主页做缓存的说法,想知道一个动态主页如何做缓存呢?我的主页上基本上是一个以当前登录用户为条件收集相关信息的report review页面,比如当前用户待审核item数目,新消息数等,每个人登录以后都不一样,这个如何缓存?
10 楼 liuzongan 2008-11-07  
写的不错,功力有所提升,继续支持
9 楼 downpour 2008-11-07  
Norther 写道
写的很好,我以前也有类似的实现,目的都一样,但是还有改进的余地,我是加一个spring tag,<method-cache:annotation-driven />,类似<tx:annotation-driven /> 的原理,不用配那个拦截器,在那个tag的BeanDefinitionParser里,注册一个Advisor和你那个拦截器,那个Advisor可以cut带你那两个annotation的方法或者类,这样不是更爽。


爽是爽,不过貌似如果这样的话,更加抬高了别人学习的台阶啦。
8 楼 ahuaxuan 2008-11-07  
ray_linn 写道
methodCacheInterceptor这个似乎在哪里看过代码

看页面右上角,或者google一下,到处都是

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

这个主意不错,呵呵
7 楼 Norther 2008-11-07  
写的很好,我以前也有类似的实现,目的都一样,但是还有改进的余地,我是加一个spring tag,<method-cache:annotation-driven />,类似<tx:annotation-driven /> 的原理,不用配那个拦截器,在那个tag的BeanDefinitionParser里,注册一个Advisor和你那个拦截器,那个Advisor可以cut带你那两个annotation的方法或者类,这样不是更爽。
6 楼 ray_linn 2008-11-07  
methodCacheInterceptor这个似乎在哪里看过代码
5 楼 taupo 2008-11-07  
ahuaxuan的帖子一定是要顶的

我们的项目中也是用这种方法做缓存
4 楼 ahuaxuan 2008-11-07  
既然兄弟们都说了两句,俺也不能闷着,也凑两句:

不管什么东西,使用它们的时候都有一定的场景,小到组件,再大点到框架,再上升到语言,它们都有各自的优缺点以及适应的场景,项目管理上没有银弹,技术上同样没有,俺们要做的是找到合适的锤子来敲我们手里的钉子.

等哪天俺们哪天不但能从组件或者框架,还能从语言(再从语言到其相应的框架和组件),操作系统,和硬件上来判断我们应该使用哪些,并且能熟练使用+深刻理解其内部原理的时候,俺们就都成架构师了.

Aop cache这把锤子的用途我相信肯定是有的,但是是不是合适大家各自的钉子我就不知道了,所以等要用的时候再拿出来用好了,不过今天先看看这把锤子长什么样也无伤大雅.

引用

如果要系统的讨论缓存问题,请看一系列的文章:
http://www.iteye.com/topic/345693

相关推荐

    SpringBoot AOP各种注解、自定义注解、鉴权使用案例(免费下载)

    public Object cache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable { // 缓存逻辑... } } ``` 最后,我们讨论一下鉴权。在SpringBoot中,权限验证可以通过AOP来实现,例如使用Spring ...

    SpringAOP结合ehCache实现简单缓存实例

    接下来,我们将讨论如何整合Spring AOP和EhCache来创建一个简单的缓存实例: 1. **引入依赖**:首先,你需要在项目的pom.xml文件中添加Spring AOP和EhCache的相关依赖。确保引入最新版本的Spring Framework和...

    SSM与memcached整合项目Spring Cache

    下面我们将详细讨论这个整合过程中的关键知识点。 首先,让我们了解Spring Cache。Spring Cache提供了一个统一的API,用于在应用中添加缓存功能,无论你选择哪种缓存提供商。它通过AOP(面向切面编程)来实现,可以...

    给DAL层加上Cache(张宁).pdf

    在本文中,我们将讨论如何给DAL层加上Cache,以提高系统的性能和减少数据库访问次数。我们将通过一个实例来说明如何实现缓存机制,并讨论使用AOP(Aspect-Oriented Programming)框架来解耦合数据库访问和缓存逻辑。...

    AutoFac+缓存+Redis

    接下来,我们讨论如何将 Redis 集成到 AutoFac 中。为了实现缓存功能,我们需要一个支持 Redis 的缓存提供者。可以使用 StackExchange.Redis 这个库,它是官方推荐的 .NET 客户端,提供了丰富的 API 来操作 Redis ...

    SpringBoot 仿牛客网讨论社区项目

    在实现这些功能时,开发者会运用到Spring Boot的各种特性,如AOP(面向切面编程)进行日志记录,Spring Data JPA或MyBatis进行数据操作,Spring Cache实现缓存,以及Spring Boot Actuator监控应用健康状态等。...

    spring-cache-redis

    对于Spring Cache,我们需要添加`spring-context`、`spring-aop`和`spring-expression`等核心模块,以及`spring-data-redis`模块来支持Redis。同时,还需要引入Redis客户端Jedis或Lettuce的依赖。 ```xml &lt;!-- ...

    Java缓存讨论.pdf

    TreeCache模块提供树形结构的缓存,而TreeCacheAOP则利用面向切面编程(AOP)对POJO进行动态管理。 OSCache是由OpenSymphony开发的高性能J2EE缓存框架,它允许开发者在JSP页面内直接实现内存缓冲。OSCache的特点包括...

    jbosscache manual

    - **事务处理**:讨论 PojoCache 如何支持事务,包括事务的提交与回滚机制。 #### 3. 配置 为了使用 JBoss Cache,首先需要下载 JBoss Cache 3.x 发行版。可以从官方下载页面 ...

    spring支持ehcache

    【描述】"使用spring对缓存ehcache进行管理"表明我们将讨论如何通过Spring框架来配置和控制Ehcache的使用。Spring 提供了 Cache Abstraction,使得集成各种缓存系统变得简单,其中包括Ehcache。 首先,要使用Spring...

    Spring和emcache整合demo

    我们将讨论以下几个核心知识点: 1. **Spring框架**:Spring是一个开源的Java平台,它简化了Java企业级应用程序的开发。它提供了依赖注入(DI)、面向切面编程(AOP)以及一系列的模块,如数据访问、Web MVC、测试...

    spring+springmvc

    10. **优化**:性能优化包括使用缓存技术(如Spring Cache或Redis),调整数据库连接池配置,以及利用Spring AOP进行事务管理优化等。 总之,SSM是一个强大的Java Web开发组合,它提供了完整的功能,从控制层到持久...

    Spring-MYBatis企业应用实战-有详细目录

    例如,利用Spring的AOP特性实现全局事务管理,或者通过MyBatis的Cache接口自定义缓存机制。此外,对于性能优化,如批处理SQL、减少数据库连接开销等方面也会有所涉及。 最后,可能还会介绍如何将Spring-MYBatis应用...

    hands-high-performance-spring-5

    本内容将围绕Spring 5的关键特性和性能优化策略展开讨论。 1. **Spring 5新特性** - **WebFlux**: 引入了响应式编程模型,支持非阻塞I/O,提高服务器处理高并发的能力。 - **WebSocket支持增强**: 提供更好的实时...

    java面试宝典

    24. **Cache技术**:Ehcache和Memcached的原理及使用。 25. **SQL优化**:涉及查询优化、索引的原理和使用。 26. **Oracle数据库**:理解rownum和rowid的区别,以及分页查询的方法。 27. **执行计划分析**:学习...

    Java_Web整合开发王者归来_11

    这部分可能讨论了如何通过缓存技术(如Spring Cache或Redis)、负载均衡、数据库优化、代码优化等手段来提升系统的响应速度和并发能力。 另外,文件名中的"00011.pdf"表明这可能是系列教程的第11章或者第11部分,...

    Hibernate程序高手秘笈.part01-03.rar

    10. 扩展与高级主题:涉及Hibernate的事件监听、拦截器、动态模型、批量操作和CGLIB/AOP集成等内容,帮助读者掌握更高级的Hibernate技巧。 11. 实战案例:书中可能会提供一些实际项目案例,让读者将所学知识应用于...

    Java工具包Hutool Wiki PDF版

    1. hutool-aop:封装了JDK动态代理,提供了非依赖于IOC容器的面向切面编程(AOP)功能。 2. hutool-bloomFilter:实现了布隆过滤器,用于快速地判断元素是否存在集合中。 3. hutool-cache:提供了简单易用的缓存功能...

    (毕业设计)基于SSM的校园论坛系统的设计与实现.zip

    2. 论坛板块:设计不同的讨论区,用户可以根据兴趣选择参与讨论。可以使用Spring Data JPA来操作数据库,方便查询和更新板块信息。 3. 主题与帖子:用户可以发布新主题或在已有的主题下发表回复。需要考虑帖子的...

    java游戏论坛 毕业设计

    它提供了一个AOP(面向切面编程)框架,允许在不修改代码的情况下进行功能增强。此外,Spring还支持JDBC抽象层和事务管理,降低了数据库操作的复杂性。 2. **Struts框架**:Struts作为MVC(模型-视图-控制器)架构...

Global site tag (gtag.js) - Google Analytics