2.1配置类默认RedisConfig
package com.taobao.tbfilecrud.config.redis; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); template.setHashValueSerializer(serializer); template.setDefaultSerializer(serializer); // key的序列化采用StringRedisSerializer template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); return template; } }2.2 配置cache
package com.taobao.tbfilecrud.config.redis; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.taobao.tbfilecrud.utils.CodecUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.Cache; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.cache.CacheKeyPrefix; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; import java.util.List; import java.util.UUID; @EnableCaching public class CacheConfig extends CachingConfigurerSupport { private static Logger logger = LoggerFactory.getLogger(CacheConfig.class); @Value("${spring.application.name}") private String applicationName; /** * 配置缓存管理器 * @param redisConnectionFactory 连接工厂 * @param cacheKeyPrefix 缓存key的前缀 * @return */ @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, CacheKeyPrefix cacheKeyPrefix) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); //取出数据序列化配置 ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(mapper); //存入数据序列化配置 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() //配置缓存失效时间10min .entryTtl(Duration.ofSeconds(600)) //缓存key的前缀 .computePrefixWith(cacheKeyPrefix) //key序列化方式 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) //value序列化方式 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)); RedisCacheManager cacheManager = RedisCacheManager .builder(redisConnectionFactory) .cacheDefaults(config) .build(); return cacheManager; } /** * 缓存key前缀,spring项目名:{cacheNames} * @return */ @Bean public CacheKeyPrefix cacheKeyPrefix() { return cacheName -> { StringBuilder sb = new StringBuilder(100); //spring项目名 sb.append(applicationName).append(":"); //缓存名(用户自定义) sb.append(cacheName).append(":"); return sb.toString(); }; } /** * 自定义redis key生成器 * 默认key的生成规则:key的前缀(项目名:缓存名):简单类名:方法名:参数名(多个参数#号,List和非简单类型都有特殊处理) */ @Override @Bean public KeyGenerator keyGenerator() { return (o, method, params) -> { StringBuilder sb = new StringBuilder(); // 类名 sb.append(o.getClass().getSimpleName()) .append(":") // 方法名 .append(method.getName()) .append(":"); // 参数名 int argIndex = 0; for (Object param : params) { if (argIndex > 0) { //多个参数用'#'号隔开 sb.append("#"); } sb.append(getKeyString(param)); argIndex++; } return sb.toString(); }; } /** * redis数据操作异常处理 * 这里的处理:在日志中打印出错误信息,但是放行 * 保证redis服务器出现连接等问题的时候不影响程序的正常运行,使得能够出问题时不用缓存 * @return */ @Bean @Override public CacheErrorHandler errorHandler() { CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() { @Override public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) { redisErrorException(exception, key); } @Override public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) { redisErrorException(exception, key); } @Override public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) { redisErrorException(exception, key); } @Override public void handleCacheClearError(RuntimeException exception, Cache cache) { redisErrorException(exception, null); } }; return cacheErrorHandler; } protected void redisErrorException(Exception exception, Object key){ logger.error("redis异常:key=[{}]", key , exception); } /** * 方法参数处理成rediskey的方式 * @param obj * @return */ private String getKeyString(Object obj) { if (obj == null) { return "NULL"; } try { if (BeanUtils.isSimpleValueType(obj.getClass())) { //简单类型参数处理方式 return obj.toString(); } else if (obj instanceof List) { //List对象参数处理方式 return "List-" + ((List<?>) obj).size() + "@" + CodecUtils.MD5(new ObjectMapper().writeValueAsString(obj)); } else { //其他对象参数处理方式 return obj.getClass().getSimpleName() + "@" + CodecUtils.MD5(new ObjectMapper().writeValueAsString(obj)); } } catch (Exception e) { e.printStackTrace(); return obj.getClass().getSimpleName() + "@" + UUID.randomUUID(); } } }2.3 生成Configuration
package com.taobao.tbfilecrud.config.redis; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; /** * @author CJJ */ @Configuration @Import({RedisConfig.class,CacheConfig.class}) public class ApCloudRedisAutoConfig { }// 缓存默认失效时间:10分钟 Spring项目名:缓存名:简单类名:方法名:参数1#参数2#参数3…
@RunWith(SpringRunner.class) @SpringBootTest(classes = TbfilecrudApplication.class) @ActiveProfiles("dev") public class RedisTest { @Autowired private RedisTemplate redisTemplate; @Test public void set() { redisTemplate.opsForValue().set("aaa", 20); redisTemplate.opsForValue().set("ddd", "20"); } @Test public void get() { Object a = redisTemplate.opsForValue().get("ddd"); System.out.println(a); }4.1 Spring Cache是作用在方法上的,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。 4.2 @Cacheable标记在一个方法上,对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。 需要注意的是:当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。
cacheNames属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
@Service public class UserService { /** 最终redis key:leo-demo:users:UserService:getByUserId:10 */ @Cacheable(cacheNames = "users") //users: 缓存名 public User getUserById(Long id) { // id :10 return findOne(id); } /** Cache是发生在cache1和cache2上的 最终有俩redis key: leo-demo:cache1:UserService:findById:10 leo-demo:cache2:UserService:findById:10 */ @Cacheable({"cache1", "cache2"}) public User findById(Long id) { return findOne(id); } }**
@Service public class UserService{ /** 最终的redis key:leo-demo:users:10 */ @Cacheable(value="users", key="#id") //#参数名 public User find1(Long id) { //id=10 return findOne(id); } /** 最终的redis key:leo-demo:users:10 */ @Cacheable(value="users", key="#p0") //#p参数index public User find2(Long id) { //id=10 return findOne(id); } /** 参数是对象时,获取属性值作为key 最终的redis key:leo-demo:users:10 */ @Cacheable(value="users", key="#user.id") //参数对象.属性名 public User find(User user) { // id=10 return findOne(id); } /** 参数是对象时,获取属性值作为key 最终的redis key:leo-demo:users:10 */ @Cacheable(value="users", key="#p0.id")//#p参数index.属性名 public User find(User user) { // id=10 return findOne(id); } }除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。
属性名称描述示例methodName当前方法名#root.methodNamemethod当前方法#root.method.nametarget当前被调用的对象#root.targettargetClass当前被调用的对象的class#root.targetClassargs当前方法参数组成的数组#root.args[0]caches当前被调用的方法使用的Cache#root.caches[0].name当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 示例:
@Service public class UserService{ /** 最终key:leo-demo:users:UserService:user:10 */ @Cacheable(cacheNames = "users",key = "#root.targetClass.simpleName+':user:'+#user.id") public User getByUserId(User user) { return findOne(user.getId()); } }有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。
@Service public class UserService{ /** 只缓存用户id为偶数的数据 */ @Cacheable(cacheNames = "users", key="#user.id", condition="#user.id%2==0") public User find(User user) { return findOne(user.getId()); } }@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中,可用于修改数据的应用场景。
@Service public class UserService{ /** 要保证最终的redis key一致才能更新成功对应的数据 */ @CachePut(cacheNames = "users",key="#id") //每次都会执行方法,并将结果存入指定的缓存中 public User update(Long id) { return update(id); } }@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
@CacheEvict(value="users", allEntries=true) public void delete(Integer id) { System.out.println("delete user by id: " + id); }清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true) public void delete(Integer id) { System.out.println("delete user by id: " + id); }@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"), @CacheEvict(value = "cache3", allEntries = true) }) public User find(Long id) { return findOne(id); }