springboot 에서 사용자 정의 2 급 캐 시 를 어떻게 사용 합 니까?

26981 단어 springboot캐 시
작업 중 에 springboot 캐 시 를 사 용 했 습 니 다.사용 하기에 편리 합 니 다.redis 나 ehcache 같은 캐 시 의존 패키지 와 관련 캐 시 된 starter 의존 패 키 지 를 직접 도입 한 다음 시작 클래스 에@EnableCashing 주 해 를 추가 한 다음 필요 한 곳 에서@Cacheable 과@CacheEvict 를 사용 하여 캐 시 를 사용 하고 삭제 할 수 있 습 니 다.이 사용 은 매우 간단 합 니 다.spring boot 캐 시 를 사용 한 사람 은 모두 놀 수 있 을 것 이 라 고 믿 습 니 다.여 기 는 더 이상 말 하지 않 겠 습 니 다.옥 에 티 는 springboot 이 플러그 인 식 통합 방식 을 사용 하여 사용 하기에 편리 하지만 ehcache 를 통합 할 때 ehcache 를 사용 하고 redis 를 통합 할 때 redis 를 사용 합 니 다.두 가 지 를 함께 사용 하려 면 ehcache 는 로 컬 1 급 캐 시 로 서 redis 는 통합 식 2 급 캐 시 로 서 기본 적 인 방식 을 사용 합 니 다.제 가 알 기 로 는 실현 할 수 없 는 것 으로 알 고 있 습 니 다.많은 서 비 스 는 여러 가지 배 치 를 필요 로 하기 때문에 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 형식의 값 키 가 되 었 습 니 다.그리고 이 zset 안 은 비어 있 습 니 다.예 를 들 어@Cacheable(value="cache 1",key="key 1")과 같 습 니 다.정상 적 인 상황 에서 redis 에 cache 1->map(key 1,value 1)가 나타 나 야 합 니 다.그 중에서 cache 1 은 캐 시 이름 으로,map 는 캐 시 값 으로,key 는 map 의 키 로 서 서로 다른 캐 시 이름 의 캐 시 를 효과적으로 격 리 할 수 있 습 니 다.그러나 실제 redis 에 서 는 cache 1->빈(zset)과 key 1->value 1 이 있 습 니 다.두 개의 독립 된 키 값 이 맞 습 니 다.서로 다른 캐 시 이름 의 캐 시 는 완전히 공용 이라는 것 을 알 게 되 었 습 니 다.관심 이 있 는 친구 가 시험 해 볼 수 있 습 니 다.즉,이 value 속성 은 실제 적 으로 장식 이 고 키 의 유일 성 은 key 속성 에 의 해 만 보장 되 는 것 입 니 다.나 는 이것 이 spring 의 캐 시 에서 이 루어 진 bug 라 고 생각 하거나 특별히 이렇게 디자인 한 것 이 라 고 생각 할 수 밖 에 없다.
본론 으로 돌아 가면 주해 가 있 으 면 주해 처리 류 가 필요 합 니 다.여기 서 저 는 op 의 절단면 으로 차단 처 리 를 했 습 니 다.원생 의 실현 도 크게 다 르 지 않 습 니 다.절단면 처리 류 는 다음 과 같다.

    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;
        }

    }
위의 화면 은 캐 시 사용 여 부 를 캐 시 Enable 변수 로 제어 합 니 다.빈 틈 없 는 springboot 접속 을 위해 서 는 원생@EnableCaching 주석 에 의 해 제어 되 어야 합 니 다.여기 서 저 는 spring 용기 로 불 러 온 모니터 를 사용 한 다음 에 모니터 에서@EnableCaching 주석 에 의 해 수 정 된 클래스 가 있 는 지 찾 습 니 다.있 으 면 spring 용기 에서 MultiCache Aspect 대상 을 가 져 온 다음 cache Enable 을 true 로 설정 합 니 다.이렇게 하면 빈 틈 없 이 spring boot 에 접속 할 수 있 습 니 다.여러분 들 은 더 우아 한 방법 이 있 을 지 모 르 겠 습 니 다.교 류 를 환영 합 니 다!모니터 클래스 는 다음 과 같 습 니 다.

    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 에서 사용자 정의 2 급 캐 시 를 어떻게 사용 하 는 지 에 대한 상세 한 내용 입 니 다.springboot 에서 사용자 정의 2 급 캐 시 를 사용 하 는 것 에 관 한 자 료 는 다른 관련 글 을 주목 하 십시오!

좋은 웹페이지 즐겨찾기