业务场景是这样:想要做一个自定义注解@MethodCache,注解方法,功能是通过Aspect切片来自动缓存方法结果
@Target({ METHOD })
@Retention(RUNTIME)
public @interface MethodCache {
/**
* 过期时间 单位秒 默认60秒
*/
int expire() default 60;
}
Aspect类
@Aspect
@Configuration
public class MethodCacheAnnotationAspect {
// 日志
private Logger logger = LoggerFactory.getLogger(MethodCacheAnnotationAspect.class);
//redis
@Autowired
private RedisTemplate bbscache;
// around 建言 切入点为匹配注解了@MethodCache
@Around("@annotation(com.ewt360.bbsapi.service.component.MethodCache)")
public Object cacheProcess(ProceedingJoinPoint jp) throws Throwable {
Class<?> targetClz = jp.getTarget().getClass();
String methodName = jp.getSignature().getName();
if(!(jp.getSignature() instanceof MethodSignature)){
logger.warn("该方法接口无法启用缓存功能: {}", jp.getSignature().toLongString());
return jp.proceed();
}
MethodSignature methodSign = (MethodSignature)jp.getSignature();
MethodCache sc = methodSign.getMethod().getAnnotation(MethodCache.class);
if (sc == null)
return jp.proceed();
int expire = sc.expire() > 0 ? sc.expire() : 200;
// 组装缓存key
String cacheKey = buildCacheKey(targetClz, methodName, jp.getArgs());
logger.info("cacheInvoke =>{}",cacheKey);
Object rval = cacheInvoke(sc, methodSign, jp, cacheKey, expire);
return rval;
}
private String buildCacheKey(Class targetClz, String methodName, Object[] args){
return targetClz.getPackage()+methodName+ StringUtils.arrayToDelimitedString(args, ".");
}
private Object cacheInvoke(MethodCache sc, MethodSignature methodSign, ProceedingJoinPoint jp, String cacheKey, int expire) throws Throwable {
//得到方法的结果类型
Class returnClazz = methodSign.getReturnType();
Object result;
Object rval = bbscache.opsForValue().get(cacheKey);
if (rval == null) {
logger.info("miss from cache, load backend for key : {}", cacheKey);
result = jp.proceed();
if(result != null){
logger.info("cache to redis {},{}", cacheKey, JSON.toJSONString(result));
bbscache.opsForValue().set(cacheKey, result, expire, TimeUnit.SECONDS);
}
}
else{
if(ObjectUtils.isBaseType(returnClazz)){
result = rval;
}
else {
JSON.parseObject((String) rval, new TypeReference<List<Integer>>() {});
result = JSONObject.toJavaObject((JSON) rval, returnClazz);
}
logger.info("cache get object: {}", JSON.toJSONString(rval));
}
return result;
}
}
使用的时候只要在方法上加上注解就ok
@MethodCache()
public List<Integer> testInt() {
List<Integer> list = new ArrayList<>();
list.add(0);
list.add(1);
return list;
}
利用redis缓存方法执行结果,如果已缓存则将缓存转换为方法出参类型,正常的参数类型都OK的,可以得到方法的结果类型,但是如果是泛型类型的话,比如List和map, 却只能得到List,Map类型,并不能得到确切的泛型类型,比如
List<Map<String,String>>
List<User>
Map<String, User>
只能解出
List<JSONObject>
Map<Object, JSONObject>
因为泛型的类型擦除,所以很难做到,也想过在注解里面传入具体类型,但是也没办法把泛型类型传进来,这个要怎么做,希望大家提供思路!!
你需要加这个注解的方法,肯定已经确定数据类型了吧,你在切面获取到之后,切面里应该要用**instanceof**判断一下吧,得出结论再决定怎么放入缓存
遍历 List 或Map 中的元素, 用instanceof 判断 元素类型 , 再根据类型强转一下, 不就行啦
就是这个意思,既然你注解的方法肯定确定类型,比如你确定的有Student、Teacher、User几个具体的类型,那就用这些类型用instanceof去做判断不就知道你传过来的类型了? 如果涉及父类子类需要区分的,比如Person类是Student类父类的,也是可以的,示例代码:
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Student();//Student是Person子类
Student s1 = new Student();
//判断P1具体是什么类型 (说明是Person)
System.out.println(p1 instanceof Person);//true
System.out.println(p1 instanceof Student);//false
//判断P2具体是什么类型(说明是Student)
System.out.println(p2 instanceof Person);//true
System.out.println(p2 instanceof Student);//true
//判断s1具体类型(说明是Student)
System.out.println(s1 instanceof Person);//true
System.out.println(s1 instanceof Student);//true
}
}
如果是想获取到切面的返回值类型,那你可是要下一方功夫了。因为默认返回的就是Object
做起来貌似会很麻烦,要定义一切可能出现的类型,然后通过instance判断然后强转。
建议在方法内部处理结果进缓存,或者使用单独服务任务去刷新缓存信息。
因为我感觉让AOP去处理这感觉很不雅观。