欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis結(jié)合AOP與自定義注解實現(xiàn)分布式緩存流程詳解

 更新時間:2022年11月21日 10:55:46   作者:小學生波波  
項目中如果查詢數(shù)據(jù)是直接到MySQL數(shù)據(jù)庫中查詢的話,會查磁盤走IO,效率會比較低,所以現(xiàn)在一般項目中都會使用緩存,目的就是提高查詢數(shù)據(jù)的速度,將數(shù)據(jù)存入緩存中,也就是內(nèi)存中,這樣查詢效率大大提高

1、背景

項目中如果查詢數(shù)據(jù)是直接到MySQL數(shù)據(jù)庫中查詢的話,會查磁盤走IO,效率會比較低,所以現(xiàn)在一般項目中都會使用緩存,目的就是提高查詢數(shù)據(jù)的速度,將數(shù)據(jù)存入緩存中,也就是內(nèi)存中,這樣查詢效率大大提高

分布式緩存方案

優(yōu)點:

  • 使用Redis作為共享緩存 ,解決緩存不同步問題
  • Redis是獨立的服務(wù),緩存不用占應(yīng)用本身的內(nèi)存空間

什么樣的數(shù)據(jù)適合放到緩存中呢?

同時滿足下面兩個條件的數(shù)據(jù)就適合放緩存:

  • 經(jīng)常要查詢的數(shù)據(jù)
  • 不經(jīng)常改變的數(shù)據(jù)

接下來我們使用 AOP技術(shù) 來實現(xiàn)分布式緩存,這樣做的好處是避免重復代碼,極大減少了工作量

2、目標

我們希望分布式緩存能幫我們達到這樣的目標:

  • 對業(yè)務(wù)代碼無侵入(或侵入性較小)
  • 使用起來非常方便,最好是打一個注解就可以了,可插拔式的
  • 對性能影響盡可能的小
  • 要便于后期維護

3、方案

此處我們選擇的方案就是:AOP+自定義注解+Redis

  • 自定義一個注解,需要做緩存的接口打上這個注解即可
  • 使用Spring AOP的環(huán)繞通知增強被自定義注解修飾的方法,把緩存的存儲和刪除都放這里統(tǒng)一處理
  • 那么需要用到分布式鎖的接口,只需要打一個注解即可,這樣才夠靈活優(yōu)雅

4、實戰(zhàn)編碼

4.1、環(huán)境準備

首先我們需要一個簡單的SpringBoot項目環(huán)境,這里我寫了一個基礎(chǔ)Demo版本,地址如下:

https://gitee.com/colinWu_java/spring-boot-base.git

大家可以先下載下來,本文就是基于這份主干代碼進行修改的

4.2、pom依賴

pom.xml中需要新增以下依賴:

<!-- aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--jackson-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.5.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.11.1</version>
</dependency>

4.3、自定義注解

添加緩存的注解

package org.wujiangbo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @desc 自定義注解:向緩存中添加數(shù)據(jù)
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCache {
    String cacheNames() default "";
    String key() default "";
    //緩存時間(單位:秒,默認是無限期)
    int time() default -1;
}

刪除緩存注解:

package org.wujiangbo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @desc 自定義注解:從緩存中刪除數(shù)據(jù)
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCacheEvict {
    String cacheNames() default "";
    String key() default "";
}

4.4、切面處理類

下面兩個切面類實際上是可以寫在一個類中的,但是為了方便理解和觀看,我分開寫了

package org.wujiangbo.aop;

import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.wujiangbo.annotation.MyCache;
import org.wujiangbo.service.RedisService;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
 * @desc 切面類,處理分布式緩存添加功能
 */
@Aspect
@Component
@Slf4j
public class MyCacheAop {
    @Resource
    private RedisService redisService;
    /**
     * 定義切點
     */
    @Pointcut("@annotation(myCache)")
    public void pointCut(MyCache myCache){
    }
    /**
     * 環(huán)繞通知
     */
    @Around("pointCut(myCache)")
    public Object around(ProceedingJoinPoint joinPoint, MyCache myCache) {
        String cacheNames = myCache.cacheNames();
        String key = myCache.key();
        int time = myCache.time();
        /**
         * 思路:
         * 1、拼裝redis中存緩存的key值
         * 2、看redis中是否存在該key
         * 3、如果存在,直接取出來返回即可,不需要執(zhí)行目標方法了
         * 4、如果不存在,就執(zhí)行目標方法,然后將緩存放一份到redis中
         */
        String redisKey = new StringBuilder(cacheNames).append(":").append(key).toString();
        String methodPath = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
        Object result ;
        if (redisService.exists(redisKey)){
            log.info("訪問接口:[{}],直接從緩存獲取數(shù)據(jù)", methodPath);
            return redisService.getCacheObject(redisKey);
        }
        try {
            //執(zhí)行接口
            result = joinPoint.proceed();
            //接口返回結(jié)果存Redis
            redisService.setCacheObject(redisKey, result, time, TimeUnit.SECONDS);
            log.info("訪問接口:[{}],返回值存入緩存成功", methodPath);
        } catch (Throwable e) {
            log.error("發(fā)生異常:{}", e);
            throw new RuntimeException(e);
        }
        return result;
    }
}

還有一個:

package org.wujiangbo.aop;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.wujiangbo.annotation.MyCacheEvict;
import org.wujiangbo.service.RedisService;
import javax.annotation.Resource;
/**
 * @desc 切面類,處理分布式緩存刪除功能
 */
@Aspect
@Component
@Slf4j
public class MyCacheEvictAop {
    @Resource
    private RedisService redisService;
    /**
     * 定義切點
     */
    @Pointcut("@annotation(myCache)")
    public void pointCut(MyCacheEvict myCache){
    }
    /**
     * 環(huán)繞通知
     */
    @Around("pointCut(myCache)")
    public Object around(ProceedingJoinPoint joinPoint, MyCacheEvict myCache) {
        String cacheNames = myCache.cacheNames();
        String key = myCache.key();
        /**
         * 思路:
         * 1、拼裝redis中存緩存的key值
         * 2、刪除緩存
         * 3、執(zhí)行目標接口業(yè)務(wù)代碼
         * 4、再刪除緩存
         */
        String redisKey = new StringBuilder(cacheNames).append(":").append(key).toString();
        String methodPath = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
        Object result ;
        //刪除緩存
        redisService.deleteObject(redisKey);
        try {
            //執(zhí)行接口
            result = joinPoint.proceed();
            //刪除緩存
            redisService.deleteObject(redisKey);
            log.info("訪問接口:[{}],緩存刪除成功", methodPath);
        } catch (Throwable e) {
            log.error("發(fā)生異常:{}", e);
            throw new RuntimeException(e);
        }
        return result;
    }
}

4.5、工具類

Redis的工具類:

package org.wujiangbo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * @desc Redis工具類
 */
@Component  //交給Spring來管理 的自定義組件
public class RedisService {
    @Autowired
    public RedisTemplate redisTemplate;
    /**
     * 查看key是否存在
     */
    public boolean exists(String key)
    {
        return redisTemplate.hasKey(key);
    }
    /**
     * 清空Redis所有緩存數(shù)據(jù)
     */
    public void clearAllRedisData()
    {
        Set<String> keys = redisTemplate.keys("*");
        redisTemplate.delete(keys);
    }
    /**
     * 緩存基本的對象,Integer、String、實體類等
     *
     * @param key 緩存的鍵值
     * @param value 緩存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * 緩存基本的對象,Integer、String、實體類等
     *
     * @param key 緩存的鍵值
     * @param value 緩存的值
     * @param timeout 時間
     * @param timeUnit 時間顆粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        if(timeout == -1){
            //永久有效
            redisTemplate.opsForValue().set(key, value);
        }
        else{
            redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
        }
    }
    /**
     * 設(shè)置有效時間
     *
     * @param key Redis鍵
     * @param timeout 超時時間
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }
    /**
     * 設(shè)置有效時間
     *
     * @param key Redis鍵
     * @param timeout 超時時間
     * @param unit 時間單位
     * @return true=設(shè)置成功;false=設(shè)置失敗
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }
    /**
     * 獲得緩存的基本對象。
     *
     * @param key 緩存鍵值
     * @return 緩存鍵值對應(yīng)的數(shù)據(jù)
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
    /**
     * 刪除單個對象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        if(exists(key)){
            redisTemplate.delete(key);
        }
        return true;
    }
    /**
     * 刪除集合對象
     *
     * @param collection 多個對象
     * @return
     */
    public long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }
    /**
     * 緩存List數(shù)據(jù)
     *
     * @param key 緩存的鍵值
     * @param dataList 待緩存的List數(shù)據(jù)
     * @return 緩存的對象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }
    /**
     * 獲得緩存的list對象
     *
     * @param key 緩存的鍵值
     * @return 緩存鍵值對應(yīng)的數(shù)據(jù)
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
    /**
     * 緩存Set
     *
     * @param key 緩存鍵值
     * @param dataSet 緩存的數(shù)據(jù)
     * @return 緩存數(shù)據(jù)的對象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }
    /**
     * 獲得緩存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }
    /**
     * 緩存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }
    /**
     * 獲得緩存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * 往Hash中存入數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKey Hash鍵
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }
    /**
     * 獲取Hash中的數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKey Hash鍵
     * @return Hash中的對象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }
    /**
     * 獲取多個Hash中的數(shù)據(jù)
     *
     * @param key Redis鍵
     * @param hKeys Hash鍵集合
     * @return Hash對象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }
    /**
     * 獲得緩存的基本對象列表
     *
     * @param pattern 字符串前綴
     * @return 對象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

4.6、配置類

package org.wujiangbo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.Resource;
/**
 * @desc redis配置類
 */
@Configuration
public class RedisSerializableConfig extends CachingConfigurerSupport {
    @Resource
    private RedisConnectionFactory factory;
    @Bean
    public RedisTemplate<Object, Object> redisTemplate()
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);
        // 使用StringRedisSerializer來序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
    @Bean
    public DefaultRedisScript<Long> limitScript()
    {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(limitScriptText());
        redisScript.setResultType(Long.class);
        return redisScript;
    }
    /**
     * 限流腳本
     */
    private String limitScriptText()
    {
        return "local key = KEYS[1]\n" +
                "local count = tonumber(ARGV[1])\n" +
                "local time = tonumber(ARGV[2])\n" +
                "local current = redis.call('get', key);\n" +
                "if current and tonumber(current) > count then\n" +
                "    return tonumber(current);\n" +
                "end\n" +
                "current = redis.call('incr', key)\n" +
                "if tonumber(current) == 1 then\n" +
                "    redis.call('expire', key, time)\n" +
                "end\n" +
                "return tonumber(current);";
    }
}

FastJson2JsonRedisSerializer類:

package org.wujiangbo.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
 * @desc Redis使用FastJson序列化
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    @SuppressWarnings("unused")
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Class<T> clazz;
    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }
    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }
    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }
    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }
    public void setObjectMapper(ObjectMapper objectMapper)
    {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }
    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

4.7、yml配置

server:
  port: 8001
  undertow:
    # 設(shè)置IO線程數(shù), 它主要執(zhí)行非阻塞的任務(wù),它們會負責多個連接, 默認設(shè)置每個CPU核心一個線程
    # 不要設(shè)置過大,如果過大,啟動項目會報錯:打開文件數(shù)過多(CPU有幾核,就填寫幾)
    io-threads: 6
    # 阻塞任務(wù)線程池, 當執(zhí)行類似servlet請求阻塞IO操作, undertow會從這個線程池中取得線程
    # 它的值設(shè)置取決于系統(tǒng)線程執(zhí)行任務(wù)的阻塞系數(shù),默認值是:io-threads * 8
    worker-threads: 48
    # 以下的配置會影響buffer,這些buffer會用于服務(wù)器連接的IO操作,有點類似netty的池化內(nèi)存管理
    # 每塊buffer的空間大小,越小的空間被利用越充分,不要設(shè)置太大,以免影響其他應(yīng)用,合適即可
    buffer-size: 1024
    # 每個區(qū)分配的buffer數(shù)量 , 所以pool的大小是buffer-size * buffers-per-region
    buffers-per-region: 1024
    # 是否分配的直接內(nèi)存(NIO直接分配的堆外內(nèi)存)
    direct-buffers: true
spring:
  #配置數(shù)據(jù)庫鏈接信息
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test1?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&rewriteBatchedStatements=true
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  application:
    name: springboot #服務(wù)名
  #redis配置
  redis:
    # 數(shù)據(jù)庫索引
    database: 0
    # 地址
    host: 127.0.0.1
    # 端口,默認為6379
    port: 6379
    # 密碼
    password: 123456
    # 連接超時時間
    timeout: 10000

#MyBatis-Plus相關(guān)配置
mybatis-plus:
  #指定Mapper.xml路徑,如果與Mapper路徑相同的話,可省略
  mapper-locations: classpath:org/wujiangbo/mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true #開啟駝峰大小寫自動轉(zhuǎn)換
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #開啟控制臺sql輸出

4.8、使用

Controller中寫兩個接口分別測試一下緩存的新增和刪除

package org.wujiangbo.controller;
import lombok.extern.slf4j.Slf4j;
import org.wujiangbo.annotation.CheckPermission;
import org.wujiangbo.annotation.MyCache;
import org.wujiangbo.annotation.MyCacheEvict;
import org.wujiangbo.result.JSONResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @desc 測試接口類
 */
@RestController
@Slf4j
public class TestController {
    //測試刪除緩存
    @GetMapping("/deleteCache")
    @MyCacheEvict(cacheNames = "cacheTest", key = "userData")
    public JSONResult deleteCache(){
        System.out.println("deleteCache success");
        return JSONResult.success("deleteCache success");
    }
    //測試新增緩存
    @GetMapping("/addCache")
    @MyCache(cacheNames = "cacheTest", key = "userData")
    public JSONResult addCache(){
        System.out.println("addCache success");
        return JSONResult.success("addCache success");
    }
}

4.9、測試

瀏覽器先訪問:http://localhost:8001/addCache

然后再通過工具查看Redis中是不是添加了緩存數(shù)據(jù),正確情況應(yīng)該是緩存添加進去了

然后再訪問:http://localhost:8001/deleteCache

再通過工具查看Redis,緩存應(yīng)該是被刪除了,沒有了

到此完全符合預期,測試成功

總結(jié)

本文主要是介紹了分布式緩存利用AOP+注解的方式處理,方便使用和擴展希望對大家有所幫助

最后本案例代碼已全部提交到gitee中了,地址如下:

https://gitee.com/colinWu_java/spring-boot-base.git

本文新增的代碼在【RedisDistributedCache】分支中

到此這篇關(guān)于Redis結(jié)合AOP與自定義注解實現(xiàn)分布式緩存流程詳解的文章就介紹到這了,更多相關(guān)Redis分布式緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論