怎么破?
问题已经定位了,我从来不缺解决方案。下面我给小伙伴们介绍三种,任君选择
方案一:使用a0/p0的方式去对方法入参进行引用
说了很多次了,key
中使用SpEL表达式,即可用字段名,也可以用a0/p0
这种按照顺序的方式去获取,形如这样:
@Cacheable(cacheNames = "demoCache", key = "#a0")
运行一把试试,终于一切正常,并且缓存也生效了:
----------验证缓存是否生效---------- org.springframework.cache.concurrent.ConcurrentMapCache@709ed6f3 User(id=1, name=fsx, age=21)
这种方案使用起来相对非常简单(把控好参数顺序),并且得到了源生支持无需额外开发,所以推荐使用~
方案二:自定义注解 + KeyGenerator
从之前的源码分析知道,如果自己不指定key这个属性,会交给KeyGenerator去自动生成,此方案就是以这个原理为理论基础实现的。
但是,难道需要给每个方法都定一个个性化的KeyGenerator来解决???
当然这样也是一种解决方案,但是也太麻烦了,现在此方案不可能落地的。所以本文需要结合一个自定义注解,绕开key这个属性然后加上一个**通用的KeyGenerator**来解决问题,下面我直接给出示例代码:
1、自定义一个自己的注解,绕过key,提供一个新属性mykey
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Cacheable public @interface MyCacheable { @AliasFor("cacheNames") String[] value() default {}; @AliasFor("value") String[] cacheNames() default {}; String key() default ""; String keyGenerator() default ""; String cacheManager() default ""; String cacheResolver() default ""; String condition() default ""; String unless() default ""; boolean sync() default false; // 自定义的属性,代替key属性(请不要使用key属性了) String myKey() default ""; }
2、准备一个通用的KeyGenerator来可以处理自定义注解的myKey属性:
@EnableCaching // 使用了CacheManager,别忘了开启它 否则无效 @Configuration public class CacheConfig extends CachingConfigurerSupport { @Bean(name = "myMethodParamKeyGenerator") public KeyGenerator myMethodParamKeyGenerator() { return (target, method, params) -> { //获得注解 MyCacheable myCacheable = AnnotationUtils.findAnnotation(method, MyCacheable.class); if (myCacheable != null) { String myKey = myCacheable.myKey(); if (myKey != null && StringUtils.hasText(myKey)) { //获取方法的参数集合 Parameter[] parameters = method.getParameters(); StandardEvaluationContext context = new StandardEvaluationContext(); //遍历参数,以参数名和参数对应的值为组合,放入StandardEvaluationContext中 // 注意:若没有java8的编译参数-parameters,参数名都回事arg0,arg1... 若有参数就是具体的参数名了 for (int i = 0; i < parameters.length; i++) { context.setVariable(parameters[i].getName(), params[i]); } ExpressionParser parser = new SpelExpressionParser(); //根据newKey来解析获得对应值 Expression expression = parser.parseExpression(myKey); return expression.getValue(context, String.class); } } return params[0].toString(); }; } ... }
3、把新注解使用在我们的Mapper接口上:
// @Repository //备注:这个注解是没有必要的 因为已经被@MapperScan扫进去了 public interface CacheDemoMapper { @Select("select * from user where id = #{id}") @MyCacheable(cacheNames = "demoCache", /*key = "#a0"*/ keyGenerator = "myMethodParamKeyGenerator", myKey = "#arg0") User getUserById(Integer id); }
运行如上测试用例:缓存生效,正常work
此方案我个人也是比较推荐的,不仅仅能解决问题,而且还能达到炫技的效果。(不要说炫技无用,炫技从另外一方面能反映出你的实力,领导才能看中你嘛~~~当然,在生产环境下不要过度炫技)
方案三:开启Java8的-parameters 编译参数
方案二有个弊端,就是只能使用arg0、arg1这种方式引用到入参的值,使用起来不是特别的方便。原因是Java编译器在编译的时候就已经把Method的形参变量名抹去了。若想保留这个值,Java8提供了-parameters编译参数来实现:
此处处理编译以Idea为例,若是用maven编译的话,方式雷同。
步骤:setting-Java Compiler(如下图):
加入此参数后,编译后再运行。这时,你的myKey就只能这么写了:myKey = "#id"(#变量名x形式) 一切正常~
此种方其实我是并不推荐的,因为它还得强依赖于编译参数,有这种强依赖还是不太好
总结
虽然说程序员最重要的技能是ctrl c加ctrl v。拿来主义固然是好,但是我建议还是得活出差异化,否则怎么脱颖而出呢?因此我是支持狂造你的代码,只有你的花招多了,你才是那个特别的你。
熟悉我写博文的小伙伴应该知道,我很少介绍一种技术的基本使用,而是注重乱造代码,因为我还是比较注重分享稍微高质量一些的知识,也希望前行的路上有你的支持和鼓励