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

  工作中用到了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.error("",e);
                }
                return result;
            }

            // 得到被代理方法的返回值类型
            Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();
            // 得到被代理的方法
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            // 得到被代理的方法上的注解
            Cacheable ca = method.getAnnotation(Cacheable.class);
            //获得经过el解析后的key值
            String key = parseKey(ca.key(),method,args);
            Class<?> elementClass = ca.type();
            //从注解中获取缓存名称
            String name = ca.value();

            try {
                //先从ehcache中取数据
                String cacheValue = cacheFactory.ehGet(name,key);
                if(StringUtils.isEmpty(cacheValue)) {
                    //如果ehcache中没数据,从redis中取数据
                    cacheValue = cacheFactory.redisGet(name,key);
                    if(StringUtils.isEmpty(cacheValue)) {
                        //如果redis中没有数据
                        // 调用业务方法得到结果
                        result = joinPoint.proceed(args);
                        //将结果序列化后放入redis
                        cacheFactory.redisPut(name,key,serialize(result));
                    } else {
                        //如果redis中可以取到数据
                        //将缓存中获取到的数据反序列化后返回
                        if(elementClass == Exception.class) {
                            result = deserialize(cacheValue, returnType);
                        } else {
                            result = deserialize(cacheValue, returnType,elementClass);
                        }
                    }
                    //将结果序列化后放入ehcache
                    cacheFactory.ehPut(name,key,serialize(result));
                } else {
                    //将缓存中获取到的数据反序列化后返回
                    if(elementClass == Exception.class) {
                        result = deserialize(cacheValue, returnType);
                    } else {
                        result = deserialize(cacheValue, returnType,elementClass);
                    }
                }

            } catch (Throwable throwable) {
                logger.error("",throwable);
            }

           return result;
        }

        /**
         * 在方法调用前清除缓存,然后调用业务方法
         * @param joinPoint
         * @return
         * @throws Throwable
         *
         */
        @Around("cacheEvict()")
        public Object evictCache(ProceedingJoinPoint joinPoint) throws Throwable {
            // 得到被代理的方法
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            //得到被切面修饰的方法的参数列表
            Object[] args = joinPoint.getArgs();
            // 得到被代理的方法上的注解
            CacheEvict ce = method.getAnnotation(CacheEvict.class);
            //获得经过el解析后的key值
            String key = parseKey(ce.key(),method,args);
            //从注解中获取缓存名称
            String name = ce.value();
            // 清除对应缓存
            cacheFactory.cacheDel(name,key);
            return joinPoint.proceed(args);
        }

        /**
         * 获取缓存的key
         * key 定义在注解上,支持SPEL表达式
         * @return
         */
        private String parseKey(String key,Method method,Object [] args){

            if(StringUtils.isEmpty(key)) return null;

            //获取被拦截方法参数名列表(使用Spring支持类库)
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paraNameArr = u.getParameterNames(method);

            //使用SPEL进行key的解析
            ExpressionParser parser = new SpelExpressionParser();
            //SPEL上下文
            StandardEvaluationContext context = new StandardEvaluationContext();
            //把方法参数放入SPEL上下文中
            for(int i=0;i<paraNameArr.length;i++){
                context.setVariable(paraNameArr[i], args[i]);
            }
            return parser.parseExpression(key).getValue(context,String.class);
        }

        //序列化
        private String serialize(Object obj) {

            String result = null;
            try {
                result = JsonUtil.serialize(obj);
            } catch(Exception e) {
                result = obj.toString();
            }
            return result;

        }

        //反序列化
        private Object deserialize(String str,Class clazz) {

            Object result = null;
            try {
                if(clazz == JSONObject.class) {
                    result = new JSONObject(str);
                } else if(clazz == JSONArray.class) {
                    result = new JSONArray(str);
                } else {
                    result = JsonUtil.deserialize(str,clazz);
                }
            } catch(Exception e) {
            }
            return result;

        }

        //反序列化,支持List<xxx>
        private Object deserialize(String str,Class clazz,Class elementClass) {

            Object result = null;
            try {
                if(clazz == JSONObject.class) {
                    result = new JSONObject(str);
                } else if(clazz == JSONArray.class) {
                    result = new JSONArray(str);
                } else {
                    result = JsonUtil.deserialize(str,clazz,elementClass);
                }
            } catch(Exception e) {
            }
            return result;

        }

        public void setCacheEnable(boolean cacheEnable) {
            this.cacheEnable = cacheEnable;
        }

    }

  上面这个界面使用了一个cacheEnable变量控制是否使用缓存,为了实现无缝的接入springboot,必然需要受到原生@EnableCaching注解的控制,这里我使用一个spring容器加载完成的监听器,然后在监听器里找到是否有被@EnableCaching注解修饰的类,如果有就从spring容器拿到MultiCacheAspect对象,然后将cacheEnable设置成true。这样就可以实现无缝接入springboot,不知道朋友们还有没有更加优雅的方法呢?欢迎交流!监听器类如下

    import com.xuanwu.apaas.core.multicache.CacheFactory;
    import com.xuanwu.apaas.core.multicache.MultiCacheAspect;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.event.ContextRefreshedEvent;
    import org.springframework.stereotype.Component;

    import java.util.Map;

    /**
     * 用于spring加载完成后,找到项目中是否有开启缓存的注解@EnableCaching
     * @author rongdi
     */
    @Component
    public class ContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {

        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            // 判断根容器为Spring容器,防止出现调用两次的情况(mvc加载也会触发一次)
            if(event.getApplicationContext().getParent()==null){
                //得到所有被@EnableCaching注解修饰的类
                Map<String,Object> beans = event.getApplicationContext().getBeansWithAnnotation(EnableCaching.class);
                if(beans != null && !beans.isEmpty()) {
                    MultiCacheAspect multiCache = (MultiCacheAspect)event.getApplicationContext().getBean("multiCacheAspect");
                    multiCache.setCacheEnable(true);
                }

            }
        }
    }

  实现了无缝接入,还需要考虑多点部署的时候,多点的ehcache怎么和redis缓存保持一致的问题。在正常应用中,一般redis适合长时间的集中式缓存,ehcache适合短时间的本地缓存,假设现在有A,B和C服务器,A和B部署了业务服务,C部署了redis服务。当请求进来,前端入口不管是用LVS或者nginx等负载软件,请求都会转发到某一个具体服务器,假设转发到了A服务器,修改了某个内容,而这个内容在redis和ehcache中都有,这时候,A服务器的ehcache缓存,和C服务器的redis不管控制缓存失效也好,删除也好,都比较容易,但是这时候B服务器的ehcache怎么控制失效或者删除呢?一般比较常用的方式就是使用发布订阅模式,当需要删除缓存的时候在一个固定的通道发布一个消息,然后每个业务服务器订阅这个通道,收到消息后删除或者过期本地的ehcache缓存(最好是使用过期,但是redis目前只支持对key的过期操作,没办法操作key下的map里的成员的过期,如果非要强求用过期,可以自己加时间戳自己实现,不过用删除出问题的几率也很小,毕竟加缓存的都是读多写少的应用,这里为了方便都是直接删除缓存)。总结起来流程就是更新某条数据,先删除redis中对应的缓存,然后发布一个缓存失效的消息在redis的某个通道中,本地的业务服务去订阅这个通道的消息,当业务服务收到这个消息后去删除本地对应的ehcache缓存,redis的各种配置如下

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xuanwu.apaas.core.multicache.subscriber.MessageSubscriber;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MultiCacheConfig {

   @Bean
   public CacheManager cacheManager(RedisTemplate redisTemplate) {
      RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
      //设置缓存过期时间(秒)
      Map<String, Long> expires = new HashMap<>();
      expires.put("ExpOpState",0L);
      expires.put("ImpOpState",0L);
      rcm.setExpires(expires);
      rcm.setDefaultExpiration(600);
      return rcm;
   }

   @Bean
   public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
      StringRedisTemplate template = new StringRedisTemplate(factory);
      StringRedisSerializer redisSerializer = new StringRedisSerializer();
      template.setValueSerializer(redisSerializer);
      template.afterPropertiesSet();
      return template;
   }

   /**
    * redis消息监听器容器
    * 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
    * 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
    * @param connectionFactory
    * @param listenerAdapter
    * @return
    */
   @Bean
   public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                  MessageListenerAdapter listenerAdapter) {
      RedisMessageListenerContainer container = new RedisMessageListenerContainer();
      container.setConnectionFactory(connectionFactory);
      //订阅了一个叫redis.uncache的通道
      container.addMessageListener(listenerAdapter, new PatternTopic("redis.uncache"));
      //这个container 可以添加多个 messageListener
      return container;
   }

   /**
    * 消息监听器适配器,绑定消息处理器,利用反射技术调用消息处理器的业务方法
    * @param receiver
    * @return
    */
   @Bean
   MessageListenerAdapter listenerAdapter(MessageSubscriber receiver) {
      //这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“handle”
      return new MessageListenerAdapter(receiver, "handle");
   }

}

消息发布类如下:

    import com.xuanwu.apaas.core.multicache.CacheFactory;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;

    @Component
    public class MessageSubscriber {

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

        @Autowired
        private CacheFactory cacheFactory;

        /**
         * 接收到redis订阅的消息后,将ehcache的缓存失效
         * @param message 格式为name_key
         */
        public void handle(String message){

            logger.debug("redis.ehcache:"+message);
            if(StringUtils.isEmpty(message)) {
               return;
            }
            String[] strs = message.split("#");
            String name = strs[0];
            String key = null;
            if(strs.length == 2) {
                key = strs[1];
            }
            cacheFactory.ehDel(name,key);

        }

    }

  具体操作缓存的类如下:

import com.xuanwu.apaas.core.multicache.publisher.MessagePublisher;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.InputStream;

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

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

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MessagePublisher messagePublisher;

    private CacheManager cacheManager;

    public CacheFactory() {
        InputStream is = this.getClass().getResourceAsStream("/ehcache.xml");
        if(is != null) {
            cacheManager = CacheManager.create(is);
        }
    }

    public void cacheDel(String name,String key) {
        //删除redis对应的缓存
        redisDel(name,key);
        //删除本地的ehcache缓存,可以不需要,订阅器那里会删除
     //   ehDel(name,key);
        if(cacheManager != null) {
            //发布一个消息,告诉订阅的服务该缓存失效
            messagePublisher.publish(name, key);
        }
    }

    public String ehGet(String name,String key) {
        if(cacheManager == null) return null;
        Cache cache=cacheManager.getCache(name);
        if(cache == null) return null;
        cache.acquireReadLockOnKey(key);
        try {
            Element ele = cache.get(key);
            if(ele == null) return null;
            return (String)ele.getObjectValue();
        } finally {
            cache.releaseReadLockOnKey(key);
        }

    }

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

    public void ehPut(String name,String key,String value) {
        if(cacheManager == null) return;
        if(!cacheManager.cacheExists(name)) {
            cacheManager.addCache(name);
        }
        Cache cache=cacheManager.getCache(name);
        //获得key上的写锁,不同key互相不影响,类似于synchronized(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)

相关推荐

  • Springboot Caffeine本地缓存使用示例

    Caffeine是使用Java8对Guava缓存的重写版本性能有很大提升 一 依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- caffeine --> <dependency> <groupId&

  • SpringBoot中Shiro缓存使用Redis、Ehcache的方法

    SpringBoot 中配置redis作为session 缓存器. 让shiro引用 本文是建立在你是使用这shiro基础之上的补充内容 第一种:Redis缓存,将数据存储到redis 并且开启session存入redis中. 引入pom <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifac

  • SpringBoot2整合Redis缓存三步骤代码详解

    遵循SpringBoot三板斧 第一步加依赖 <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- redis依赖commons-pool 这个依赖一定要添加 --> <

  • SpringBoot + Mybatis-plus实战之Mybatis-plus的一级缓存、二级缓存

    前言 现在的JAVA行业,貌似已经是SpringBoot + SpringCloud 的天下了,早期的SSH,SSM框架已经老去,与SpringBoot相结合的JPA框架虽然省去了很多的增删改查sql,但是比较笨拙,在面对一些复杂多变的逻辑时常常力不从心,而相对应的Mybatis由于其高度的灵活性受到广大JAVA攻城狮的欢迎.之前整合过了springboot+mybatis,前几天看到一个面试的问一个问题,Mybatis的一级缓存,二级缓存.我想这个应该也是一个重点吧,所以今天决定来详细解读一下

  • SpringBoot中默认缓存实现方案的示例代码

    在上一节中,我带大家学习了在Spring Boot中对缓存的实现方案,尤其是结合Spring Cache的注解的实现方案,接下来在本章节中,我带大家通过代码来实现. 一. Spring Boot实现默认缓存 1. 创建web项目 我们按照之前的经验,创建一个web程序,并将之改造成Spring Boot项目,具体过程略. 2. 添加依赖包 <dependency> <groupId>org.springframework.boot</groupId> <artif

  • 详解SpringBoot2.0的@Cacheable(Redis)缓存失效时间解决方案

    问题   @Cacheable注解不支持配置过期时间,所有需要通过配置CacheManneg来配置默认的过期时间和针对每个类或者是方法进行缓存失效时间配置. 解决   可以采用如下的配置信息来解决的设置失效时间问题 配置信息 @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return new RedisCacheManager( RedisCacheWriter.no

  • SpringBoot+SpringCache实现两级缓存(Redis+Caffeine)

    1. 缓存.两级缓存 1.1 内容说明 Spring cache:主要包含spring cache定义的接口方法说明和注解中的属性说明 springboot+spring cache:rediscache实现中的缺陷 caffeine简介 spring boot+spring cache实现两级缓存 使用缓存时的流程图 1.2 Sping Cache spring cache是spring-context包中提供的基于注解方式使用的缓存组件,定义了一些标准接口,通过实现这些接口,就可以通过在方法

  • springboot中redis的缓存穿透问题实现

    什么是缓存穿透问题?? 我们使用redis是为了减少数据库的压力,让尽量多的请求去承压能力比较大的redis,而不是数据库.但是高并发条件下,可能会在redis还没有缓存的时候,大量的请求同时进入,导致一大批的请求直奔数据库,而不会经过redis.使用代码模拟缓存穿透问题如下: 首先是service里面的代码: @Service public class NewsService { @Autowired private NewsDAO newsDAO; //springboot自动初始化,不需要

  • 详解SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)

    引言 ​前两天在写一个实时数据处理的项目,项目要求是 1s 要处理掉 1k 的数据,这时候显然光靠查数据库是不行的,技术选型的时候老大跟我提了一下使用 Layering-Cache 这个开源项目来做缓存框架. ​之间问了一下身边的小伙伴,似乎对这块了解不多.一般也就用用 Redis 来缓存,应该是很少用多级缓存框架来专门性的管理缓存吧. ​趁着这个机会,我多了解了一些关于 SpringBoot 中缓存的相关技术,于是有了这篇文章! 在项目性能需求比较高时,就不能单单依赖数据库访问来获取数据了,必

  • SpringBoot加入Guava Cache实现本地缓存代码实例

    这篇文章主要介绍了SpringBoot加入Guava Cache实现本地缓存代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在pom.xml中加入guava依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version>

  • SpringBoot2.3整合redis缓存自定义序列化的实现

    1.引言 我们使用redis作为缓存中间件时,当我们第一次查询数据的时候,是去数据库查询,然后查到的数据封装到实体类中,实体类会被序列化存入缓存中,当第二次查数据时,会直接去缓存中查找被序列化的数据,然后反序列化被我们获取.我们在缓存中看到的序列化数据不直观,如果想看到类似json的数据格式,就需要自定义序列化规则. 2.整合redis pom.xml: <!--引入redis--> <dependency> <groupId>org.springframework.d

随机推荐