Spring 中缓存注解的使用


CacheAnnotation

在 SpringBoot 中使用缓存注解, 其原理是借助于 AOP (动态代理) 对目标方法进行增强。

@CacheConfig

抽取缓存的公共配置, 只能在类上声明。方法上的同名属性会覆盖类上的配置。

@Cacheable

在调用方法之前,首先在缓存中查找方法的返回值,如果存在,直接返回缓存中的值,否则执行该方法,并将返回值保存到缓存中
和 @CachePut 联合使用时, 会强制读取数据库中的数据

value/cacheNames: 该属性值必须提供,用于指定缓存组的名字,可以指定多个缓存组名称

key: 缓存数据使用的 key, 一条数据的名称由缓存组和 key 所组成,不指定 key 则默认是使用方法参数的值,该属性值支持SpEL表达式 (详情可查阅源码注释)

keyGenerator: 可为空, 指定 key 名称生成器, 当 key 属性未设置时根据生成器规则生成缓存的 key

cacheManager: 可为空, 指定缓存管理器, 用于创建 cacheResolver

cacheResolver: 可为空, 指定获取解析器, 不能与 cacheManager 同时设置

condition:可为空, 指定符合条件的情况下才启用缓存, 当判断结果为 false 时该缓存注解将不启用, 支持SpEL表达式

unless: 可为空, 指定是否不保存缓存, 当判断结果为 true 时将放弃对返回值进行缓存

sync: 默认值为 false, 指定是否以同步的方式操作目标缓存

@CachePut

执行目标方法,并将返回值进行缓存

@CacheEvict

删除指定的缓存数据

allEntries: 默认值为 false, 是否删除缓存组中所有的 key, 为 true 时, 注解中将不能设置 key 属性

beforeInvocation: 默认值为 false, 是否在方法执行器删除缓存数据, 当为 true 时无论方法是否成功执行, 都会删除缓存数据

@CacheIng

分组注解, 可以在该注解中声明 cacheable, CachePut 和 CacheEvict 属性

SpEL

root.method

引用目标方法对象

root.target

引用目标执行对象

root.caches

引用受影响的缓存

root.methodName

引用目标方法名称

root.targetClass

引用目标执行对象类名

root.args[1], #p1, #a1

引用参数列表中的第 2 个参数, 或者使用 #参数名

result

引用返回值对象

注解功能实现

以下代码可以实现与 @Cacheable 相同效果的缓存功能 (需要先配置 Redis 数据源), 同理 @CachePut, @CacheEvict 原理也大致相同。

package com.xtyuns.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Aspect
@Component
public class CacheAspect {
    @Autowired
    private RedisTemplate redisTemplate;

    // 使用 Jackson 序列化缓存键值属性
    @PostConstruct
    private void init() {
        this.redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer());
        this.redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    }

    @Around("execution(* com.xtyuns.service.impl.UserServiceImpl.selectByPrimaryKey(..))")
    public Object cacheGetUser(ProceedingJoinPoint pjp) throws Throwable {
        Object id = pjp.getArgs()[0];
        Object o = this.redisTemplate.opsForValue().get(id);
        // 如果缓存中存在数据, 则直接返回
        if (null != o) return o;
        
        // 未命中缓存, 执行数据库查询并将数据放入缓存
        Object result = pjp.proceed();
        this.redisTemplate.opsForValue().set(id, result);
        return result;
    }
}