spring cache集成redis快速入门(含源码解读)

    spring集成cache支持对缓存进行处理,spring cache支持多种缓存实现,本文对缓存实现方案中的redis操作进行说明,期间会对应源码进行解读.如果对源码不感兴趣的同学可以忽略,仅关注具体使用即可.
    1.案例demo
        1.1 需要添加依赖
        1.2 redis配置文件
        1.3 启动类需要添加的注解@EnableCaching
        1.4 业务代码
    2.常用注解(@Cacheable、@CachePut、CacheEvict)使用说明以及注解属性源码解读
    3.注意事项

1.案例demo

1.1 需要添加的依赖

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

1.2 redis配置文件

@EnableCaching
@Configuration
public class RedisConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {

        RedisCacheManager redisCacheManager = new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                // 设置缓存的有效时间,单位秒
                this.getRedisCacheConfigurationWithTtl(600),
                // 指定 key 策略
                this.getRedisCacheConfigurationMap()
        );
        redisCacheManager.setTransactionAware(true);
        return redisCacheManager;
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16);
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {

        // 指定使用GenericJackson2JsonRedisSerializer序列化方式
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(genericJackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }
}

1.3 启动类需要添加的注解@EnableCaching

@EnableCaching
@SpringBootApplication
@MapperScan("com.chenghao.program.chenghaoprogram")
public class ChenghaoProgramApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(ChenghaoProgramApplication.class, args);
	}

}

1.4 业务代码

@RequestMapping("/redis")
@RestController
public class TestSpringCacheRedisController {

    @Autowired
    private TestSpringCacheRedisServiceImp testSpringCacheRedisService;

    @GetMapping("/findPersonList")
    public void findPersonList(){
        List<Person> personList = testSpringCacheRedisService.findPersonList();
        System.out.println(personList);
    }

    @GetMapping("/findPersonInfo")
    public void findPersonInfo(String name){
        Person personInfo = testSpringCacheRedisService.findPersonInfo(name);
        System.out.println(personInfo);
    }

    @PostMapping
    public void updatePersonalInfo(String name){
        testSpringCacheRedisService.updatePersonalInfo(name);
        System.out.println("修改完成");
    }

    @DeleteMapping
    public void deletePersonalInfo(String name){
        testSpringCacheRedisService.deletePersonalInfo(name);
        System.out.println("删除完成");
    }

}

逻辑层:

public interface TestSpringCacheRedisService {

    // 查询人员信息
    List<Person> findPersonList();

    // 根据姓名查询用户信息
    Person findPersonInfo(String name);

    // 更新用户信息
    Person updatePersonalInfo(String name);

    // 删除用户信息
    void deletePersonalInfo(String name);

}
@Service
public class TestSpringCacheRedisServiceImp implements TestSpringCacheRedisService {

    @Cacheable(value = "personList",key = "#root.methodName",unless = "#result.size() == 3")
    @Override
    public List<Person> findPersonList() {

        int a=0;

        List<Person> PersonList = Arrays.asList(new Person(13, "张三"),
                new Person(14, "李四"));
        return PersonList;
    }

    // key定义方式:"#参数名"或者"#p参数index"
    @Cacheable(value = "personalInfo",key = "#name",condition = "#a0 != \"王五\"")
    @Override
    public Person findPersonInfo(String name) {
        int a=0;

        // 模拟数据库获取数据
        Person person=null;
        if("张三".equals(name)){
            person = new Person(13, "张三");
        }else if("李四".equals(name)){
            person = new Person(14, "李四");
        }else if("王五".equals(name)){
            person = new Person(15, "王五");
        }

        return person;
    }

    @CachePut(value = "personalInfo",key = "#name")
    @Override
    public Person updatePersonalInfo(String name) {
        int a=0;
        // 模拟数据库修改数据,修改张三的年龄
        Person person=null;
        if("张三".equals(name)){
            person = new Person(23, "张三");
        }

        // 主要要将修改后的对象信息进行返回,否则会将之前的缓存value信息置为null
        return person;
    }

    @CacheEvict(value = "personalInfo",key = "#name",beforeInvocation = true)
    @Override
    public void deletePersonalInfo(String name) {
        System.out.println("模拟执行数据库删除操作");
        int a=1/0;
    }
}

实体类:

public class Person implements Serializable {
    private int age;
    private String name;

    public Person() {
    }

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

	// 省略get/set以及tostring
}

2.常用注解(@Cacheable、@CachePut、CacheEvict)使用说明以及注解属性源码解读

2.1三个注解属性对照表
    @Caching为三个注解的组合,在此不做讲解
在这里插入图片描述
2.2常用的spel表达式(很多属性支持spel表达式):
    #root.method 或#root.methodName:获取当前请求方法或方法名;root对应CacheExpressionRootObject;
    #root.target或#root.targetClass: 获取当前方法请求目标对象或是目标class对象;
    #root.caches: 获取当缓存cache列表.
    #root.args[1]或#p1 或#a1:获取请求方法中参数信息。
    下面对各个属性进行介绍:
    2.3 cacheNames和value作用:标识缓存cache的命名空间,即缓存的名称。value是CacheNames的别名,两者作用相同。
spring cache集成redis快速入门(含源码解读)_第1张图片

    2.4 key作用:指定缓存数据的key值,默认会按照key生成策略生成,支持spel表达式。
demo中方法缓存的的key值说明:

// 缓存的key值为findPersonList,value为PersonList集合
 @Cacheable(value = "personList",key = "#root.methodName",unless = "#result.size() == 3")
    @Override
    public List<Person> findPersonList() {
        List<Person> PersonList = Arrays.asList(new Person(13, "张三"),
                new Person(14, "李四"));
        return PersonList;
    }
// 缓存的key值为传入name的实参,value为person对象
@Cacheable(value = "personalInfo",key = "#name",condition = "#a0 != \"王五\"")
    @Override
    public Person findPersonInfo(String name) {
       // 模拟数据库获取数据
        Person person=null;
        if("张三".equals(name)){
            person = new Person(13, "张三");
        }else if("李四".equals(name)){
            person = new Person(14, "李四");
        }else if("王五".equals(name)){
            person = new Person(15, "王五");
        }
        return person;
    }

对应源码:CacheAspectSupport.java中generateKey

        protected Object generateKey(@Nullable Object result) {
        // 如果key属性中指定spel表达式,则按照解析后的spel表达式生成key,否则按照默认的策略进行生成
            if (StringUtils.hasText(this.metadata.operation.getKey())) {
                EvaluationContext evaluationContext = this.createEvaluationContext(result);
                return CacheAspectSupport.this.evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
            } else {
                return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
            }
        }

    2.5 keyGenerator作用:缓存数据key的生成器。有两种实现
spring cache集成redis快速入门(含源码解读)_第2张图片

    cacheManager作用:配置cacheManager,可指定序列化方式和缓存ttl有效期等配置信息.
    cacheResolver作用:注解解析会初始化CacheOperationContext,其中caches为当前的缓存列表信息,就是通过cacheResolver进行获取.。CacheResolver常见的实现类如下:
spring cache集成redis快速入门(含源码解读)_第3张图片
对应源码(CacheAspectSupport.java中CacheOperationContext):

public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
			this.metadata = metadata;
			this.args = extractArgs(metadata.method, args);
			this.target = target;
			// 缓存列表caches获取
			this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
			this.cacheNames = createCacheNames(this.caches);
		}

AbstractCacheResolver.java中resolveCaches:

public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
		Collection<String> cacheNames = getCacheNames(context);
		if (cacheNames == null) {
			return Collections.emptyList();
		}
		Collection<Cache> result = new ArrayList<>(cacheNames.size());
		for (String cacheName : cacheNames) {
			Cache cache = getCacheManager().getCache(cacheName);
			if (cache == null) {
				throw new IllegalArgumentException("Cannot find cache named '" +
						cacheName + "' for " + context.getOperation());
			}
			result.add(cache);
		}
		return result;
	}

    2.6 condition作用:将符合条件的数据存入缓存或是删除缓存,不指定时,全部缓存数据都进行存入或清空.支持的spel表达式;

// condition = "#a0 != \"王五\"" 标识如果name为王五的数据不存入缓存中
 @Cacheable(value = "personalInfo",key = "#name",condition = "#a0 != \"王五\"")
    @Override
    public Person findPersonInfo(String name) {
        // 模拟数据库获取数据
        Person person=null;
        if("张三".equals(name)){
            person = new Person(13, "张三");
        }else if("李四".equals(name)){
            person = new Person(14, "李四");
        }else if("王五".equals(name)){
            person = new Person(15, "王五");
        }

        return person;
    }

对应源码解读(CacheAspectSupport.java):

private void collectPutRequests(Collection<CacheOperationContext> contexts,
			@Nullable Object result, Collection<CachePutRequest> putRequests) {

		for (CacheOperationContext context : contexts) {
			// 解析注解中condition标识的条件是否成立,如果成立或是condition中没有指定条件,则将该方法请求存入putRequests中,为下一步添加到缓存中做准备.
			if (isConditionPassing(context, result)) {
				Object key = generateKey(context, result);
				putRequests.add(new CachePutRequest(context, key));
			}
		}
	}
protected boolean isConditionPassing(@Nullable Object result) {
// 每次请求会判断注解中是否有condition属性,如果有则按照spel表达式进行解析,判断条件是否成立.
			if (this.conditionPassing == null) {
				if (StringUtils.hasText(this.metadata.operation.getCondition())) {
					EvaluationContext evaluationContext = createEvaluationContext(result);
					this.conditionPassing = evaluator.condition(this.metadata.operation.getCondition(),
							this.metadata.methodKey, evaluationContext);
				}
				else {
					this.conditionPassing = true;
				}
			}
			return this.conditionPassing;
		}

    2.7 unless作用:与condition作用相反,unless表达式成立时不进行缓存;默认为空,为空时表示进行缓存.

// unless = "#result.size() == 3" 表示方法执行结果集合个数为3时不进行缓存.
 @Cacheable(value = "personList",key = "#root.methodName",unless = "#result.size() == 3")
    @Override
    public List<Person> findPersonList() {
        List<Person> PersonList = Arrays.asList(new Person(13, "张三"),
                new Person(14, "李四"));
        return PersonList;
    }

对应源码解析(CacheAspectSupport.java):

// 判断返回结果是否需要添加到缓存中
protected boolean canPutToCache(@Nullable Object value) {
			String unless = "";
			if (this.metadata.operation instanceof CacheableOperation) {
				unless = ((CacheableOperation) this.metadata.operation).getUnless();
			}
			else if (this.metadata.operation instanceof CachePutOperation) {
				unless = ((CachePutOperation) this.metadata.operation).getUnless();
			}
			// 注解中unless属性中表达式不为空则j进行spel表达式解析
			if (StringUtils.hasText(unless)) {
				EvaluationContext evaluationContext = createEvaluationContext(value);
				return !evaluator.unless(unless, this.metadata.methodKey, evaluationContext);
			}
			return true;
		}

解析表达式判断条件是否存成立最底层逻辑:

public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
		// 表达式左边内容解析
        Object left = this.getLeftOperand().getValueInternal(state).getValue();
        // 表达式右边内容解析
        Object right = this.getRightOperand().getValueInternal(state).getValue();
        this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(left);
        this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(right);
        // 判断条件是否存成立
        return BooleanTypedValue.forValue(equalityCheck(state.getEvaluationContext(), left, right));
    }

    2.8 sync作用:如果有多个线程正在运行,开启时会为同一个键加载值。默认为false,同步操作有几个限制:不能与unless同时使用;只能指定一个缓存,不能组合其他与缓存相关的操作.
    2.9 allEntries作用:是否删除所有缓存信息,默认false,如果设置为true,key属性不能有值,否则只会清空key对应的缓存非清空所有缓存.
对应源码解析(SpringCacheAnnotationParser.java中parseEvictAnnotation):

private CacheEvictOperation parseEvictAnnotation(
			AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {

		CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();

		// 省略部分代码,解析allEntries属性值,赋值给CacheWide
		builder.setCacheWide(cacheEvict.allEntries());
		return op;
	}

执行清除缓存逻辑(CacheAspectSupport.java中performCacheEvict):

private void performCacheEvict(
			CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {

		Object key = null;
		for (Cache cache : context.getCaches()) {
		// 判断@CacheEvict中allEntries是否设置为true,如果是则删除所有,CacheWid对应allEntries,为TRUE,执行doClear为清空所有缓存。
			if (operation.isCacheWide()) {
				logInvalidating(context, operation, null);
				doClear(cache, operation.isBeforeInvocation());
			}
			else {
				if (key == null) {
					key = generateKey(context, result);
				}
				logInvalidating(context, operation, key);
				doEvict(cache, key, operation.isBeforeInvocation());
			}
		}
	}

    2.91 beforeInvocation作用:发送请求,调用方法执行之前进行删除缓存操作,不论后续业务处理是否成功都删除缓存数据,默认false;适用场景:执行删除业务逻辑成功,但由于其他业务处理异常导致缓存删除失败;

// beforeInvocation = true表示方法执行deletePersonalInfo就进行删除缓存数据,即便具体业务中抛出异常缓存也被清空。spring cache底层是根据aop进行对方法前后进行业务增强。先删除缓存,然后代理对象执行方法,不论代理对象执行方法逻辑是否成功,缓存都已被删除。
 @CacheEvict(value = "personalInfo",key = "#name",beforeInvocation = true)
    @Override
    public void deletePersonalInfo(String name) {
        System.out.println("模拟执行数据库删除操作");
        int a=1/0;
    }

对应源码(CacheAspectSupport.java中execute):

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
	
		// 省略部分代码
		// 执行删除缓存操作,仅当方法标注 @CacheEvict,并且beforeInvocation为true时生效
	processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
			// 省略部分代码
			//代理对象执行具体的业务逻辑,如果有异常抛出,以后的逻辑不会进行
			returnValue = invokeOperation(invoker);
			cacheValue = unwrapReturnValue(returnValue);
		
		// @CachePut注解处理逻辑添加缓存处理
		collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

	
		// 缓存请求结果,最终是调用ConcurrentMapCache中put方法将查询缓存数据存入内存ConcurrentMapCache中
		for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
		}

		// @CacheEvict注解处理逻辑对缓存进行删除操作
	processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

		return returnValue;
	}

3.注意事项

    @CachePut注解属性同@Cacheable,底层实现都是添加缓存,注意@CachePut使用时需要将修改后对象信息进行返回(修改的方法中要将修改后的要缓存的结果返回),否则会将已经存在的缓存的value置为null.

@CachePut(value = "personalInfo",key = "#name")
    @Override
    public Person updatePersonalInfo(String name) {
        int a=0;
        // 模拟数据库修改数据,修改张三的年龄
        Person person=null;
        if("张三".equals(name)){
            person = new Person(23, "张三");
        }

        // 主要要将修改后的对象信息进行返回,否则会将之前的缓存value信息置为null
        return person;
    }

    对spring cache 完整执行流程相关源码感兴趣的点击:通俗易懂讲解Spring Cache基于ConcurrentMapCache源码实现原理
    redis序列化处理:springboot集成redis序列化问题汇总
    原创不易,欢迎点赞收藏加评论!

你可能感兴趣的:(redis,spring,cache,源码)