色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于javaPoet的緩存key優化實踐

京東云 ? 來源:京東物流 方志民 ? 作者:京東物流 方志民 ? 2025-01-14 15:18 ? 次閱讀

作者:京東物流 方志民

一. 背景

在一次系統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-manager表示需要依賴一個緩存管理器,它的作用是提供一種機制來緩存數據,以便在后續的訪問中可以更快地獲取數據。它可以支持caffine,encache,Jcache等多種類型的緩存管理器。文中是使用的自定義管理來支持公司內部的redis客戶端。

chaijie_default.png

//redis緩存管理器
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

    private Collection caches;


    public void setCaches(List caches) {
        this.caches = caches;
    }

    @Override
    protected Collection 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緩存操作的目的。


        
        
            
                
            
        
    

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
    key
    +關注

    關注

    0

    文章

    50

    瀏覽量

    12829
收藏 人收藏

    評論

    相關推薦

    LRU緩存模塊最佳實踐

    LRU(Least Recently Used)是一種緩存替換算法,它的核心思想是當緩存滿時,替換最近最少使用的數據。在實際應用中,LRU算法被廣泛應用于緩存、頁面置換等領域。Rust語言提供了一個
    的頭像 發表于 09-30 16:47 ?923次閱讀

    本地緩存的技術實踐

    一、摘要 說到緩存,面試官基本上會繞不開以下幾個話題! 項目中哪些地方用到了緩存?為什么要使用緩存?怎么使用它的?引入緩存后會帶來哪些問題? 這些問題,基本上是互聯網公司面試時必問的一
    的頭像 發表于 09-30 15:29 ?699次閱讀
    本地<b class='flag-5'>緩存</b>的技術<b class='flag-5'>實踐</b>

    緩存有大key?你得知道的一些手段

    ?? ? ? ? 背景: 最近系統內緩存CPU使用率一直報警,超過設置的70%報警閥值,針對此場景,需要對應解決緩存是否有大key使用問題,掃描緩存集群的大
    的頭像 發表于 06-19 09:38 ?791次閱讀
    <b class='flag-5'>緩存</b>有大<b class='flag-5'>key</b>?你得知道的一些手段

    【RTC程序設計:實時音視頻權威指南】傳輸控制優化閱讀 及其實踐分享

    書中第八章 傳輸控制優化,主要介紹了擁塞控制,弱網降級,抖動緩存與平滑發送,錯誤恢復等幾個部分的內容。 其中抖動緩存與平滑發送是非常重要的一部分,在實踐中是保證音視頻實時不斷流的傳輸
    發表于 04-21 17:11

    基于Java的分布式緩存優化在網絡管理系統中的應用

    基于Java的分布式緩存優化在網絡管理系統中的應用討論建立在JMX管理框架上的網絡性能管理系統的優化方案,利用JMX體系結構的可擴展特性,在系統的服務器端嵌入了分布式緩存系統對服務器端
    發表于 09-19 09:20

    緩存優化引起的DMA問題

    保持不正確。看來內存更新被困在緩存中,或者優化器正在保持寄存器的變化。我認為這是緩存,因為我已經嘗試用AyAtAtditTyx編譯內存寫代碼((優化(-O0))),并且更改是多字節的。
    發表于 06-04 09:42

    緩存的作用和設計模式

    查詢數據,獲取數據后并加載到緩存緩存失效:數據更新寫到數據庫,操作成功后,讓緩存失效,查詢時候再重新加載;緩存穿透:查詢數據庫不存在的對象,也就不存在
    發表于 01-05 17:57

    MySql5.6性能優化最佳實踐

    MySql5.6性能優化最佳實踐
    發表于 09-08 08:47 ?13次下載
    MySql5.6性能<b class='flag-5'>優化</b>最佳<b class='flag-5'>實踐</b>

    面向多目標優化的自適應SSD緩存系統

    以SSD(solid state drive)為代表的新型存儲介質在虛擬化環境下得到了廣泛的應用,通常作為虛擬機讀寫緩存。起到優化磁盤I/O性能的作用.已有研究往往關注SSD緩存的容量規劃,依據
    發表于 12-26 17:13 ?0次下載
    面向多目標<b class='flag-5'>優化</b>的自適應SSD<b class='flag-5'>緩存</b>系統

    費用優化的云存儲緩存策略

    為提高云存儲的訪問速率并降低費用,提出了一種面向費用優化的云存儲緩存策略。利用幾乎免費的局域網環境下的多臺桌面計算機,在本地建立一個分布式文件系統,并將其作為遠端云存儲的緩存。進行文件讀取時,首先
    發表于 01-24 14:45 ?0次下載
    費用<b class='flag-5'>優化</b>的云存儲<b class='flag-5'>緩存</b>策略

    緩存服務器運作的原理解析

    /O。另一方面,memcached在存儲區中對于每一個key都維護一個過期時間,一旦達到這個過期時間,memcached便會自動刪除這個key,這使得我們的過期檢查非常容易,只需要在保存緩存數據時指定過期時間即可。
    發表于 04-28 12:43 ?1182次閱讀

    如何設計一個緩存系統?

    則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。 解決方案 有很多種方法可以有效地解決
    的頭像 發表于 02-08 11:40 ?2951次閱讀

    AN5212_利用STM32L5系列緩存優化性能和電源效率

    AN5212_利用STM32L5系列緩存優化性能和電源效率
    發表于 11-21 08:11 ?0次下載
    AN5212_利用STM32L5系列<b class='flag-5'>緩存</b><b class='flag-5'>優化</b>性能和電源效率

    使用STM32高速緩存優化性能和能效

    使用STM32高速緩存優化性能和能效
    發表于 11-21 17:07 ?0次下載
    使用STM32高速<b class='flag-5'>緩存</b><b class='flag-5'>優化</b>性能和能效

    緩存被穿透了如何解決

    首先來了解幾個概念: 緩存穿透:大量請求根本不存在的key 緩存雪崩:redis中大量key集體過期 緩存擊穿:redis中一個熱點
    的頭像 發表于 05-23 09:54 ?708次閱讀
    <b class='flag-5'>緩存</b>被穿透了如何解決
    主站蜘蛛池模板: 午夜办公室在线观看高清电影| 久久兔费黄A级毛片高清| 乱辈通奷XXXXXHD猛交| 乱叫抽搐流白浆免费视频| 亚洲伊人情人综合网站| 最近最新的日本字幕MV| 苍井空教师BD在线观看全集| 国产精品欧美久久久久天天影视| 久久精品国产eeuss| 青青草原伊人| 亚洲 无码 制服 日韩| 99re在这里只有精品| 国产精品熟女人妻| 欧美eee114| 亚洲99精品A片久久久久久| 国产午夜精品理论片免费观看| 女人麻豆国产香蕉久久精品| bbbbbxxxxx肥胖| 久久www成人看片| 伊人久久大香线蕉综合色啪| 国产国产人免费观看在线视频| 美女胸被男子强捏视频| 天天看高清影视在线18| 99re久久这里只有精品| 强奷漂亮女老板在线播放| 高H高肉强J短篇NP| 浓毛BWBWBWBWBW日本| 宅男午夜大片又黄又爽大片| 最近中文字幕MV高清在线| 免费亚洲视频在线观看| 在线欧美 精品 第1页| 麻豆三级电影| 在线观看中文字幕国产| 欧美另类摘花hd| 99精品在线| 久啪久久全部视频在线| 亚洲中文久久精品AV无码| 美女被爽cao免费漫画| 第一次处破女18分钟免费| 欧美丰满熟妇无码XOXOXO| 国产成人免费高清激情视频|