上篇文章介绍了利用 SpringCache 和 Redis 设置缓存,可是SpringCache 注解并不支持设置缓存时间,确实很使人头疼。这篇文章将叫你用最简单的方式解决 SpringCache 和 Redis 设置缓存并设置缓存时间。 此篇文章基于上篇博客,有啥不懂的地方请查看上篇博客。 上篇文章连接:优雅的缓存解决方案--SpringCache和Redis集成(SpringBoot)html
@Cacheable注解不支持配置过时时间,全部须要经过配置CacheManneg来配置默认的过时时间和针对每一个类或者是方法进行缓存失效时间配置。java
解决 能够采用以下的配置信息来解决的设置失效时间问题配置信息git
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.io.Serializable;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/** * @Author: MaoLin * @Date: 2019/3/26 17:04 * @Version 1.0 */
@Configuration
@EnableCaching
public class RedisConfig implements Serializable {
/** * 申明缓存管理器,会建立一个切面(aspect)并触发Spring缓存注解的切点(pointcut) * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值 */
/* @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return RedisCacheManager.create(redisConnectionFactory); } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 建立一个模板类 RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); // 将刚才的redis链接工厂设置到模板类中 template.setConnectionFactory(factory); // 设置key的序列化器 template.setKeySerializer(new StringRedisSerializer()); // 设置value的序列化器 //使用Jackson 2,将对象序列化为JSON Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //json转对象类,不设置默认的会将json转成hashmap ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); return template; }*/
/** * 最新版,设置redis缓存过时时间 */
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), this.getRedisCacheConfigurationWithTtl( 60), this.getRedisCacheConfigurationMap() // 指定 key 策略
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
//SsoCache和BasicDataCache进行过时时间配置
redisCacheConfigurationMap.put("messagCache", this.getRedisCacheConfigurationWithTtl(30 * 60)); redisCacheConfigurationMap.put("userCache", this.getRedisCacheConfigurationWithTtl(60));//自定义设置缓存时间
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
复制代码
redisCacheConfigurationMap.put("userCache",this.getRedisCacheConfigurationWithTtl(60));
复制代码
@Cacheable("userCache")
注:名称为配置类里面设置的名称userCache,可设置多个缓存名称及时间Controller测试类github
import com.ml.demo.dao.UserDao;
import com.ml.demo.entity.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
/** * @Author: MaoLin * @Date: 2019/3/26 17:03 * @Version 1.0 */
@RestController
public class testController implements Serializable {
@Resource
private UserDao userDao;
/** * 查询出一条数据而且添加到缓存 * * @param userId * @return */
@RequestMapping("/getUser")
@Cacheable("userCache")
public User getUser(@RequestParam(required = true) String userId) {
System.out.println("若是没有缓存,就会调用下面方法,若是有缓存,则直接输出,不会输出此段话");
return userDao.getUser(Integer.parseInt(userId));
}
/** * 删除一个缓存 * * @param userId * @return */
@RequestMapping(value = "/deleteUser")
@CacheEvict("userCache")
public String deleteUser(@RequestParam(required = true) String userId) {
return "删除成功";
}
/** * 添加一条保存的数据到缓存,缓存的key是当前user的id * * @param user * @return */
@RequestMapping("/saveUser")
@CachePut(value = "userCache", key = "#result.userId +''")
public User saveUser(User user) {
return user;
}
/** * 返回结果userPassword中含有nocache字符串就不缓存 * * @param userId * @return */
@RequestMapping("/getUser2")
@CachePut(value = "userCache", unless = "#result.userPassword.contains('nocache')")
public User getUser2(@RequestParam(required = true) String userId) {
System.out.println("若是走到这里说明,说明缓存没有生效!");
User user = new User(Integer.parseInt(userId), "name_nocache" + userId, "nocache");
return user;
}
@RequestMapping("/getUser3")
@Cacheable(value = "userCache", key = "#root.targetClass.getName() + #root.methodName + #userId")
public User getUser3(@RequestParam(required = true) String userId) {
System.out.println("若是第二次没有走到这里说明缓存被添加了");
return userDao.getUser(Integer.parseInt(userId));
}
}
复制代码
测试运行及结果web
保存缓存 redis
查看缓存 spring
查看redis json
一分钟后缓存过时 缓存
再查询缓存 bash
控制台运行结果
2019-03-31 14:21:05.163 ERROR 17056 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `com.ml.demo.entity.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (byte[])"["com.ml.demo.entity.User",{"userId":11,"userName":"\"张三\"","userPassword":"123"}]"; line: 1, column: 29]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.ml.demo.entity.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (byte[])"["com.ml.demo.entity.User",{"userId":11,"userName":"\"张三\"","userPassword":"123"}]"; line: 1, column: 29]] with root cause 复制代码
这个 bug 调了很久才解决,其实问题很简单。
缘由:
缘由是我在该实体类中添加了一个为了方便实例化该类用的构造函数,致使JVM不会添加默认的无参构造函数,而jackson的反序列化须要无参构造函数,所以报错。
Response实体类同理。
解决:
在实体类中补上一个无参构造器便可。
public User() {}
利用 Spring 提供的缓存机制(对象)结合Redis 实现缓存实际上是很好的方法,可是没有提供设置缓存时间,这个就很不人性化了,Redis 的使用其实 Spring 还提供了 RedisTemplate 和 StringRedisTemplate 这两个类都支持设置缓存时间,若是要是以为 SpringCache 的使用不太方便,能够利用 RedisTemplate 类自定义 Redis 工具类来实现缓存。