作者:京東物流 方志民
一. 背景
在一次系統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進行動態代理。
// !-- 添加緩存注解支持 --?> !-- 開啟aop切面 --?>
代碼中cache-manager表示需要依賴一個緩存管理器,它的作用是提供一種機制來緩存數據,以便在后續的訪問中可以更快地獲取數據。它可以支持caffine,encache,Jcache等多種類型的緩存管理器。文中是使用的自定義管理來支持公司內部的redis客戶端。
//redis緩存管理器 public class RedisCacheManager extends AbstractTransactionSupportingCacheManager { private Collection? extends Cache?> caches; public void setCaches(List 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緩存操作的目的。
/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 cacheOp = this.getCacheOperationSource().getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(cacheOp)) { Map> ops = this.createOperationContext(cacheOp, method, args, target, targetClass); this.inspectBeforeCacheEvicts((Collection)ops.get("cacheevict")); CacheAspectSupport.CacheStatus status = this.inspectCacheables((Collection)ops.get("cacheable")); Map 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 cacheOp = this.getCacheOperationSource().getCacheOperations(method, targetClass); if (!CollectionUtils.isEmpty(cacheOp)) { Map> ops = this.createOperationContext(cacheOp, method, args, target, targetClass); this.inspectBeforeCacheEvicts((Collection)ops.get("cacheevict")); CacheAspectSupport.CacheStatus status = this.inspectCacheables((Collection)ops.get("cacheable")); Map 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 cacheables) { Map 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 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 cacheMap = new ConcurrentHashMap(); /** * 生成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 importHashSet = new HashSet(); //根據key中的配置的方法生成表達式列表 List expressionList = new LinkedList(); 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 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 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!
審核編輯 黃宇
-
代碼
+關注
關注
30文章
4807瀏覽量
68800 -
key
+關注
關注
0文章
50瀏覽量
12829
發布評論請先 登錄
相關推薦
評論