SpringBoot整合Redis相关组件
整合Redis 经过 Spring Boot 的整合封装与自动化配置,在 Spring Boot 中整合Redis 已经变得非常容易了,开发者只需要引入 Spring Data Redis 依赖,然后简单配下 redis 的基本信息,系统就会提供一个 RedisTemplate 供开发者使用,但是今天松哥想和大伙聊的不是这种用法,而是结合 Cache 的用法。Spring3.1 中开始引入了令人激动的 Cache,在 Spring Boot 中,可以非常方便的使用 Redis 来作为 Cache 的实现,进而实现数据的缓存。
POM文件导入依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > <exclusions > <exclusion > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > </exclusion > <exclusion > <groupId > io.lettuce</groupId > <artifactId > lettuce-core</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > <version > 2.8.0</version > </dependency >
配置文件配置
在application.properties 中配置redis信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring.redis.port =6379 spring.redis.host =127.0.0.1 spring.redis.database =0 spring.redis.jedis.pool.max-active =8 spring.redis.jedis.pool.max-wait =-1 spring.redis.jedis.pool.max-idle =5 spring.redis.jedis.pool.min-idle =0 spring.redis.timeout =10000
创建Redis配置类 使用Jackson2作为序列化器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Configuration public class RedisConfiguration extends CachingConfigurerSupport { @Bean public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate <>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer (Object.class); ObjectMapper om = new ObjectMapper (); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); template.setValueSerializer(jacksonSeial); template.setKeySerializer(new StringRedisSerializer ()); template.setHashKeySerializer(new StringRedisSerializer ()); template.setHashValueSerializer(jacksonSeial); template.afterPropertiesSet(); return template; } }
使用fastJSON作为序列化器 POM文件导入fastjson 1 2 3 4 5 6 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.68</version > </dependency >
自定义序列化类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class FastJsonRedisSerializer <T> implements RedisSerializer <T> { private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8" ); private Class<T> clazz; public FastJsonRedisSerializer (Class<T> clazz) { super (); this .clazz = clazz; } @Override public byte [] serialize(T t) throws SerializationException { if (null == t) { return new byte [0 ]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize (byte [] bytes) throws SerializationException { if (null == bytes || bytes.length <= 0 ) { return null ; } String str = new String (bytes, DEFAULT_CHARSET); return (T) JSON.parseObject(str, clazz); } }
配置类
最近在整合redis的时候,发现数据从redis取出后无法强转为原先存入的对象,因为我的HttpMessage解析用的是FastJson,所以为了保持一致,我将RedisSerializer接口实现了,然后主动注入RedisTemplate,将其中的序列化方式指定为FastJson。但是在执行反序列化的最后一步JSON.parseObject(str, clazz);始终提示(autotype is not support),我的类型不支持被强转,然后去查了一下。
2017年3月15日,fastjson官方发布安全升级公告,该公告介绍fastjson在1.2.24及之前的版本存在代码执行漏洞,当恶意攻击者提交一个精心构造的序列化数据到服务端时,由于fastjson在反序列化时存在漏洞,可导致远程任意代码执行。 自1.2.25及之后的版本,禁用了部分autotype的功能,也就是”@type”这种指定类型的功能会被限制在一定范围内使用。 而由于反序列化对象时,需要检查是否开启了autotype。所以如果反序列化检查时,autotype没有开启,就会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Configuration public class RedisConfiguration extends CachingConfigurerSupport { public RedisConfiguration () { ParserConfig.getGlobalInstance() .addAccept("com.demo.entity" ); } @Bean public RedisCacheConfiguration redisCacheConfiguration () { FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer <>(Object.class); RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(30 )); return configuration; } @Bean public RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory factory) { RedisTemplate<Object, Object> template = new RedisTemplate <>(); FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer (Object.class); template.setValueSerializer(fastJsonRedisSerializer); template.setHashValueSerializer(fastJsonRedisSerializer); template.setKeySerializer(new StringRedisSerializer ()); template.setHashKeySerializer(new StringRedisSerializer ()); template.setConnectionFactory(factory); return template; } }
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @RunWith(value = SpringJUnit4ClassRunner.class) @SpringBootTest(classes = {DempApplication.class}) public class RedisTest { @Autowired RedisTemplate redisTemplate; @Test public void test () { User user = new User (); user.setId(1L ); user.setUserName("xxx" ); user.setAddress("河南郑州" ); redisTemplate.opsForValue().set("user" , user); User value = (User) redisTemplate.opsForValue().get("user" ); System.out.println(value); } }
实现Session共享 前言 发展至今,已经很少还存在单服务的应用架构,不说都使用分布式架构部署, 至少也是多点高可用服务。在多个服务器的情况下,Seession共享就是必须面对的问题了。
解决Session共享问题,大多数人的思路都是比较清晰的, 将需要共享的数据存在某个公共的服务中,如缓存。很多人都采用的Redis,手动将Session存在Redis,需要使用时,再从Redsi中读取数据。毫无疑问,这种方案是可行的,只是在手动操作的工作量确实不少。
在这里采用的Spring-Session来实现。它使用代理过滤器,将Session操作拦截,自动将数据同步到Redis中,以及自动从Redis读取数据。从此,操作分布式的Session就像操作单服务的Session一样,可以为所欲为了。
POM文件导入依赖 1 2 3 4 <dependency > <groupId > org.springframework.session</groupId > <artifactId > spring-session-data-redis</artifactId > </dependency >
配置文件配置
参考上面的配置
创建controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @RestController @RequestMapping(value = "/session") public class SessionShareController { @Value("${server.port}") Integer port; @GetMapping(value = "/set") public String set (HttpSession session) { session.setAttribute("user" ,"wangwq8" ); return String.valueOf(port); } @GetMapping(value = "get") public String get (HttpSession session) { return "用户:" +session.getAttribute("user" )+",端口:" +port; } }
测试
总结 本文主要是Spring Session的简单使用,从上面可以看出,除了引入了Spring Session的jar, 其他方面,不管是代码还是配置,都与之没有什么关联,就相当于在操作最常用的HttpSession,在实际项目中用起来也是相当方便。
整合redisson redisson简介 Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
POM文件导入依赖 1 2 3 4 5 <dependency > <groupId > org.redisson</groupId > <artifactId > redisson-spring-boot-starter</artifactId > <version > 3.12.5</version > </dependency >
配置文件配置
参考上面的配置
redisson 配置类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Configuration public class RedissonConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private String port; @Bean public RedissonClient getRedisson () { Config config = new Config (); config.useSingleServer().setAddress("redis://" + host + ":" + port); return Redisson.create(config); } }
redisson操作类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 @Service("redissonService") public class RedissonService { @Autowired private RedissonClient redissonClient; public void getRedissonClient () throws IOException { Config config = redissonClient.getConfig(); System.out.println(config.toJSON().toString()); } public <T> RBucket<T> getRBucket (String objectName) { RBucket<T> bucket = redissonClient.getBucket(objectName); return bucket; } public <K, V> RMap<K, V> getRMap (String objectName) { RMap<K, V> map = redissonClient.getMap(objectName); return map; } public <V> RSortedSet<V> getRSortedSet (String objectName) { RSortedSet<V> sortedSet = redissonClient.getSortedSet(objectName); return sortedSet; } public <V> RSet<V> getRSet (String objectName) { RSet<V> rSet = redissonClient.getSet(objectName); return rSet; } public <V> RList<V> getRList (String objectName) { RList<V> rList = redissonClient.getList(objectName); return rList; } public <V> RQueue<V> getRQueue (String objectName) { RQueue<V> rQueue = redissonClient.getQueue(objectName); return rQueue; } public <V> RDeque<V> getRDeque (String objectName) { RDeque<V> rDeque = redissonClient.getDeque(objectName); return rDeque; } public RLock getRLock (String objectName) { RLock rLock = redissonClient.getLock(objectName); return rLock; } public RReadWriteLock getRWLock (String objectName) { RReadWriteLock rwlock = redissonClient.getReadWriteLock(objectName); return rwlock; } public RAtomicLong getRAtomicLong (String objectName) { RAtomicLong rAtomicLong = redissonClient.getAtomicLong(objectName); return rAtomicLong; } public RCountDownLatch getRCountDownLatch (String objectName) { RCountDownLatch rCountDownLatch = redissonClient.getCountDownLatch(objectName); return rCountDownLatch; } }
分布式锁测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @RunWith(value = SpringJUnit4ClassRunner.class) @SpringBootTest(classes = {DempApplication.class}) public class RedisTest { private static final Logger log = LoggerFactory.getLogger(RedisTest.class); @Autowired private RedissonService redissonService; @Test public void test () throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(10 ); CountDownLatch countDownLatch = new CountDownLatch (10 ); final String recordId = "recordId_123" ; for (int i = 0 ; i < 10 ; i++) { executorService.execute(() -> { countDownLatch.countDown(); log.info("开始并发执行: " + System.currentTimeMillis()); tryLock(recordId); }); } log.info("线程池完成:" + System.currentTimeMillis()); executorService.shutdown(); log.info("线程池退出:" + System.currentTimeMillis()); Thread.sleep(10000 ); } public void tryLock (String recordId) { RLock lock = redissonService.getRLock(recordId); try { boolean bs = lock.tryLock(5 , 6 , TimeUnit.SECONDS); if (bs) { log.info("进入业务代码: " + recordId + ":" + System.currentTimeMillis()); Thread.sleep(10 ); } else { log.info("数据已被锁定: " + recordId + ":" + System.currentTimeMillis()); } } catch (Exception e) { log.error("出现错误..." , e); lock.unlock(); } } }