OO模块集成 Jetcache 缓存问题

活动Action开发过程中碰到缓存使用的问题,Action中的代码基本从原 service 中迁移,
Jetcache 缓存的注解对Spring 容器内的类生效(现在代码中都作用在@service标记类中),但像Action 这些类手动创建出来实例缓存注解就失效了;

思路1:

  1. 简单有效的方式,通过显式的 Api 调用来集成,通过抽象类定义存取缓存基础动作,子类显式调用,这种思路下搞到缓存入口CacheManager就行,配合官方demo。
    代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public abstract class AbstractActivityHandlerFacility {

    protected CacheManager getCacheManager() {
    return SpringUtil.getBean(CacheManager.class);
    }
    private Cache<String, Object> caches;

    public AbstractActivityHandlerFacility() {
    QuickConfig qc = QuickConfig.newBuilder("userCache")
    .expire(Duration.ofSeconds(3600))
    .loader(this::loadOrderSumFromDatabase)
    .refreshPolicy(RefreshPolicy.newPolicy(60, TimeUnit.SECONDS).stopRefreshAfterLastAccess(100, TimeUnit.SECONDS))
    .penetrationProtect(true)
    .build();
    caches = getCacheManager().getOrCreateCache(qc);
    }

    protected String getCacheKey(String constantPrefix, ActivityContext context) {
    return new StringBuilder(constantPrefix)
    .append(context.getActivityInstance().getActivityCombineInfo().getActivityBaseInfo().getId())
    .append("_")
    .append(context.getUserInstance().getUserId()).toString();
    }

    protected RedisService getRedisService() {
    return SpringUtil.getBean(RedisService.class);
    }

    protected void putToCache(String key, Object value, long timeout, TimeUnit timeUnit) {
    caches.put(key, value, timeout, timeUnit);
    }

    protected <T> T getCache(String key) {
    return (T) caches.get(key);
    }

    protected void clearCache(String key) {
    caches.remove(key);
    }

    }

备注: QuickConfig 是jetcache 2.7.x 版本才有, 项目中v2.6.7
业务代码中手动调用存取数据,代码量多了50%,同时增加了编码的思维负担。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public ActivityCombineInfo getJoinActivityStatus() {
String cacheKey = getCacheKey(CacheConstant.ACTIVITY_USER_JOIN_INFO, context);
log.info("getJoinActivityStatus cacheKey: {}", cacheKey);

ActivityCombineInfo cache = getCache(cacheKey);

if (cache == null) {

log.info("Get join activity status from cache, cacheKey: {}", cacheKey);
JoinStatusAction joinStatusAction = getJoinStatusAction();
ActivityCombineInfo<JoinActivityResponse> execute = null;

try {
execute = joinStatusAction.execute(context);
// Put Cache
putToCache(cacheKey, execute, 60, TimeUnit.SECONDS);
} catch (ActivityActionException e) {
log.error("Join status execute failed: {}", e.getMessage());
}

return execute;
}
return cache;
}


思路1能达到要求,但不够优雅,万不得已情况下实在难以接受。

思路2(目前采用):

不打破现有cache 的编码使用习惯,Action 中可以直接使用@Cached、@CacheInvalidate等注解。
要做到这种程度还是要将Action 调用方 ActivityHandler和容器勾搭上,看看代码找找思路。

过程

spring 容器下注解工作套路,容器生命周期的钩子中触发扫 package->反射->挂切面->动态代理目标对象。
盲猜 jetcache 也差不多,只是时机的选择问题。考古一张spring 的图

源码解读:

  • Maven: com.alicp.jetcache:jetcache-anno:2.6.7 注解工作代码
  • Maven: com.alicp.jetcache:jetcache-anno-api:2.6.7 注解声明
  • Maven: com.alicp.jetcache:jetcache-core:2.6.7 核心实现,没什么和spring相关内容
  • Maven: com.alicp.jetcache:jetcache-autoconfigure:2.6.7 springboot 自动装配套路
  • Maven: com.alicp.jetcache:jetcache-redis:2.6.7
  • Maven: com.alicp.jetcache:jetcache-starter-redis:2.6.7

撸了下代码,跟预想差不多 CreateCacheAnnotationBeanPostProcessor,作为时机 开始初始化缓存注解相关的内容;

层次依次: CacheAdvisor -> CachePointcut ->JetCacheInterceptor

有这三个就具备 aop 条件了。

关键代码:
通过ProxyFactory动态将CacheAdvisor织入activityHandler

1
2
3
4
5
6
7
8
9
10
public <T> T dynamicCreateActivityHandler(ActivityHandler activityHandler){
ProxyFactory factory = new ProxyFactory();
factory.setTarget(activityHandler);
factory.addAdvisor(SpringUtil.getBean(CacheAdvisor.class));
// Error: Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.
// 设置显式暴露后,AopContext.currentProxy()才能正常使用
factory.setExposeProxy(true);
//factory.setInterfaces(JetCacheInterceptor.class);
return (T) factory.getProxy();
}

调用方式和原来service中一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ActionsHandler implements  Handler {

//省略

private ActionsHandler _this() {
return (ActionsHandler) AopContext.currentProxy();
}

@Override
public boolean doJoinActivity() {
return _this()._doJoinActivity(context);

}
@CacheInvalidate(name = CacheConstant.ACTIVITY_USER_JOIN_INFO, key =
"#context.activityInstance.activityCombineInfo.activityBaseInfo.id + '_' + #context.userInstance.userId"
)
private boolean _doJoinActivity(ActivityContext context){
DoJoinAction doJoinAction = getDoJoinAction();
try {
return doJoinAction.execute(context);
} catch (ActivityActionException e) {
log.error("Do Join execute failed: {}", e.getMessage());
}
return false;
}
}

区别是多绕2个弯,原接口doJoinActivity()设计时考虑灵活性,没使用形参而是使用实例成员作为参数,注解中的 key 无法动态计算#userId这种变量
翻了解析逻辑,关键代码ExpressionUtil.eval:61 ,这块看似留了KeyEvaluator作口子用来扩展,但是没有给上下文修Context改的空间,翻了一圈,无计可施。

目前取巧暂时用内部方法包装context,凑合先用,种草 后续来拔。

Author

Gavin

Posted on

2024-12-19

Updated on

2024-12-19

Licensed under

Comments