91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在springboot中自定義兩級緩存

發布時間:2021-05-17 17:14:04 來源:億速云 閱讀:132 作者:Leah 欄目:編程語言

本篇文章給大家分享的是有關怎么在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;

  @Configuration
  public class RedisConfig {

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
     RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
     //設置緩存過期時間(秒)
     rcm.setDefaultExpiration(600);
     return rcm;
    }


    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
     StringRedisTemplate template = new StringRedisTemplate(factory);
     Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
     ObjectMapper om = new ObjectMapper();
     om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
     om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
     jackson2JsonRedisSerializer.setObjectMapper(om);
     template.setValueSerializer(jackson2JsonRedisSerializer);
     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;
      if(cacheManager.cacheExists(name)) {
        //如果key為空,直接根據緩存名刪除
        if(StringUtils.isEmpty(key)) {
          cacheManager.removeCache(name);
        } else {
          Cache cache=cacheManager.getCache(name);
          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")

附上主要的依賴包

  1. "org.springframework.boot:spring-boot-starter-redis:1.4.2.RELEASE",

  2. 'net.sf.ehcache:ehcache:2.10.4',

  3. "org.json:json:20160810"

springboot是什么

springboot一種全新的編程規范,其設計目的是用來簡化新Spring應用的初始搭建以及開發過程,SpringBoot也是一個服務于框架的框架,服務范圍是簡化配置文件。

以上就是怎么在springboot中自定義兩級緩存,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

定西市| 永春县| 嫩江县| 上犹县| 新野县| 东港市| 大厂| 开远市| 普宁市| 乐清市| 嘉义市| 利川市| 靖州| 封开县| 都兰县| 太仓市| 贺州市| 札达县| 堆龙德庆县| 镇原县| 盘锦市| 岳阳县| 蛟河市| 石屏县| 同德县| 松潘县| 小金县| 红原县| 无棣县| 南召县| 宾川县| 共和县| 杭锦后旗| 牡丹江市| 安吉县| 西乌| 青龙| 泌阳县| 伊通| 九寨沟县| 维西|