项目中基于@Aspect实现AOP,通过环绕增强(@Around)控制Dao的缓存(set/delete)。DAO接口及实现及AOP部分代码如下:
///
package cn.xxx.dao;
public interface FunModuleDao {
/**
* 更新用户视图布局
*
* @param userId 用户ID
* @param fmlList 布局对象列表
*/
public void updateFunModuleUsers(Long userId,List fmlList);
}
//
package cn.xxx.dao.impl;
@Repository
public class FunModuleDaoImpl implements FunModuleDao {
@Override
@CacheEvict(prefix = "userCache", suffix="0")
public void updateFunModuleUsers(Long userId,List fmlList){
代码略..
}
package cn.xxx.cache.aop;
@Component
@Aspect
public class CacheAop {
private static final Log log = LogFactory.getLog(CacheAop.class);
@Autowired
private SiteService siteService;
@Autowired
private MemKeyService memKeyService;
@Autowired
private MemCachedClient memCachedClient ;
@Around(value="@annotation(cn.xxx.cache.annotation.Cacheable)")
public Object cache(ProceedingJoinPoint call){
Object result = null;
Boolean cacheEnable = CustomizedPropertyPlaceholderConfigurer.getCacheEnabled();
//判断是否开启缓存
if(!cacheEnable){
try {
result= call.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
Method method=getMethod(call);
Cacheable cacheable=method.getAnnotation(cn.ac.ucas.sep.cache.annotation.Cacheable.class);
String fieldKey =parseKey(cacheable.suffix(),method,call.getArgs());
String prefix = cacheable.prefix();
String cacheKey = prefix+"_"+fieldKey;
result =memCachedClient.get(cacheKey);
if(null == result){
try {
result = call.proceed();
long expiration = cacheable.expiration();//1000*60*60*48==48小时过期
Date expirationTime=new Date(System.currentTimeMillis()+expiration);
memCachedClient.set(cacheKey, result,expirationTime));
} catch (Throwable e) {
e.printStackTrace();
}
}
return result;
}
/**
* 定义清除缓存逻辑
*/
@Around(value="@annotation(cn.xxx.cache.annotation.CacheEvict)")
public Object evict(ProceedingJoinPoint call){
代码略...
}
/**
* 获取被拦截方法对象
*
* MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
* 而缓存的注解在实现类的方法上
* 所以应该使用反射获取当前对象的方法对象
*/
public Method getMethod(ProceedingJoinPoint call){
//获取参数的类型
Object [] args=call.getArgs();
Class [] argTypes=new Class[call.getArgs().length];
for(int i=0;i<args.length;i++){
argTypes[i]=args[i].getClass();
}
Method method=null;
try {
method=call.getTarget().getClass().getMethod(call.getSignature().getName(),argTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return method;
}
/**
* 获取缓存的key
* key 定义在注解上,支持SPEL表达式
*/
private String parseKey(String key,Method method,Object [] args){
代码略...
}
}
运行调试过程中出现一个很诡异的问题,错误信息如下:
java.lang.NoSuchMethodException: cn.xxx.dao.impl.FunModuleDaoImpl.updateFunModuleUsers(java.lang.Long, java.util.ArrayList)
at java.lang.Class.getMethod(Class.java:1607)
at cn.ac.ucas.sep.cache.aop.CacheAop.getMethod(CacheAop.java:148)
//CacheAop.java:148即method=call.getTarget().getClass().getMethod(call.getSignature().getName(),argTypes);
跟踪执行过程最终找到原因:
通过ProceedingJoinPoint.getArgs()[1].getClass()获取的第二个参数类型(java.util.ArrayList)与call.getTarget().getClass().getMethod(...)中的类型(java.util.List)不匹配造成的。
解决办法:
修改Dao接口和实现的参数类型由List变为ArrayList
///
package cn.xxx.dao;
public interface FunModuleDao {
/**
* 更新用户视图布局
*
* @param userId 用户ID
* @param fmlList 布局对象列表
*/
public void updateFunModuleUsers(Long userId,ArrayList fmlList);
}
//
package cn.xxx.dao.impl;
@Repository
public class FunModuleDaoImpl implements FunModuleDao {
@Override
@CacheEvict(prefix = "userCache", suffix="0")
public void updateFunModuleUsers(Long userId,ArrayList fmlList){
代码略..
}
获取还有更好的办法,暂时先用上吧