背景:
擴展RedisTemplate的實現的時候寫了這樣一段代碼:
public class BusinessRedisTemplate extends RedisTemplate<String, String> {private final String prefix = "business";public BusinessRedisTemplate (RedisConnectionFactory connectionFactory) {setConnectionFactory(connectionFactory);}public BusinessRedisTemplate (RedisConnectionFactory connectionFactory) {setConnectionFactory(connectionFactory);}}
BusinessRedisTemplate 繼承了RedisTemplate,并重寫了convertAndSend()方法,邏輯比較簡單,統一給消息加個前綴,實際工作中更復雜一點,BusinessRedisTemplate 要生效就需要把它定義為Bean,所以有了如下Bean定義代碼:
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){return new BusinessRedisTemplate (redisConnectionFactory);
}
然后在Service中注入它:
@Autowired
private BusinessRedisTemplate redisTemplate;
然后啟動SpringBoot,竟然報錯了:
a bean of type 'com.business.BusinessRedisTemplate ' that could not be found.
說找不到BusinessRedisTemplate 這個類型的Bean,可是我明明定義了呀…,為什么會這樣?關鍵是我如果把屬性類型改為RedisTemplate就不報錯了,也就是這樣:
@Autowired
private RedisTemplate redisTemplate;
而且我debug了確實找到就是BusinessRedisTemplate 對象,那為什么上面那么寫就找不到Bean呢?情況就是這么個情況,不知道各位大佬想到原因了沒,暫時沒想到的,那就聽我來給大家分析分析。
首先,拋一個問題給大家:Spring在根據屬性進行依賴注入時,所需的Bean對象是否已經存在了?
答案是不一定,得看Bean的創建順序,比如順序是A—>B,A里面依賴了B,就算A和B都是非懶加載的單例Bean,Spring也會按順序進行創建,那么在創建A時就會進行依賴注入,而這個時候B對象是不存在的,所以A在進行依賴注入時需要判斷:Spring容器中有沒有B類型的Bean對象,如果沒有則判斷有沒有B類型的Bean定義,如果有Bean定義,那就此時此刻根據Bean定義把B對象創建出來,如果沒有,則報上述根據類型找不到Bean的錯誤。
回到我們的場景,其實類似,原因就是在創建Service的Bean對象時會針對屬性進行依賴注入,會根據BusinessRedisTemplate 類型去Spring容器找Bean對象,不過這時Spring容器中是沒有BusinessRedisTemplate 類型的Bean對象的,所以Spring會去找BusinessRedisTemplate 類型的Bean定義,那為什么找不到BusinessRedisTemplate 類型的Bean定義呢?我們再來看看我們定義Bean的方式:
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){return new BusinessRedisTemplate (redisConnectionFactory);
}
這個Bean的類型到底是RedisTemplate,還是BusinessRedisTemplate 呢?不一樣嗎?它兩不是父子關系嗎?
來來來,重點來了,當Spring解析@Bean注解時,也就是在生成Bean定義時,會把方法的返回值類型RedisTemplate當做Bean類型,而不會把方法中真正返回的對象類型當做Bean類型,因為在解析@Bean注解時并不會真正執行該方法,所以這個Bean的類型一開始只能是RedisTemplate,只有真正執行了該方法之后才知道它具體的類型是BusinessRedisTemplate 。
所以Service中在進行依賴注入時,只能找到RedisTemplate類型的Bean,而找不到BusinessRedisTemplate 類型的Bean,除非!在進行本次依賴注入之前,BusinessRedisTemplate 這個Bean對象已經被創建出來了,這樣在進行依賴注入的時候,就能根據RedisTemplate類型找到BusinessRedisTemplate 這個Bean對象了(根據父類找到子類對象)。
所以,我們再來看一下依賴注入的代碼:
@Autowired
private BusinessRedisTemplate redisTemplate;
如果屬性類型是BusinessRedisTemplate ,那么就有可能根據BusinessRedisTemplate 類型即找不到Bean對象,也找不到Bean定義,從而報錯。
而如果改成:
@Autowired
private RedisTemplate redisTemplate;
就可以了,因為就算根據RedisTemplate類型找不到Bean對象,也能根據RedisTemplate類型找到Bean定義,最終也能根據Bean定義創建出來BusinessRedisTemplate 對象完成依賴注入。
當然,最好的方式是改Bean的定義:
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){return new BusinessRedisTemplate (redisConnectionFactory);
}
改為:
@Bean
public BusinessRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){return new BusinessRedisTemplate (redisConnectionFactory);
}
這樣不管是根據BusinessRedisTemplate 類型找Bean定義,還是根據RedisTemplate類型找Bean定義,都能找到。
也許有讀者會想到,改成@Resource行不行,只能說可能行,也可能不行,因為@Resource會先根據名字找Bean,找到了自然沒問題,但是如果找不到仍然會再根據類型找Bean,最終也可能進入本文所分析的場景中。