1. 程式人生 > >Springboot2.x+shiro+redis整合填坑 (一)redis只做快取的情況

Springboot2.x+shiro+redis整合填坑 (一)redis只做快取的情況

主要記錄關鍵和有坑的地方

前提:

1、SpringBoot+shiro已經整合完畢,如果沒有整合,先查閱之前的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;//過期日期

暫時就是這些關鍵點和關鍵坑,記錄,不然肯定忘記。