AOP+自定义注解+Redis实现分布式缓存(经典)

167 篇文章 6 订阅

文章目录
1、背景
2、目标
3、方案
4、实战编码
4.1、环境准备
4.2、pom依赖
4.3、自定义注解
4.4、切面处理类
4.5、工具类
4.6、配置类
4.7、yml配置
4.8、使用
4.9、测试
总结
1、背景
项目中如果查询数据是直接到MySQL数据库中查询的话,会查磁盘走IO,效率会比较低,所以现在一般项目中都会使用缓存,目的就是提高查询数据的速度,将数据存入缓存中,也就是内存中,这样查询效率大大提高

分布式缓存方案

优点:

使用Redis作为共享缓存 ,解决缓存不同步问题
Redis是独立的服务,缓存不用占应用本身的内存空间
什么样的数据适合放到缓存中呢?

同时满足下面两个条件的数据就适合放缓存:

经常要查询的数据
不经常改变的数据
接下来我们使用 AOP技术 来实现分布式缓存,这样做的好处是避免重复代码,极大减少了工作量

2、目标
我们希望分布式缓存能帮我们达到这样的目标:

对业务代码无侵入(或侵入性较小)
使用起来非常方便,最好是打一个注解就可以了,可插拔式的
对性能影响尽可能的小
要便于后期维护
3、方案
此处我们选择的方案就是:AOP+自定义注解+Redis

自定义一个注解,需要做缓存的接口打上这个注解即可
使用Spring AOP的环绕通知增强被自定义注解修饰的方法,把缓存的存储和删除都放这里统一处理
那么需要用到分布式锁的接口,只需要打一个注解即可,这样才够灵活优雅
4、实战编码
4.1、环境准备
首先我们需要一个简单的SpringBoot项目环境,这里我写了一个基础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>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 自定义注解:向缓存中添加数据
 * @author 波波老师(微信:javabobo0513)
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCache {

    String cacheNames() default "";

    String key() default "";

    //缓存时间(单位:秒,默认是无限期)
    int time() default -1;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
删除缓存注解:

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 自定义注解:从缓存中删除数据
 * @author 波波老师(微信:javabobo0513)
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCacheEvict {

    String cacheNames() default "";

    String key() default "";
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 切面类,处理分布式缓存添加功能
 * @author 波波老师(微信:javabobo0513)
 */
@Aspect
@Component
@Slf4j
public class MyCacheAop {

    @Resource
    private RedisService redisService;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(myCache)")
    public void pointCut(MyCache myCache){
    }

    /**
     * 环绕通知
     */
    @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、如果存在,直接取出来返回即可,不需要执行目标方法了
         * 4、如果不存在,就执行目标方法,然后将缓存放一份到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("访问接口:[{}],直接从缓存获取数据", methodPath);
            return redisService.getCacheObject(redisKey);
        }
        try {
            //执行接口
            result = joinPoint.proceed();
            //接口返回结果存Redis
            redisService.setCacheObject(redisKey, result, time, TimeUnit.SECONDS);
            log.info("访问接口:[{}],返回值存入缓存成功", methodPath);
        } catch (Throwable e) {
            log.error("发生异常:{}", e);
            throw new RuntimeException(e);
        }
        return result;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
还有一个:

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 切面类,处理分布式缓存删除功能
 * @author 波波老师(微信:javabobo0513)
 */
@Aspect
@Component
@Slf4j
public class MyCacheEvictAop {

    @Resource
    private RedisService redisService;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(myCache)")
    public void pointCut(MyCacheEvict myCache){
    }

    /**
     * 环绕通知
     */
    @Around("pointCut(myCache)")
    public Object around(ProceedingJoinPoint joinPoint, MyCacheEvict myCache) {
        String cacheNames = myCache.cacheNames();
        String key = myCache.key();
        /**
         * 思路:
         * 1、拼装redis中存缓存的key值
         * 2、删除缓存
         * 3、执行目标接口业务代码
         * 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 {
            //执行接口
            result = joinPoint.proceed();
            //删除缓存
            redisService.deleteObject(redisKey);
            log.info("访问接口:[{}],缓存删除成功", methodPath);
        } catch (Throwable e) {
            log.error("发生异常:{}", e);
            throw new RuntimeException(e);
        }
        return result;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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工具类
 * @author 波波老师(微信:javabobo0513)
 */
@Component  //交给Spring来管理 的自定义组件
public class RedisService {

    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 查看key是否存在
     */
    public boolean exists(String key)
    {
        return redisTemplate.hasKey(key);
    }

    /**
     * 清空Redis所有缓存数据
     */
    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);
        }
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    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数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @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 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    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中存入数据
     *
     * @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中的数据
     *
     * @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中的数据
     *
     * @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);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
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配置类
 * @author 波波老师(微信:javabobo0513)
 */
@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);";
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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序列化
 * @author 波波老师(微信:javabobo0513)
 */
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);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
4.7、yml配置
server:
  port: 8001
  undertow:
    # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
    # 不要设置过大,如果过大,启动项目会报错:打开文件数过多(CPU有几核,就填写几)
    io-threads: 6
    # 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线程
    # 它的值设置取决于系统线程执行任务的阻塞系数,默认值是:io-threads * 8
    worker-threads: 48
    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
    # 每块buffer的空间大小,越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可
    buffer-size: 1024
    # 每个区分配的buffer数量 , 所以pool的大小是buffer-size * buffers-per-region
    buffers-per-region: 1024
    # 是否分配的直接内存(NIO直接分配的堆外内存)
    direct-buffers: true
spring:
  #配置数据库链接信息
  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 #服务名
  #redis配置
  redis:
    # 数据库索引
    database: 0
    # 地址
    host: 127.0.0.1
    # 端口,默认为6379
    port: 6379
    # 密码
    password: 123456
    # 连接超时时间
    timeout: 10000

#MyBatis-Plus相关配置
mybatis-plus:
  #指定Mapper.xml路径,如果与Mapper路径相同的话,可省略
  mapper-locations: classpath:org/wujiangbo/mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true #开启驼峰大小写自动转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启控制台sql输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 测试接口类
 * @author 波波老师(weixin:javabobo0513)
 */
@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");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
4.9、测试
浏览器先访问:http://localhost:8001/addCache

然后再通过工具查看Redis中是不是添加了缓存数据,正确情况应该是缓存添加进去了

然后再访问:http://localhost:8001/deleteCache

再通过工具查看Redis,缓存应该是被删除了,没有了

到此完全符合预期,测试成功

总结
本文主要是介绍了分布式缓存利用AOP+注解的方式处理,方便使用和扩展
希望对大家有所帮助
最后本案例代码已全部提交到gitee中了,地址如下:

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

本文新增的代码在【RedisDistributedCache】分支中
————————————————
版权声明:本文为CSDN博主「小学生波波」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wujiangbo520/article/details/127656537

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用 Redisson 实现分布式锁,具体实现如下: 1. 引入 Redisson 依赖: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.16.1</version> </dependency> ``` 2. 定义自定义注解 `DistributedLock`: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DistributedLock { String value() default ""; long leaseTime() default 30L; TimeUnit timeUnit() default TimeUnit.SECONDS; } ``` 3. 在需要加锁的方法上加上 `@DistributedLock` 注解: ```java @Service public class UserService { @DistributedLock("user:#{#userId}") public User getUserById(String userId) { // ... } } ``` 4. 实现 `DistributedLockAspect` 切面: ```java @Aspect @Component public class DistributedLockAspect { private final RedissonClient redissonClient; public DistributedLockAspect(RedissonClient redissonClient) { this.redissonClient = redissonClient; } @Around("@annotation(distributedLock)") public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable { String lockKey = distributedLock.value(); if (StringUtils.isEmpty(lockKey)) { lockKey = joinPoint.getSignature().toLongString(); } RLock lock = redissonClient.getLock(lockKey); boolean locked = lock.tryLock(distributedLock.leaseTime(), distributedLock.timeUnit()); if (!locked) { throw new RuntimeException("获取分布式锁失败"); } try { return joinPoint.proceed(); } finally { lock.unlock(); } } } ``` 5. 在 `application.yml` 中配置 Redisson: ```yaml spring: redis: host: localhost port: 6379 password: database: 0 redisson: single-server-config: address: redis://${spring.redis.host}:${spring.redis.port} ``` 这样就实现了一个基于 Redis分布式锁。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值