一. 背景

在一次系统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进行动态代理。

//
!-- 添加缓存注解支持 -->
:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/> !-- 开启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 (cache = super.getCache("DEFAULT")) == null) {
throw new NullPointerException();
}
return cache;
} }

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


="cacheManager" class="com.xx.xx.RedisCacheManager">
="transactionAware" value="true"/>
="caches">
<list>
class="com.xx.xx.MyCache"/>
/list>
/property>
/beanCache接口中有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 ) {
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 接下来,是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) {
targetClass = target.getClass();
} Collection<CacheOperation> cacheOp = this.getCacheOperationSource().getCacheOperations(method, targetClass);
if (= 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 !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) {
targetClass = target.getClass();
} Collection<CacheOperation> cacheOp = this.getCacheOperationSource().getCacheOperations(method, targetClass);
if (= 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
@Component
public 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 ();

    /**
* 生成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 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 ();
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 )
.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());
MapString, byte[]= 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!

作者:京东物流 方志民

来源:京东云开发者社区 自猿其说 Tech 转载请注明来源

基于javaPoet的缓存key优化实践的更多相关文章

  1. 基于 Nginx 的 HTTPS 性能优化实践

    前言 分享一个卓见云的较多客户遇到HTTPS优化案例. 随着相关浏览器对HTTP协议的“不安全”.红色页面警告等严格措施的出台,以及向 iOS 应用的 ATS 要求和微信.支付宝小程序强制 HTTPS ...

  2. ASP.NET Core ResponseCaching:基于 VaryByHeader 定制缓存 Key

    ASP.NET Core ResponseCaching 提供了缓存http响应内容的能力,通过它可以在本地内存中直接缓存http响应内容,这是速度最快的服务端缓存,省却了网络传输与生成响应内容的开销 ...

  3. 大型网站的 HTTPS 实践(三)——基于协议和配置的优化

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt389 1 前言 上文讲到 HTTPS 对用户访问速度的影响. 本文就为大家介 ...

  4. 腾讯技术分享:Android版手机QQ的缓存监控与优化实践

    本文内容整理自公众号腾讯Bugly,感谢原作者的分享. 1.问题背景 对于Android应用来说,内存向来是比较重要的性能指标.内存占用过高,会影响应用的流畅度,甚至引发OOM,非常影响用户体验.因此 ...

  5. Linux实战教学笔记36:PHP服务缓存加速深度优化实践

    一,PHP缓存加速器介绍与环境准备 1.1 PHP缓存加速器介绍 1.1.1 操作码介绍及缓存原理 当客户端请求一个PHP程序时,服务器的PHP引擎会解析该PHP程序,并将其编译为特定的操作码(Ope ...

  6. 【实战分享】又拍云 OpenResty / Nginx 服务优化实践

    2018 年 11 月 17 日,由 OpenResty 主办的 OpenResty Con 2018 在杭州举行.本次 OpenResty Con 的主题涉及 OpenResty 的新开源特性.业界 ...

  7. 基于 Docker 的微服务架构实践

    本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 D ...

  8. HTTPS 性能优化 -- 基于协议和配置的优化

    基于协议和配置的优化 1 前言 上文讲到 HTTPS 对用户访问速度的影响. 本文就为大家介绍 HTTPS 在访问速度,计算性能,安全等方面基于协议和配置的优化. 2 HTTPS 访问速度优化 2.1 ...

  9. 【Vuejs】335-(超全) Vue 项目性能优化实践指南

    点击上方"前端自习课"关注,学习起来~ 前言 Vue 框架通过数据双向绑定和虚拟 DOM 技术,帮我们处理了前端开发中最脏最累的 DOM 操作部分, 我们不再需要去考虑如何操作 D ...

  10. 京东基于Spark的风控系统架构实践和技术细节

    京东基于Spark的风控系统架构实践和技术细节 时间 2016-06-02 09:36:32  炼数成金 原文  http://www.dataguru.cn/article-9419-1.html ...

随机推荐

  1. 动手实践丨基于ModelAtrs使用A2C算法制作登月器着陆小游戏

    摘要:在本案例中,我们将展示如何基于A2C算法,训练一个LunarLander小游戏. 本文分享自华为云社区<使用A2C算法控制登月器着陆>,作者:HWCloudAI . LunarLan ...

  2. 用IoT放羊养牛,不出门也能知道它们的动向

    摘要:羊圈里几只小羊羔有点无精打采,"叮",牧民拉索划开手机,第一时间得知了这个情况.草场上有只牛一直到天黑都没回来,拉索再次通过手机软件,很快定位到那只独自流落在外的牛儿-- 本 ...

  3. 通过windows自带管理工具、系统命令行、快捷键等快速操作

    windows自带管理工具 我们win+R 输入一些命令,可以快速打开一些界面,比如: sysdm.cpl win10.win11 我电脑,属性与之前win7不同了,我希望打开之前的属性打不开了 通过 ...

  4. 如何配置Apple推送证书 push证书

    ​ 转载:如何配置Apple推送证书 push证书 想要制作push证书,就需要使用快捷工具appuploader工具制 作证书,然后使用Apple的推送功能配置push证书,就可以得到了.PS:pu ...

  5. 用 docker 部署 folkmq 消息中间件

    docker run -p 18602:18602 -p 8602:8602 noearorg/folkmq-server:1.0.19

  6. ubuntu下完全卸载重装docker教程

    操作需在管理员权限下运行 卸载docker 1.删除docker的所有包 apt-get autoremove docker docker-ce docker-engine docker.io con ...

  7. 叫板GPT-4的Gemini,我做了一个聊天网页,可图片输入,附教程

    先看效果: 简介 Gemini 是谷歌研发的最新一代大语言模型,目前有三个版本,被称为中杯.大杯.超大杯,Gemini Ultra 号称可与GPT-4一较高低: Gemini Nano(预览访问) 为 ...

  8. CS01 BOM客制化屏幕增强

    一.BOM行项目新增定制字段 效果如下 二.前台增强实现步骤 1.行项目表新增字段 2.CMOD,增强项目PCSD0002:在行项目中增强(PCSD0003:在抬头增强) 3.双击创建定制化屏幕 4. ...

  9. C# Task 多任务:C# 扩展TaskScheduler实现独立线程池,支持多任务批量处理,互不干扰,无缝兼容Task

    先上源码: https://gitee.com/s0611163/TaskSchedulerEx     为什么编写TaskSchedulerEx类? 因为.NET默认线程池只有一个线程池,如果某个批 ...

  10. 如何在CSDN上如何快速转载博客

    复制粘贴应该是最显而易见的方法,但是不仅会有丢失内容,而且格式也会丢失.要想达到更好的效果,可以从html源码入手. 1.在chrome浏览器中打开要转载的文章,右键选择检查(or使用F12) 2.在 ...