springboot中如何使用自定义两级缓存

论坛 期权论坛 脚本     
niminba   2021-5-23 02:38   2100   0

  工作中用到了springboot的缓存,使用起来挺方便的,直接引入redis或者ehcache这些缓存依赖包和相关缓存的starter依赖包,然后在启动类中加入@EnableCaching注解,然后在需要的地方就可以使用@Cacheable和@CacheEvict使用和删除缓存了。这个使用很简单,相信用过springboot缓存的都会玩,这里就不再多说了。美中不足的是,springboot使用了插件式的集成方式,虽然用起来很方便,但是当你集成ehcache的时候就是用ehcache,集成redis的时候就是用redis。如果想两者一起用,ehcache作为本地一级缓存,redis作为集成式的二级缓存,使用默认的方式据我所知是没法实现的(如果有高人可以实现,麻烦指点下我)。毕竟很多服务需要多点部署,如果单独选择ehcache可以很好地实现本地缓存,但是如果在多机之间共享缓存又需要比较费时的折腾,如果选用集中式的redis缓存,因为每次取数据都要走网络,总感觉性能不会太好。

  为了不要侵入springboot原本使用缓存的方式,这里自己定义了两个缓存相关的注解,如下

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Cacheable {

        String value() default "";

        String key() default "";

        //泛型的Class类型
        Class<?> type() default Exception.class;

    }
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CacheEvict {

        String value() default "";

        String key() default "";

    }

  如上两个注解和spring中缓存的注解基本一致,只是去掉了一些不常用的属性。说到这里,不知道有没有朋友注意过,当你在springboot中单独使用redis缓存的时候,Cacheable和CacheEvict注解的value属性,实际上在redis中变成了一个zset类型的值的key,而且这个zset里面还是空的,比如@Cacheable(value="cache1",key="key1"),正常情况下redis中应该是出现cache1 -> map(key1,value1)这种形式,其中cache1作为缓存名称,map作为缓存的值,key作为map里的键,可以有效的隔离不同的缓存名称下的缓存。但是实际上redis里确是cache1 -> 空(zset)和key1 -> value1,两个独立的键值对,试验得知不同的缓存名称下的缓存完全是共用的,如果有感兴趣的朋友可以去试验下,也就是说这个value属性实际上是个摆设,键的唯一性只由key属性保证。我只能认为这是spring的缓存实现的bug,或者是特意这么设计的,(如果有知道啥原因的欢迎指点)。

  回到正题,有了注解还需要有个注解处理类,这里我使用aop的切面来进行拦截处理,原生的实现其实也大同小异。切面处理类如下:

    import com.xuanwu.apaas.core.multicache.annotation.CacheEvict;
    import com.xuanwu.apaas.core.multicache.annotation.Cacheable;
    import com.xuanwu.apaas.core.utils.JsonUtil;
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.json.JSONArray;
    import org.json.JSONObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    import org.springframework.stereotype.Component;

    import java.lang.reflect.Method;

    /**
     * 多级缓存切面
     * @author rongdi
     */
    @Aspect
    @Component
    public class MultiCacheAspect {

        private static final Logger logger = LoggerFactory.getLogger(MultiCacheAspect.class);

        @Autowired
        private CacheFactory cacheFactory;

        //这里通过一个容器初始化监听器,根据外部配置的@EnableCaching注解控制缓存开关
        private boolean cacheEnable;

        @Pointcut("@annotation(com.xuanwu.apaas.core.multicache.annotation.Cacheable)")
        public void cacheableAspect() {
        }

        @Pointcut("@annotation(com.xuanwu.apaas.core.multicache.annotation.CacheEvict)")
        public void cacheEvict() {
        }

        @Around("cacheableAspect()")
        public Object cache(ProceedingJoinPoint joinPoint) {

            //得到被切面修饰的方法的参数列表
            Object[] args = joinPoint.getArgs();
            // result是方法的最终返回结果
            Object result = null;
            //如果没有开启缓存,直接调用处理方法返回
            if(!cacheEnable){
                try {
                    result = joinPoint.proceed(args);
                } catch (Throwable e) {
                    logger.errord(key.intern()){}
        cache.acquireWriteLockOnKey(key);
        try {
            cache.put(new Element(key, value));
        } finally {
            //释放写锁
            cache.releaseWriteLockOnKey(key);
        }
    }

    public void redisPut(String name,String key,String value) {
        HashOperations<String,String,String> oper = redisTemplate.opsForHash();
        try {
            oper.put(name, key, value);
        } catch (RedisConnectionFailureException e) {
            //连接失败,不抛错,直接不用redis缓存了
            logger.error("connect redis error ",e);
        }
    }

    public void ehDel(String name,String key) {
        if(cacheManager == null) return;
        Cache cache = cacheManager.getCache(name);
        if(cache != null) {
            //如果key为空,直接根据缓存名删除
            if(StringUtils.isEmpty(key)) {
                cacheManager.removeCache(name);
            } else {
                cache.remove(key);
            }
        }
    }

    public void redisDel(String name,String key) {
        HashOperations<String,String,String> oper = redisTemplate.opsForHash();
        try {
            //如果key为空,直接根据缓存名删除
            if(StringUtils.isEmpty(key)) {
                redisTemplate.delete(name);
            } else {
                oper.delete(name,key);
            }
        } catch (RedisConnectionFailureException e) {
            //连接失败,不抛错,直接不用redis缓存了
            logger.error("connect redis error ",e);
        }
    }
}

    工具类如下

    import com.fasterxml.jackson.core.type.TypeReference;
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.JavaType;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.apache.commons.lang3.StringUtils;
    import org.json.JSONArray;
    import org.json.JSONObject;

    import java.util.*;

    public class JsonUtil {

        private static ObjectMapper mapper;

        static {
            mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
                    false);
        }
        

        /**
         * 将对象序列化成json
         *
         * @param obj 待序列化的对象
         * @return
         * @throws Exception
         */
        public static String serialize(Object obj) throws Exception {

            if (obj == null) {
                throw new IllegalArgumentException("obj should not be null");
            }
            return mapper.writeValueAsString(obj);
        }

        /**
            带泛型的反序列化,比如一个JSONArray反序列化成List<User>
        */
        public static <T> T deserialize(String jsonStr, Class<?> collectionClass,
                                        Class<?>... elementClasses) throws Exception {
            JavaType javaType = mapper.getTypeFactory().constructParametrizedType(
                    collectionClass, collectionClass, elementClasses);
            return mapper.readValue(jsonStr, javaType);
        }
        
        /**
         * 将json字符串反序列化成对象
         * @param src 待反序列化的json字符串
         * @param t   反序列化成为的对象的class类型
         * @return
         * @throws Exception
         */
        public static <T> T deserialize(String src, Class<T> t) throws Exception {
            if (src == null) {
                throw new IllegalArgumentException("src should not be null");
            }
            if("{}".equals(src.trim())) {
                return null;
            }
            return mapper.readValue(src, t);
        }

    }

  具体使用缓存,和之前一样只需要关注@Cacheable和@CacheEvict注解,同样也支持spring的el表达式。而且这里的value属性表示的缓存名称也没有上面说的那个问题,完全可以用value隔离不同的缓存,例子如下

@Cacheable(value = "bo",key="#session.productVersionCode+''+#session.tenantCode+''+#objectcode")
@CacheEvict(value = "bo",key="#session.productVersionCode+''+#session.tenantCode+''+#objectcode")

附上主要的依赖包

"org.springframework.boot:spring-boot-starter-redis:1.4.2.RELEASE",
'net.sf.ehcache:ehcache:2.10.4',
"org.json:json:20160810"

以上就是springboot中如何使用自定义两级缓存的详细内容,更多关于springboot 使用自定义两级缓存的资料请关注社区其它相关文章!

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP