百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

基于javaPoet的缓存key优化实践(java缓存实现)

mhr18 2025-04-06 18:19 18 浏览 0 评论

作者:京东物流 方志民

一. 背景

在一次系统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客户端。




//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<String, Collection> 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<String, Collection> 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!

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: