写点什么

基于 javaPoet 的缓存 key 优化实践

  • 2025-01-14
    北京
  • 本文字数:14902 字

    阅读完需:约 49 分钟

作者:京东物流 方志民

一. 背景

在一次系统 opsreview 中,发现了一些服务配置了 @Cacheable 注解。@cacheable 来源于 spring cache 框架中,作用是使用 aop 的方式将数据库中的热数据缓存在 redis/本地缓存中,代码如下:


@Cacheable(value = { "per" },  key="#person.getId()"+"_"+"#person.getName()")public Person getByIsbn(Person person) {    return personMapper.getPerson(person);}
复制代码


那么这个原生 spring 组件是如何工作的?redis 的 key 是如何产生的?这一过程是否还有优化的空间?带着这些问题我们来开启源码之旅。

二. Spring@Cacheable 注解工作原理

就以项目中使用的 spring3.2.18 版本为例分析,代码中使用了 xml+cache 标签的形式去启动注解缓存。然而在 springboot 中使用的是 @EnableCaching 注解,通过自动配置加载相关组件,两种方式都是殊途同归,这里就不做赘述了,直接上链接。


首先,如果我们想使用这个组件就需要先启用缓存注解,方式与 aop 功能相类似,aop 也会加载 internalAutoProxyCreator 后置处理器。代码中通过 annotation-driven 标签加载相关组件。其中 proxy-target-class="true" 表示使用 CGLIB 的方式对 bean 进行动态代理。


//<!-- 添加缓存注解支持 --><cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/>
<!-- 开启aop切面 --> <aop:aspectj-autoproxy proxy-target-class="true"/>
复制代码


代码中 cache-manager 表示需要依赖一个缓存管理器,它的作用是提供一种机制来缓存数据,以便在后续的访问中可以更快地获取数据。它可以支持 caffine,encache,Jcache 等多种类型的缓存管理器。文中是使用的自定义管理来支持公司内部的 redis 客户端。



//redis缓存管理器public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
private Collection<? extends Cache> caches;

public void setCaches(List<Cache> caches) { this.caches = caches; }
@Override protected Collection<? extends Cache> loadCaches() { if (caches == null) { return Collections.emptyList(); } return caches; }
@Override public Cache getCache(String name) { Cache cache = super.getCache(name); if (cache == null && (cache = super.getCache("DEFAULT")) == null) { throw new NullPointerException(); } return cache; }
}
复制代码


下面通过 bean 的方式注入 cacheManager 管理器,其中 MyCache 需要实现 org.springframework.cache.Cache 中定义的方法,以达到手动 diy 缓存操作的目的。



<bean id="cacheManager" class="com.xx.xx.RedisCacheManager"> <property name="transactionAware" value="true"/> <property name="caches"> <list> <bean class="com.xx.xx.MyCache"/> </list> </property> </bean>
复制代码


Cache 接口中有 get,put,evict 等方法,可以按需替换成自己想要的操作。


public interface Cache {    String getName();
Object getNativeCache();
Cache.ValueWrapper get(Object var1);
void put(Object var1, Object var2);
void evict(Object var1);
void clear();
public interface ValueWrapper { Object get(); }}
复制代码


配置输出完了,开始切入正题。spring 容器启动时候会解析 annotation-driven 标签,具体的实现在 CacheNamespaceHandler 中。显然可以发现 beanDefinition 解析类是 AnnotationDrivenCacheBeanDefinitionParser。


public class CacheNamespaceHandler extends NamespaceHandlerSupport {    static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager";    static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager";
public CacheNamespaceHandler() { }
static String extractCacheManager(Element element) { return element.hasAttribute("cache-manager") ? element.getAttribute("cache-manager") : "cacheManager"; }
static BeanDefinition parseKeyGenerator(Element element, BeanDefinition def) { String name = element.getAttribute("key-generator"); if (StringUtils.hasText(name)) { def.getPropertyValues().add("keyGenerator", new RuntimeBeanReference(name.trim())); }
return def; }
public void init() { this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser()); this.registerBeanDefinitionParser("advice", new CacheAdviceParser()); }}
复制代码


AnnotationDrivenCacheBeanDefinitionParser 中会先判断生成切面的方式,默认使用 spring 原生 aop,也可以通过 mode 标签切换成 AspectJ。


public BeanDefinition parse(Element element, ParserContext parserContext) {        String mode = element.getAttribute("mode");        if ("aspectj".equals(mode)) {            this.registerCacheAspect(element, parserContext);        } else {            AnnotationDrivenCacheBeanDefinitionParser.AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);        }
return null; }
复制代码


往下走会到达 configureAutoProxyCreator 方法,configureAutoProxyCreator 方法的作用是配置自动代理创建器。代码很多继续往下看~


public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {            AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);            if (!parserContext.getRegistry().containsBeanDefinition("org.springframework.cache.config.internalCacheAdvisor")) {                Object eleSource = parserContext.extractSource(element);                RootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.cache.annotation.AnnotationCacheOperationSource");                sourceDef.setSource(eleSource);                sourceDef.setRole(2);                String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);                RootBeanDefinition interceptorDef = new RootBeanDefinition(CacheInterceptor.class);                interceptorDef.setSource(eleSource);                interceptorDef.setRole(2);                AnnotationDrivenCacheBeanDefinitionParser.parseCacheManagerProperty(element, interceptorDef);                CacheNamespaceHandler.parseKeyGenerator(element, interceptorDef);                interceptorDef.getPropertyValues().add("cacheOperationSources", new RuntimeBeanReference(sourceName));                String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);                RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheOperationSourceAdvisor.class);                advisorDef.setSource(eleSource);                advisorDef.setRole(2);                advisorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));                advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);                if (element.hasAttribute("order")) {                    advisorDef.getPropertyValues().add("order", element.getAttribute("order"));                }
parserContext.getRegistry().registerBeanDefinition("org.springframework.cache.config.internalCacheAdvisor", advisorDef); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName)); compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName)); compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, "org.springframework.cache.config.internalCacheAdvisor")); parserContext.registerComponent(compositeDef); }
}
复制代码


AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element)作用是注册动态代理创建器。跳转两次到达这个 registerOrEscalateApcAsRequired 方法,它会检查是否存在 org.springframework.aop.config.internalAutoProxyCreator 的 beanDefinition。


大概意思就是检查此前是否还有其他的代理比如 aop 代理,它也会加载 internalAutoProxyCreator 这个后置处理器。如果已经加载过 internalAutoProxyCreator,则根据自动代理创建器的优先级判断,使用优先级高者。然后返回 internalAutoProxyCreator 的 beanDefinition。


private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");        if (registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator")) {            BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());                int requiredPriority = findPriorityForClass(cls);                if (currentPriority < requiredPriority) {                    apcDefinition.setBeanClassName(cls.getName());                }            }            return null;        } else {            RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);            beanDefinition.setSource(source);            beanDefinition.getPropertyValues().add("order", -2147483648);            beanDefinition.setRole(2);            registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);            return beanDefinition;        }    }
复制代码


书接上文,获取 beanDefinition 后,会根据配置查看 bean 代理生成使用哪种模式,上文提到了,这里会根据 proxy-target-class 属性做判断,如果为 true 则使用 CGLIB。添加属性配置后会调用 registerComponentIfNecessary 重新注册 internalAutoProxyCreator 组件。


    private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {        if (sourceElement != null) {            boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute("proxy-target-class"));            if (proxyTargetClass) {                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);            }
boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute("expose-proxy")); if (exposeProxy) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } }
} private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ParserContext parserContext) { if (beanDefinition != null) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(beanDefinition, "org.springframework.aop.config.internalAutoProxyCreator"); parserContext.registerComponent(componentDefinition); }
}
复制代码


回到主流程中首先判断是否加载过 org.springframework.cache.config.internalCacheAdvisor 目的是避免重复。校验过后定义了 AnnotationCacheOperationSource 这个 beanDefinition,这个类比较绕,通过上帝视角总结下,它的作用是解析目标方法中包含了哪些缓存操作, 比如 Cacheable 等注解。后面会作为其他 bean 的成员变量。


RootBeanDefinition sourceDef = new RootBeanDefinition("org.springframework.cache.annotation.AnnotationCacheOperationSource");                sourceDef.setSource(eleSource);                sourceDef.setRole(2);                String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
复制代码


接下来,是 CacheInterceptor 类的 beanDefinition 注册。CacheInterceptor 实现了 aop 的 MethodInterceptor 接口,我们可以叫他代理中的代理。。。


创建 beanDefinition 后将前文中 AnnotationCacheOperationSource 解析器作为配置项添加到 CacheInterceptor 的 bean 定义中。


RootBeanDefinition interceptorDef = new RootBeanDefinition(CacheInterceptor.class);                interceptorDef.setSource(eleSource);                interceptorDef.setRole(2);                //这块不特别说明了,目的是为了添加cacheManager ref                AnnotationDrivenCacheBeanDefinitionParser.parseCacheManagerProperty(element, interceptorDef);                //设置KeyGenerator,不够灵活pass掉了                CacheNamespaceHandler.parseKeyGenerator(element, interceptorDef);                //                interceptorDef.getPropertyValues().add("cacheOperationSources", new RuntimeBeanReference(sourceName));
复制代码


CacheInterceptor 实际的作用是为配置 @Cacheable 注解的目标方法提供切面功能,非常类似于一个定制化的 @around。直接上代码。通过上面的解析器获取出缓存操作列表,如果能获取到缓存且不需要更新缓存则直接返回数据。如果需要更新则通过目标方法获取最新数据,在刷新缓存后直接返回。在这里包含了生成 rediskey 的步骤,后面会有介绍。


protected Object execute(CacheAspectSupport.Invoker invoker, Object target, Method method, Object[] args) {        if (!this.initialized) {            return invoker.invoke();        } else {            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);            if (targetClass == null && target != null) {                targetClass = target.getClass();            }
Collection<CacheOperation> cacheOp = this.getCacheOperationSource().getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(cacheOp)) { Map<String, Collection<CacheAspectSupport.CacheOperationContext>> ops = this.createOperationContext(cacheOp, method, args, target, targetClass); this.inspectBeforeCacheEvicts((Collection)ops.get("cacheevict")); CacheAspectSupport.CacheStatus status = this.inspectCacheables((Collection)ops.get("cacheable")); Map<CacheAspectSupport.CacheOperationContext, Object> updates = this.inspectCacheUpdates((Collection)ops.get("cacheupdate")); if (status != null) { if (!status.updateRequired) { return status.retVal; }
updates.putAll(status.cacheUpdates); }
Object retVal = invoker.invoke(); this.inspectAfterCacheEvicts((Collection)ops.get("cacheevict"), retVal); if (!updates.isEmpty()) { this.update(updates, retVal); }
return retVal; } else { return invoker.invoke(); } } }
复制代码


返回主流程,下面这部分是 BeanFactoryCacheOperationSourceAdvisor 缓存通知器的 beanDefinition。这个类功能是注册 aop,声明了切面的连接点(实际上依赖于上文中 cacheOperationSource 这个 bean)与通知(实际上依赖于上文中 CacheInterceptor 这个 bean)。


RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheOperationSourceAdvisor.class);                advisorDef.setSource(eleSource);                advisorDef.setRole(2);                advisorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));                advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);                if (element.hasAttribute("order")) {                    advisorDef.getPropertyValues().add("order", element.getAttribute("order"));                }
parserContext.getRegistry().registerBeanDefinition("org.springframework.cache.config.internalCacheAdvisor", advisorDef);
复制代码


BeanFactoryCacheOperationSourceAdvisor 类实现了 PointcutAdvisor 指定了切面点(实际没用表达式,直接通过 match 暴力获取注解,能获取到则表示命中 aop)


public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {    private CacheOperationSource cacheOperationSource;    private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {        protected CacheOperationSource getCacheOperationSource() {            return BeanFactoryCacheOperationSourceAdvisor.this.cacheOperationSource;        }    };
public BeanFactoryCacheOperationSourceAdvisor() { }
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) { this.cacheOperationSource = cacheOperationSource; }
public void setClassFilter(ClassFilter classFilter) { this.pointcut.setClassFilter(classFilter); }
public Pointcut getPointcut() { return this.pointcut; }}
//其中切面点matchs方法public boolean matches(Method method, Class<?> targetClass) { CacheOperationSource cas = this.getCacheOperationSource(); return cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)); }
复制代码


最后,注册复合组件,并将其注册到解析器上下文中。熟悉 aop 源码就可以知道,在 bean 实例化阶段,后置处理器会检查 bean 命中了哪个 aop,再根据自动代理生成器中的配置,来决定使用哪种代理方式生成代理类,同时织入对应的 advice。实际上是代理到 CacheInterceptor 上面,CacheInterceptor 中间商内部再调用 target 目标类,就是这么简单~


                CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);                compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));                compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));                compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, "org.springframework.cache.config.internalCacheAdvisor"));                parserContext.registerComponent(compositeDef);
复制代码

三. 缓存 key 生成原理

然而 key 是如何产生的?通过上问的阐述,就知道要找这个中间商 CacheInterceptor,上代码。


protected Object execute(CacheAspectSupport.Invoker invoker, Object target, Method method, Object[] args) {        if (!this.initialized) {            return invoker.invoke();        } else {            Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);            if (targetClass == null && target != null) {                targetClass = target.getClass();            }
Collection<CacheOperation> cacheOp = this.getCacheOperationSource().getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(cacheOp)) { Map<String, Collection<CacheAspectSupport.CacheOperationContext>> ops = this.createOperationContext(cacheOp, method, args, target, targetClass); this.inspectBeforeCacheEvicts((Collection)ops.get("cacheevict")); CacheAspectSupport.CacheStatus status = this.inspectCacheables((Collection)ops.get("cacheable")); Map<CacheAspectSupport.CacheOperationContext, Object> updates = this.inspectCacheUpdates((Collection)ops.get("cacheupdate")); if (status != null) { if (!status.updateRequired) { return status.retVal; }
updates.putAll(status.cacheUpdates); }
Object retVal = invoker.invoke(); this.inspectAfterCacheEvicts((Collection)ops.get("cacheevict"), retVal); if (!updates.isEmpty()) { this.update(updates, retVal); }
return retVal; } else { return invoker.invoke(); } } }
复制代码


倒车回到这里,最直观的嫌疑人是 return status.retVal;这句继续跟进 status。


private CacheAspectSupport.CacheStatus inspectCacheables(Collection<CacheAspectSupport.CacheOperationContext> cacheables) {        Map<CacheAspectSupport.CacheOperationContext, Object> cacheUpdates = new LinkedHashMap(cacheables.size());        boolean cacheHit = false;        Object retVal = null;        if (!cacheables.isEmpty()) {            boolean log = this.logger.isTraceEnabled();            boolean atLeastOnePassed = false;            Iterator i$ = cacheables.iterator();
while(true) { while(true) { CacheAspectSupport.CacheOperationContext context; Object key; label48: do { while(i$.hasNext()) { context = (CacheAspectSupport.CacheOperationContext)i$.next(); if (context.isConditionPassing()) { atLeastOnePassed = true; key = context.generateKey(); if (log) { this.logger.trace("Computed cache key " + key + " for operation " + context.operation); }
if (key == null) { throw new IllegalArgumentException("Null key returned for cache operation (maybe you are using named params on classes without debug info?) " + context.operation); }
cacheUpdates.put(context, key); continue label48; }
if (log) { this.logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation); } }
if (atLeastOnePassed) { return new CacheAspectSupport.CacheStatus(cacheUpdates, !cacheHit, retVal); }
return null; } while(cacheHit);
Iterator i$ = context.getCaches().iterator();
while(i$.hasNext()) { Cache cache = (Cache)i$.next(); ValueWrapper wrapper = cache.get(key); if (wrapper != null) { retVal = wrapper.get(); cacheHit = true; break; } } } } } else { return null; } }
复制代码


key = context.generateKey(); 再跳转。


protected Object generateKey() {            if (StringUtils.hasText(this.operation.getKey())) {                EvaluationContext evaluationContext = this.createEvaluationContext(ExpressionEvaluator.NO_RESULT);                return CacheAspectSupport.this.evaluator.key(this.operation.getKey(), this.method, evaluationContext);            } else {                return CacheAspectSupport.this.keyGenerator.generate(this.target, this.method, this.args);            }        }
复制代码


到达 getExpression 方法,由于 key 在注解上面配置了,所以不为空,在继续跳转。


public Object key(String keyExpression, Method method, EvaluationContext evalContext) {        return this.getExpression(this.keyCache, keyExpression, method).getValue(evalContext);    }        private Expression getExpression(Map<String, Expression> cache, String expression, Method method) {        String key = this.toString(method, expression);        Expression rtn = (Expression)cache.get(key);        if (rtn == null) {            rtn = this.parser.parseExpression(expression);            cache.put(key, rtn);        }
return rtn; }
复制代码


最终来到了 parser.parseExpression;


根据代码可以看到解析器用的是 private final SpelExpressionParser parser = new SpelExpressionParser();


可以得出结论就是 Spel 表达式这个东东吧。对于实体类+方法的表达式可能会实时去反射得到结果。那我们能不能再生产 key 的上层再加一层缓存呢?答案是肯定的。

四. 代码优化

我们可以通过 javaPoet 方式动态生成 class 的形式,将生成的类加载到内存中。通过它的实例来生成 key。


javaPoet 类似于 javasis 是一个用于动态生成代码的开源项目,通过这个类库下面的 api 我们来进行简易 diy 尝试。


上代码,忽略不重要部分,切面简写直接展示生成 key 的部分。




@Aspect@Componentpublic class CacheAspect {
@Around("@annotation(myCache)") public Object around(ProceedingJoinPoint pjp, MyCache myCache) throws Throwable { long currentTime = System.currentTimeMillis(); Object value = null; try { if(!myCache.useCache()){ return pjp.proceed(); } Object[] args = pjp.getArgs(); if(args == null || args[0] == null){ return pjp.proceed(); } Object obj = args[0]; String key = MyCacheCacheKeyGenerator.generatorCacheKey(myCache,obj.getClass().getDeclaredFields(),obj); ......
} catch (Throwable throwable) { log.error("cache throwable",throwable); } return pjp.proceed(); }

}
复制代码


缓存 key 生成接口。




public interface MyCacheKeyGenerator {
/** * 生成key * */ String generateKey(Method method, Object[] args, Object target, String key);
}
复制代码


具体实现,其中 wrapper 是一个包装类,只是一个搬运工。通过 key 来动态产生 key 生成器。


public class DyCacheKeyGenerator implements MyCacheKeyGenerator {
private final ConcurrentMap<String, Wrapper> cacheMap = new ConcurrentHashMap<String, Wrapper>();
/** * 生成key * * @param method 调用的方法名字 * @param args 参数列表 * @param target 目标值 * @param key key的格式 * @return */ @Override public String generateKey(Method method, Object[] args, Object target, String key) { Wrapper wrapper = cacheMap.computeIfAbsent(key, k -> new Wrapper()); getMykeyGenerator(method, key, wrapper); return ((MyCacheKeyGenerator) wrapper.getData()).generate(args); }
private void getMykeyGenerator(Method method, String key, Wrapper wrapper) { if (wrapper.getData() != null) { return; } synchronized (wrapper) { if (wrapper.getData() == null) { MyCacheKeyGenerator keyGenerator = MyCacheKeyGenerator.initMyKeyGenerator(method, key); wrapper.setData(keyGenerator); } } }
}
复制代码


那么我们首先根据 key 获取表达式的集合,如果是反射则会生成 DynamicExpression 表达式,连接符会生成静态的 StaticExpression 表达式。表达式持有了 key 中字符串的片段。


public static MyCacheKeyGenerator initMyKeyGenerator(Method method, String key) {
Set<Class> importHashSet = new HashSet(); //根据key中的配置的方法生成表达式列表 List<Expression> expressionList = new LinkedList<Expression>(); generateExpression(key, expressionList);
for (Expression expression : expressionList) { if (expression instanceof DynamicExpression) { String expressionStr = expression.execute(); //判断格式合法性 String[] items = expressionStr.split("\.");
String indexValue = items[0].replace("args", ""); int index = Integer.parseInt(indexValue); Class clx = method.getParameterTypes()[index]; importHashSet.add(clx); //获取对应属性的方法 String filedName = items[1]; String keyValue = Character.toUpperCase(filedName.charAt(0)) + filedName.substring(1);
try { keyValue = "get" + keyValue; Method felidMethod = clx.getMethod(keyValue); expression.setExpression(String.format("String.valueOf(((%s)args[%s]).%s())", clx.getName(), index, felidMethod.getName())); } catch (NoSuchMethodException e) { }
} }
// 定义接口类型 ClassName interfaceName = ClassName.get("com.xxx.xxx", "MyKeyGenerator");
// 定义类名和包名 ClassName className = ClassName.get("com.xxx.xxx", "DyMyKeyGeneratorImpl" + classIndex.incrementAndGet());
// 创建类构造器 TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className.simpleName()) .addModifiers(Modifier.PUBLIC) .addSuperinterface(interfaceName);
StringBuilder stringBuilder = new StringBuilder("stringBuilder"); for (Expression expression : expressionList) { stringBuilder.append(".append(").append(expression.execute()).append(")"); }
MethodSpec generateMethod = MethodSpec.methodBuilder("generate") .addModifiers(Modifier.PUBLIC) .returns(String.class) .addParameter(Object[].class, "args") .addStatement("$T stringBuilder = new StringBuilder()", StringBuilder.class) .addStatement(stringBuilder.toString()) .addStatement("return $S", "stringBuilder.toString();") .build();
classBuilder.addMethod(generateMethod);
JavaFile javaFile = JavaFile.builder(className.packageName(), classBuilder.build()) .build();

StringBuilder sb = new StringBuilder(); try { javaFile.writeTo(sb); } catch (IOException e) { logger.error("写入StringBuilder失败", e); }

try { System.out.println(sb.toString()); Map<String, byte[]> results = compiler.compile(className + ".java", sb.toString()); Class<?> clazz = compiler.loadClass("com.xxx.xxx." + className, results); return (KeyGenerator) clazz.newInstance(); } catch (Exception e) { logger.error("编译失败,编译内容:{}", sb.toString(), e); throw new RuntimeException("内存class编译失败"); } } public static void generateExpression(String key, List<Expression> expressionList) { if (StringUtils.isEmpty(key)) { return; } int index = key.indexOf(paramsPrefix); if (index < 0) { expressionList.add(new StaticExpression(key)); return; }else{ expressionList.add(new DynamicExpression(key.substring(0, index))); } generateExpression(key.substring(index + paramsPrefix.length()), expressionList); }
复制代码


生成表达式列表后开始遍历,最终得到 key 中每个 arg 形参与对应的方法片段(key 格式类似于 @Cacheable 注解的用法。比如文章开始时候提到的我们可以改成这样使用,代码如下:)


@MyCache(key="#args0.getId()"+"_"+"#args0.getName()")public Person getByIsbn(Person person) {    return personMapper.getPerson(person);}
复制代码


将静态与动态片段重新拼接放入表达式中。然后我们使用 JavaPoet 的接口动态创建 class,实现其中的 generateKey 方法,并且解析表达式填充到方法的实现中。最终将 class 加载到内存中,再生产一个实例,并将这个实例缓存到内存中。这样下次调用就可以使用动态生成的实例丝滑的拼接 key 啦!!

五. 总结

JavaPoet 用法还有很多,而且 @Cacheable 还有很多灵活玩法,由于篇幅太长就不一一呈现了。respect!

发布于: 刚刚阅读数: 3
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
基于javaPoet的缓存key优化实践_京东科技开发者_InfoQ写作社区