spring如何在Aspect中获取切点注解方法的泛型参数类型

业务场景是这样:想要做一个自定义注解@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去处理这感觉很不雅观。