Springboot2.x+shiro+redis整合填坑 (一)redis只做快取的情況
主要記錄關鍵和有坑的地方
前提:
1、SpringBoot+shiro已經整合完畢,如果沒有整合,先查閱之前的ofollow,noindex" target="_blank">Springboot2.0 整合shiro許可權管理
2、redis已經安裝完成
3、redis客戶端使用Lettuce,這也是sprinboot2.0後預設的,與jedis的區別,自行百度
4、json使用springboot預設的
一、依賴
<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> //在用使用shiro的情況下整合redis,必須帶這個依賴 <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.1.0</version> </dependency>
必須登出:
<!--與reids快取衝突--> <!--<dependency>--> <!--<groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-devtools</artifactId>--> <!--<optional>true</optional>--> <!--</dependency>-->
二、Application.yml
spring: ...省略 cache: redis: time-to-live: 60s //沒起作用,待研究 type: redis redis: host: 127.0.0.1 port: 6379 password: [email protected] timeout: 10000 lettuce: pool: max-idle: 10 max-active: 10 min-idle: 5 max-wait: 10000 database: 0
三、redis配置類
@Configuration @EnableCaching //開啟快取,重要!!! public class RedisConfig extends CachingConfigurerSupport { @Resource private LettuceConnectionFactory lettuceConnectionFactory; private Duration timeToLive = Duration.ofSeconds(60); @Bean //在沒有指定快取Key的情況下,key生成策略 public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuffer sb = new StringBuffer(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } // 快取管理器 使用Lettuce,和jedis有很大不同 @Bean public CacheManager cacheManager() { //關鍵點,spring cache的註解使用的序列化都從這來,沒有這個配置的話使用的jdk自己的序列化,實際上不影響使用,只是打印出來不適合人眼識別 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))//key序列化方式 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))//value序列化方式 .disableCachingNullValues() .entryTtl(timeToLive);//快取過期時間 RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder .fromConnectionFactory(lettuceConnectionFactory) .cacheDefaults(config) .transactionAware(); return builder.build(); } /** * RedisTemplate配置 在單獨使用redisTemplate的時候 重新定義序列化方式 */ @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { // 設定序列化 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>( Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); RedisSerializer<?> stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer);// key序列化 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化 redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化 redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化 redisTemplate.afterPropertiesSet(); return redisTemplate; } private RedisSerializer<String> keySerializer() { return new StringRedisSerializer(); } private RedisSerializer<Object> valueSerializer() { // 設定序列化 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>( Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); return jackson2JsonRedisSerializer; //兩種方式區別不大 //return new GenericJackson2JsonRedisSerializer(); } }
在自定義序列化過程,GenericJackson2JsonRedisSerializer 和Jackson2JsonRedisSerializer大部分時候表現沒有區別,實際上如果物件中有LinkedHashMap時候,後者會出錯,這個以前坑了我很久,自我懷疑了很久。
四、在service上使用快取,具體Springboot的Cache註解百度上很多。
@CacheConfig(cacheNames = "user") public interface UserService { @Cacheable(key = "'userName'.concat(#userName)") User findByUserName(String userName); }
五、必須修改Shiro的AuthorizingRealm,這裡也是最坑的地方
public class MyShiroRealm extends AuthorizingRealm { @Resource @Lazy //就是這裡,必須延時載入,根本原因是bean例項化的順序上,shiro的bean必須要先例項化,否則@Cacheable註解無效 private UserService userService; //許可權資訊,包括角色以及許可權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 略 } /*主要是用來進行身份認證的,也就是說驗證使用者輸入的賬號和密碼是否正確。*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 略 } }
六、實體中如果有java8time,諸如LocalDateTime,redis快取反序列化的時候會失敗,必須在實體中指定json序列化和反序列化的類@JsonDeserialize和@JsonSerialize
@JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonSerialize(using = LocalDateTimeSerializer.class) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //格式化前臺頁面收到的json時間格式,不指定的話會變成預設的"yyyy-MM-dd'T'HH:mm:ss" @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime;//建立時間 @JsonDeserialize(using = LocalDateDeserializer.class) @JsonSerialize(using = LocalDateSerializer.class) @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate expiredDate;//過期日期
暫時就是這些關鍵點和關鍵坑,記錄,不然肯定忘記。