springboot提供的Cache注解
当我们在编写查询数据库Service时,需要现在redis中查询是否有所需数据的缓存,如果有,则无需去查询数据库,这无疑提高了查询速度同时减轻了数据库的压力。
在使用springboot整合redis时,相信大家都对@Cacheable这个注解不陌生吧
例如我们需要在Service查询数据库前先去缓存中查询,如果没有在查询数据库,有则不去查询,我们只需要在redisTemplate配置类中加入@EnableCaching注解,并配置Spring Cache注解功能,如下:
package com.sunyuqi.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
class RedisTemplateConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
// 配置Spring Cache注解功能
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
return cacheManager;
}
}
然后在service方法上加上注解即可
@Cacheable(cacheManager = "cacheManager", value = "testcache", key = "#userId")
public User findUserById(String userId) throws Exception {
//此处编写查询数据库代码
User user = ....
return user;
}
该方法执行前会先从redis缓存中查询,如果查询到结果,则该方法不会执行,如果没有结果,该方法才会执行。
当我们需要自定义缓存查询逻辑时,就需要定义注解了。
自定义Cache注解
导入相关依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
首先我们需要定义一个注解
package com.sunyuqi.mycache.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* cache注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
String key();
}
该注解定义在方法上,并且需要传入一个参数key。
定义AOP
package com.sunyuqi.mycache.aop;
import com.sunyuqi.mycache.annotations.Cache;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
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;
@Component
@Aspect
public class CacheAspect {
@Autowired
private RedisTemplate redisTemplate;
@Pointcut("@annotation(com.sunyuqi.mycache.annotations.Cache)")
public void cachePointcut() {
}
// 定义相应的事件
@Around("cachePointcut()")
public Object doCache(ProceedingJoinPoint joinPoint) {
Object value = null;
try {
// 获取当前方法上注解的内容
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取被注解的方法
Method method = joinPoint.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes());
Cache cacheAnnotation = method.getAnnotation(Cache.class);
// 获取到传入注解中的key的值 即"#userID"(我们需要EL解析)
String keyEl = cacheAnnotation.key();
// 创建解析器
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keyEl);
EvaluationContext context = new StandardEvaluationContext();
// 添加参数
Object[] args = joinPoint.getArgs();
DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
String[] parameterNames = discover.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i].toString());
}
// 解析EL表达式
String key = expression.getValue(context).toString();
// 查询缓存,并判定缓存中是否存在
value = redisTemplate.opsForValue().get(key);
if (value != null) {
System.out.println("从缓存中查询到结果:" + value);
return value;
}
// 缓存中没有结果,执行被注解的方法
value = joinPoint.proceed();
// 将结果存入缓存
redisTemplate.opsForValue().set(key, value);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return value;
}
}
在方法上使用自定义注解
@Cache(key = "#userId")
public User findUserById(String userId) throws Exception {
//此处编写查询数据库代码
User user = ....
return user;
}
使用方法是不是很简单?
当然别忘了配置redistemplate
package com.sunyuqi.mycache.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableAspectJAutoProxy
class MyRedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
}
这里有一个点要提醒大家:
我们存入redis的对象实际上是序列化后的字符串。
在反序列化的过程中,也就是我们在从redis中查询缓存,并转化为对象时,如果当前该对象所在的包路径与存入时对象的包路径不同,则会反序列化失败。