首頁>技術>

最近專案中使用SpringBoot整合Redis,踩到了一個坑:從Redis中獲取資料為null,但實際上Redis中是存在對應的資料的。是什麼原因導致此坑的呢?

本文就帶大家從SpringBoot整合Redis、所踩的坑以及自動配置原始碼分析來學習一下SpringBoot中如何正確的使用Redis。

SpringBoot整合Redis

在SpringBoot專案中只需在pom檔案中引入Redis對應的starter,配置Redis連線資訊即可進行使用了。pom依賴引入:

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency>

對應application配置檔案配置:

spring:  redis:    host: 127.0.0.1    port: 6379    database: 1    password: 123456    timeout: 5000

透過以上兩項配置即完成了Redis的整合,下面便是具體的使用,這裡以單元測試的形式呈現。

@SpringBootTest@RunWith(SpringRunner.class)public class TokenTest {    @Autowired    private RedisTemplate redisTemplate;    @Test    public void getValue() {        Object value = redisTemplate.opsForValue().get("1");        System.out.println("value:" + value);    }}

可以看到直接透過@Autowired注入RedisTemplate之後,即可呼叫RedisTemplate提供的方法操作。RedisTemplate提供了豐富的Redis操作方法,具體使用檢視相應的API即可,這裡不再拓展。

專案中遇到的坑

迴歸到最開始的問題:從Redis中獲取資料為null,但實際上Redis中是存在對應的資料的。

其實問題表象很詭異,但問題的原因很簡單,就是Redis中存資料和取資料時採用了不同的RedisTemplate導致的。

在SpringBoot中,針對Redis的自動配置類預設會初始化兩個RedisTemplate,先來看一下RedisAutoConfiguration中原始碼:

@Configuration@ConditionalOnClass({RedisOperations.class})@EnableConfigurationProperties({RedisProperties.class})@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})public class RedisAutoConfiguration {    @Bean    @ConditionalOnMissingBean(        name = {"redisTemplate"}    )    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {        RedisTemplate<Object, Object> template = new RedisTemplate();        template.setConnectionFactory(redisConnectionFactory);        return template;    }    @Bean    @ConditionalOnMissingBean    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {        StringRedisTemplate template = new StringRedisTemplate();        template.setConnectionFactory(redisConnectionFactory);        return template;    }}

可以看到RedisAutoConfiguration中初始化了兩個RedisTemplate的bean。第一個Bean型別為RedisTemplate<Object, Object>,Bean的名稱為redisTemplate,而且是當容器中不存在對應的Bean name時才會進行初始化。第二Bean型別為StringRedisTemplate,Bean的名稱為stringRedisTemplate,該類繼承自RedisTemplate<String, String>。

也就說一個Bean是針對Object物件處理的,一個是針對String物件進行處理的。

導致出現坑的原因便是set時注入的是RedisTemplate<Object, Object>,而獲取時注入的是StringRedisTemplate。這麼明顯的錯誤應該很容易排查的啊?

問題為什麼隱藏的那麼深?

如果直接是因為兩處型別不一致導致的,的確很好排查,看一下注入的RedisTemplate即可。

但問題難以排查,還因為另外一個因素:@Resource和@Autowired注入的問題。

預設情況下@Resource採用先根據bean名稱注入,找不到再根據型別注入,而@Autowired預設採用根據型別注入。專案獲取資料時採用了@Resource注入方式,如下:

@Resourceprivate RedisTemplate<String, String> redisTemplate;

而儲存時採用的是@Autowired注入的:

@Autowiredprivate RedisTemplate<String, String> redisTemplate;

上面兩種形式的注入,在只存在單個例項時好像並不是什麼問題,要麼其中一個直接報錯,要麼注入成功。但當像上述場景,出現了兩個RedisTemplate時,問題就變得隱蔽了。

當採用@Autowired時,根據型別注入,直接注入了RedisTemplate<String, String>的bean,因為它們的型別都是String的。

而當使用@Resource注入時,預設採用的是根據名稱匹配,原始碼中可以看到redisTemplate對應的型別為RedisTemplate<Object, Object>。因此,兩處注入了不同的RedisTemplate,於是就導致了獲取時獲取不到值的問題。

解決方案

找到問題的根源之後,解決問題便容易多了。

方案一,將@Resource的注入改為@Autowired。

方案二:將@Resource注入的bean名稱由redisTemplate改為stringRedisTemplate。當然根據具體業務場景還有其他解決方案。

小結

關於SpringBoot整合Redis其實很簡單,SpringBoot已經幫我們做了大多數的事情,但因為預設初始化了兩個RedisTemplate,再加上@Autowired和@Resource註解的區別就導致了問題的複雜度。因此,在使用的過程中儘量保持各處採用一致的規範,阿里Java開發手冊推薦使用@Resource註解。同時,當然少不了對原始碼、註解等的使用的深入學習和了解。

14
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 分散式鎖用Redis好?還是Zookeeper好?